[AB-52] upgrading to alpha 12

adding anonymous reporting
reworking message context commands
refactoring interaction packages
adding post execution handling for message context commands and modals
reworking feature mode response
fixing setup using component ids
storing infraction parameters, for example mute duration, with every infraction
adding infractions for more moderation actions
creating general method to format a duration string
adding infractions command
reworking muting to use built-in functionality of discord
enabling chunking of members
removing manual unmuting feature mode
adding ability to update infractions with a command
implemented infraction listeners for ban and warn
refactored infraction notifications
storing log messages to the infraction for editing said log messages
This commit is contained in:
Sheldan
2022-06-25 12:00:20 +02:00
parent 1a1fde0800
commit 68cae74819
363 changed files with 4306 additions and 3388 deletions

View File

@@ -5,10 +5,7 @@ import dev.sheldan.abstracto.antiraid.model.MassPingNotificationModel;
import dev.sheldan.abstracto.core.models.ConditionContextInstance; import dev.sheldan.abstracto.core.models.ConditionContextInstance;
import dev.sheldan.abstracto.core.models.database.AUserInAServer; import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.models.template.display.MemberDisplay; import dev.sheldan.abstracto.core.models.template.display.MemberDisplay;
import dev.sheldan.abstracto.core.service.ConditionService; import dev.sheldan.abstracto.core.service.*;
import dev.sheldan.abstracto.core.service.ConfigService;
import dev.sheldan.abstracto.core.service.PostTargetService;
import dev.sheldan.abstracto.core.service.SystemCondition;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService; import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
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;
@@ -61,19 +58,22 @@ public class MassPingServiceBean implements MassPingService {
@Autowired @Autowired
private UserInServerManagementService userInServerManagementService; private UserInServerManagementService userInServerManagementService;
@Autowired
private MemberService memberService;
@Override @Override
public CompletableFuture<Void> processMessage(Message message) { public CompletableFuture<Void> processMessage(Message message) {
if(message.getMentionedMembers().size() > maxAllowedMentions) { if(message.getMentions().getUsers().size() > maxAllowedMentions) {
Integer level = configService.getLongValueOrConfigDefault(MassPingService.MAX_AFFECTED_LEVEL_KEY, message.getGuild().getIdLong()).intValue(); Integer level = configService.getLongValueOrConfigDefault(MassPingService.MAX_AFFECTED_LEVEL_KEY, message.getGuild().getIdLong()).intValue();
boolean allowed = allowedToMassMention(message, level); boolean allowed = allowedToMassMention(message, level);
if(!allowed) { if(!allowed) {
return muteService.muteMemberWithoutContext(message.getMember()) return memberService.timeoutUserMaxDuration(message.getMember())
.thenAccept(unused -> self.sendMassPingMuteNotification(message)) .thenAccept(unused -> self.sendMassPingMuteNotification(message))
.thenAccept(unused -> log.info("Muted member {} in server {} because of too many member mentions. (> {}).", .thenAccept(unused -> log.info("Muted member {} in server {} because of too many member mentions. (> {}).",
message.getMember().getIdLong(), message.getGuild().getIdLong(), maxAllowedMentions)); message.getAuthor().getIdLong(), message.getGuild().getIdLong(), maxAllowedMentions));
} else { } else {
log.info("User {} in server {} is allowed to mass mention, because of level (or lack of level configuration).", log.info("User {} in server {} is allowed to mass mention, because of level (or lack of level configuration).",
message.getMember().getIdLong(), message.getGuild().getIdLong()); message.getAuthor().getIdLong(), message.getGuild().getIdLong());
return CompletableFuture.completedFuture(null); return CompletableFuture.completedFuture(null);
} }
} else { } else {
@@ -103,7 +103,7 @@ public class MassPingServiceBean implements MassPingService {
MassPingNotificationModel model = MassPingNotificationModel MassPingNotificationModel model = MassPingNotificationModel
.builder() .builder()
.messageLink(message.getJumpUrl()) .messageLink(message.getJumpUrl())
.mentionCount(message.getMentionedMembers().size()) .mentionCount(message.getMentions().getUsers().size())
.messageContent(message.getContentRaw()) .messageContent(message.getContentRaw())
.memberDisplay(MemberDisplay.fromMember(member)) .memberDisplay(MemberDisplay.fromMember(member))
.build(); .build();

View File

@@ -19,10 +19,10 @@ import dev.sheldan.abstracto.assignableroles.service.management.AssignedRoleUser
import dev.sheldan.abstracto.core.config.FeatureDefinition; import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.config.ListenerPriority; import dev.sheldan.abstracto.core.config.ListenerPriority;
import dev.sheldan.abstracto.core.interaction.InteractionService; import dev.sheldan.abstracto.core.interaction.InteractionService;
import dev.sheldan.abstracto.core.listener.ButtonClickedListenerResult; import dev.sheldan.abstracto.core.interaction.button.listener.ButtonClickedListenerResult;
import dev.sheldan.abstracto.core.listener.async.jda.ButtonClickedListener; import dev.sheldan.abstracto.core.interaction.button.listener.ButtonClickedListener;
import dev.sheldan.abstracto.core.models.database.AUserInAServer; import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.models.listener.ButtonClickedListenerModel; import dev.sheldan.abstracto.core.interaction.button.listener.ButtonClickedListenerModel;
import dev.sheldan.abstracto.core.service.RoleService; import dev.sheldan.abstracto.core.service.RoleService;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService; import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
import dev.sheldan.abstracto.core.utils.CompletableFutureList; import dev.sheldan.abstracto.core.utils.CompletableFutureList;

View File

@@ -1,6 +1,6 @@
package dev.sheldan.abstracto.assignableroles.model; package dev.sheldan.abstracto.assignableroles.model;
import dev.sheldan.abstracto.core.models.template.button.ButtonPayload; import dev.sheldan.abstracto.core.interaction.button.ButtonPayload;
import lombok.Builder; import lombok.Builder;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;

View File

@@ -11,6 +11,9 @@ import dev.sheldan.abstracto.assignableroles.service.management.*;
import dev.sheldan.abstracto.core.command.exception.CommandParameterKeyValueWrongTypeException; import dev.sheldan.abstracto.core.command.exception.CommandParameterKeyValueWrongTypeException;
import dev.sheldan.abstracto.core.exception.ChannelNotInGuildException; import dev.sheldan.abstracto.core.exception.ChannelNotInGuildException;
import dev.sheldan.abstracto.core.exception.EmoteNotUsableException; import dev.sheldan.abstracto.core.exception.EmoteNotUsableException;
import dev.sheldan.abstracto.core.interaction.ComponentPayloadManagementService;
import dev.sheldan.abstracto.core.interaction.ComponentPayloadService;
import dev.sheldan.abstracto.core.interaction.ComponentService;
import dev.sheldan.abstracto.core.models.FullEmote; import dev.sheldan.abstracto.core.models.FullEmote;
import dev.sheldan.abstracto.core.models.database.AChannel; import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.models.database.ARole; import dev.sheldan.abstracto.core.models.database.ARole;

View File

@@ -4,11 +4,10 @@ import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand
import dev.sheldan.abstracto.core.command.config.CommandConfiguration; import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo; import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter; import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.config.SlashCommandConfig; import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
import dev.sheldan.abstracto.core.command.execution.CommandContext; import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult; import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.command.execution.ContextConverter; import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService;
import dev.sheldan.abstracto.core.command.slash.parameter.SlashCommandParameterService;
import dev.sheldan.abstracto.core.config.FeatureDefinition; import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.interaction.InteractionService; import dev.sheldan.abstracto.core.interaction.InteractionService;
import dev.sheldan.abstracto.core.service.ChannelService; import dev.sheldan.abstracto.core.service.ChannelService;

View File

@@ -4,11 +4,10 @@ import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand
import dev.sheldan.abstracto.core.command.config.CommandConfiguration; import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo; import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter; import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.config.SlashCommandConfig; import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
import dev.sheldan.abstracto.core.command.execution.CommandContext; import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult; import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.command.execution.ContextConverter; import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService;
import dev.sheldan.abstracto.core.command.slash.parameter.SlashCommandParameterService;
import dev.sheldan.abstracto.core.config.FeatureDefinition; import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.interaction.InteractionService; import dev.sheldan.abstracto.core.interaction.InteractionService;
import dev.sheldan.abstracto.core.service.ChannelService; import dev.sheldan.abstracto.core.service.ChannelService;

View File

@@ -4,11 +4,10 @@ import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand
import dev.sheldan.abstracto.core.command.config.CommandConfiguration; import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo; import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter; import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.config.SlashCommandConfig; import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
import dev.sheldan.abstracto.core.command.execution.CommandContext; import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult; import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.command.execution.ContextConverter; import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService;
import dev.sheldan.abstracto.core.command.slash.parameter.SlashCommandParameterService;
import dev.sheldan.abstracto.core.config.FeatureDefinition; import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.interaction.InteractionService; import dev.sheldan.abstracto.core.interaction.InteractionService;
import dev.sheldan.abstracto.core.service.ChannelService; import dev.sheldan.abstracto.core.service.ChannelService;

View File

@@ -4,11 +4,11 @@ import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand
import dev.sheldan.abstracto.core.command.config.CommandConfiguration; import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo; import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter; import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.config.SlashCommandConfig; import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
import dev.sheldan.abstracto.core.command.config.validator.MinIntegerValueValidator; import dev.sheldan.abstracto.core.command.config.validator.MinIntegerValueValidator;
import dev.sheldan.abstracto.core.command.execution.CommandContext; import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult; import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.command.slash.parameter.SlashCommandParameterService; import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService;
import dev.sheldan.abstracto.core.config.FeatureDefinition; import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.interaction.InteractionService; import dev.sheldan.abstracto.core.interaction.InteractionService;
import dev.sheldan.abstracto.core.service.ChannelService; import dev.sheldan.abstracto.core.service.ChannelService;

View File

@@ -4,7 +4,7 @@ import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand
import dev.sheldan.abstracto.core.command.config.CommandConfiguration; import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo; import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter; import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.config.SlashCommandConfig; import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
import dev.sheldan.abstracto.core.command.execution.CommandContext; import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult; import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.config.FeatureDefinition; import dev.sheldan.abstracto.core.config.FeatureDefinition;

View File

@@ -4,10 +4,10 @@ import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand
import dev.sheldan.abstracto.core.command.config.CommandConfiguration; import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo; import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter; import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.config.SlashCommandConfig; import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
import dev.sheldan.abstracto.core.command.execution.CommandContext; import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult; import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.command.slash.parameter.SlashCommandParameterService; import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService;
import dev.sheldan.abstracto.core.config.FeatureDefinition; import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.exception.EntityGuildMismatchException; import dev.sheldan.abstracto.core.exception.EntityGuildMismatchException;
import dev.sheldan.abstracto.core.interaction.InteractionService; import dev.sheldan.abstracto.core.interaction.InteractionService;

View File

@@ -4,10 +4,10 @@ import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand
import dev.sheldan.abstracto.core.command.config.CommandConfiguration; import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo; import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter; import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.config.SlashCommandConfig; import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
import dev.sheldan.abstracto.core.command.execution.CommandContext; import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult; import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.command.slash.parameter.SlashCommandParameterService; import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService;
import dev.sheldan.abstracto.core.config.FeatureDefinition; import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.exception.EntityGuildMismatchException; import dev.sheldan.abstracto.core.exception.EntityGuildMismatchException;
import dev.sheldan.abstracto.core.interaction.InteractionService; import dev.sheldan.abstracto.core.interaction.InteractionService;

View File

@@ -4,11 +4,11 @@ import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand
import dev.sheldan.abstracto.core.command.config.CommandConfiguration; import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo; import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter; import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.config.SlashCommandConfig; import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
import dev.sheldan.abstracto.core.command.exception.SlashCommandParameterMissingException; import dev.sheldan.abstracto.core.command.exception.SlashCommandParameterMissingException;
import dev.sheldan.abstracto.core.command.execution.CommandContext; import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult; import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.command.slash.parameter.SlashCommandParameterService; import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService;
import dev.sheldan.abstracto.core.config.FeatureDefinition; import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.exception.EntityGuildMismatchException; import dev.sheldan.abstracto.core.exception.EntityGuildMismatchException;
import dev.sheldan.abstracto.core.interaction.InteractionService; import dev.sheldan.abstracto.core.interaction.InteractionService;

View File

@@ -4,10 +4,10 @@ import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand
import dev.sheldan.abstracto.core.command.config.CommandConfiguration; import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo; import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter; import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.config.SlashCommandConfig; import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
import dev.sheldan.abstracto.core.command.execution.CommandContext; import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult; import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.command.slash.parameter.SlashCommandParameterService; import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService;
import dev.sheldan.abstracto.core.config.FeatureDefinition; import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.exception.EntityGuildMismatchException; import dev.sheldan.abstracto.core.exception.EntityGuildMismatchException;
import dev.sheldan.abstracto.core.interaction.InteractionService; import dev.sheldan.abstracto.core.interaction.InteractionService;

View File

@@ -4,10 +4,10 @@ import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand
import dev.sheldan.abstracto.core.command.config.CommandConfiguration; import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo; import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter; import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.config.SlashCommandConfig; import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
import dev.sheldan.abstracto.core.command.execution.CommandContext; import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult; import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.command.slash.parameter.SlashCommandParameterService; import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService;
import dev.sheldan.abstracto.core.config.FeatureDefinition; import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.interaction.InteractionService; import dev.sheldan.abstracto.core.interaction.InteractionService;
import dev.sheldan.abstracto.core.service.ConfigService; import dev.sheldan.abstracto.core.service.ConfigService;

View File

@@ -5,7 +5,8 @@ import dev.sheldan.abstracto.core.command.config.*;
import dev.sheldan.abstracto.core.command.config.validator.MinIntegerValueValidator; import dev.sheldan.abstracto.core.command.config.validator.MinIntegerValueValidator;
import dev.sheldan.abstracto.core.command.execution.CommandContext; import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult; import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.command.slash.parameter.SlashCommandParameterService; import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService;
import dev.sheldan.abstracto.core.config.FeatureDefinition; import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.interaction.InteractionService; import dev.sheldan.abstracto.core.interaction.InteractionService;
import dev.sheldan.abstracto.core.models.database.AServer; import dev.sheldan.abstracto.core.models.database.AServer;

View File

@@ -4,7 +4,7 @@ import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand
import dev.sheldan.abstracto.core.command.config.CommandConfiguration; import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo; import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter; import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.config.SlashCommandConfig; import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
import dev.sheldan.abstracto.core.command.execution.CommandContext; import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult; import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.config.FeatureDefinition; import dev.sheldan.abstracto.core.config.FeatureDefinition;

View File

@@ -4,7 +4,7 @@ import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand
import dev.sheldan.abstracto.core.command.config.CommandConfiguration; import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo; import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter; import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.config.SlashCommandConfig; import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
import dev.sheldan.abstracto.core.command.execution.CommandContext; import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult; import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.config.FeatureDefinition; import dev.sheldan.abstracto.core.config.FeatureDefinition;

View File

@@ -4,10 +4,10 @@ import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand
import dev.sheldan.abstracto.core.command.config.CommandConfiguration; import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo; import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter; import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.config.SlashCommandConfig; import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
import dev.sheldan.abstracto.core.command.execution.CommandContext; import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult; import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.command.slash.parameter.SlashCommandParameterService; import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService;
import dev.sheldan.abstracto.core.config.FeatureDefinition; import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.exception.EntityGuildMismatchException; import dev.sheldan.abstracto.core.exception.EntityGuildMismatchException;
import dev.sheldan.abstracto.core.interaction.InteractionService; import dev.sheldan.abstracto.core.interaction.InteractionService;

View File

@@ -1,13 +1,13 @@
package dev.sheldan.abstracto.linkembed.listener; package dev.sheldan.abstracto.linkembed.listener;
import dev.sheldan.abstracto.core.config.FeatureDefinition; import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.listener.ButtonClickedListenerResult; import dev.sheldan.abstracto.core.interaction.button.listener.ButtonClickedListenerResult;
import dev.sheldan.abstracto.core.listener.async.jda.ButtonClickedListener; import dev.sheldan.abstracto.core.interaction.button.listener.ButtonClickedListener;
import dev.sheldan.abstracto.core.metric.service.MetricService; import dev.sheldan.abstracto.core.metric.service.MetricService;
import dev.sheldan.abstracto.core.models.listener.ButtonClickedListenerModel; import dev.sheldan.abstracto.core.interaction.button.listener.ButtonClickedListenerModel;
import dev.sheldan.abstracto.core.interaction.InteractionService; import dev.sheldan.abstracto.core.interaction.InteractionService;
import dev.sheldan.abstracto.core.service.MessageService; import dev.sheldan.abstracto.core.service.MessageService;
import dev.sheldan.abstracto.core.service.management.ComponentPayloadManagementService; import dev.sheldan.abstracto.core.interaction.ComponentPayloadManagementService;
import dev.sheldan.abstracto.core.templating.service.TemplateService; import dev.sheldan.abstracto.core.templating.service.TemplateService;
import dev.sheldan.abstracto.linkembed.config.LinkEmbedFeatureDefinition; import dev.sheldan.abstracto.linkembed.config.LinkEmbedFeatureDefinition;
import dev.sheldan.abstracto.linkembed.exception.LinkEmbedRemovalNotAllowedException; import dev.sheldan.abstracto.linkembed.exception.LinkEmbedRemovalNotAllowedException;

View File

@@ -3,10 +3,11 @@ package dev.sheldan.abstracto.linkembed.listener.interaction;
import dev.sheldan.abstracto.core.config.FeatureDefinition; import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.interaction.MessageContextConfig; import dev.sheldan.abstracto.core.interaction.MessageContextConfig;
import dev.sheldan.abstracto.core.listener.DefaultListenerResult; import dev.sheldan.abstracto.core.listener.DefaultListenerResult;
import dev.sheldan.abstracto.core.listener.async.MessageContextCommandListener; import dev.sheldan.abstracto.core.interaction.context.message.listener.MessageContextCommandListener;
import dev.sheldan.abstracto.core.models.GuildMemberMessageChannel; import dev.sheldan.abstracto.core.models.GuildMemberMessageChannel;
import dev.sheldan.abstracto.core.models.cache.CachedMessage; import dev.sheldan.abstracto.core.models.cache.CachedMessage;
import dev.sheldan.abstracto.core.models.listener.interaction.MessageContextInteractionModel; import dev.sheldan.abstracto.core.models.listener.interaction.MessageContextInteractionModel;
import dev.sheldan.abstracto.core.interaction.context.ContextCommandService;
import dev.sheldan.abstracto.core.service.MessageCache; import dev.sheldan.abstracto.core.service.MessageCache;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService; import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
import dev.sheldan.abstracto.linkembed.config.LinkEmbedFeatureDefinition; import dev.sheldan.abstracto.linkembed.config.LinkEmbedFeatureDefinition;
@@ -15,7 +16,6 @@ import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message; import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.events.interaction.command.MessageContextInteractionEvent; import net.dv8tion.jda.api.events.interaction.command.MessageContextInteractionEvent;
import net.dv8tion.jda.api.interactions.commands.Command;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@@ -36,6 +36,9 @@ public class MessageEmbedContextCommandListener implements MessageContextCommand
@Autowired @Autowired
private MessageEmbedContextCommandListener self; private MessageEmbedContextCommandListener self;
@Autowired
private ContextCommandService contextCommandService;
@Override @Override
public DefaultListenerResult execute(MessageContextInteractionModel model) { public DefaultListenerResult execute(MessageContextInteractionModel model) {
MessageContextInteractionEvent event = model.getEvent(); MessageContextInteractionEvent event = model.getEvent();
@@ -70,14 +73,14 @@ public class MessageEmbedContextCommandListener implements MessageContextCommand
public MessageContextConfig getConfig() { public MessageContextConfig getConfig() {
return MessageContextConfig return MessageContextConfig
.builder() .builder()
.name("Embed message") .isTemplated(true)
.name("embed_message")
.templateKey("message_embed_message_context_menu_label")
.build(); .build();
} }
@Override @Override
public Boolean handlesEvent(MessageContextInteractionModel model) { public Boolean handlesEvent(MessageContextInteractionModel model) {
return model.getEvent().getName().equals(getConfig().getName()) return contextCommandService.matchesGuildContextName(model, getConfig(), model.getServerId());
&& model.getEvent().isFromGuild()
&& model.getEvent().getCommandType().equals(Command.Type.MESSAGE);
} }
} }

View File

@@ -1,19 +1,19 @@
package dev.sheldan.abstracto.linkembed.service; package dev.sheldan.abstracto.linkembed.service;
import dev.sheldan.abstracto.core.interaction.ComponentService;
import dev.sheldan.abstracto.core.interaction.InteractionService; import dev.sheldan.abstracto.core.interaction.InteractionService;
import dev.sheldan.abstracto.core.models.GuildMemberMessageChannel; import dev.sheldan.abstracto.core.models.GuildMemberMessageChannel;
import dev.sheldan.abstracto.core.models.ServerChannelMessage; import dev.sheldan.abstracto.core.models.ServerChannelMessage;
import dev.sheldan.abstracto.core.models.cache.CachedMessage; import dev.sheldan.abstracto.core.models.cache.CachedMessage;
import dev.sheldan.abstracto.core.models.database.AServer; import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.AUserInAServer; import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.models.template.button.ButtonConfigModel; import dev.sheldan.abstracto.core.interaction.button.ButtonConfigModel;
import dev.sheldan.abstracto.core.service.management.ComponentPayloadManagementService; import dev.sheldan.abstracto.core.interaction.ComponentPayloadManagementService;
import dev.sheldan.abstracto.linkembed.config.LinkEmbedFeatureDefinition; import dev.sheldan.abstracto.linkembed.config.LinkEmbedFeatureDefinition;
import dev.sheldan.abstracto.linkembed.config.LinkEmbedFeatureMode; import dev.sheldan.abstracto.linkembed.config.LinkEmbedFeatureMode;
import dev.sheldan.abstracto.linkembed.model.template.MessageEmbedDeleteButtonPayload; import dev.sheldan.abstracto.linkembed.model.template.MessageEmbedDeleteButtonPayload;
import dev.sheldan.abstracto.linkembed.model.template.MessageEmbeddedModel; import dev.sheldan.abstracto.linkembed.model.template.MessageEmbeddedModel;
import dev.sheldan.abstracto.core.service.*; import dev.sheldan.abstracto.core.service.*;
import dev.sheldan.abstracto.core.service.management.ChannelManagementService;
import dev.sheldan.abstracto.core.service.management.ServerManagementService; import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService; import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
import dev.sheldan.abstracto.core.templating.model.MessageToSend; import dev.sheldan.abstracto.core.templating.model.MessageToSend;
@@ -26,7 +26,6 @@ import dev.sheldan.abstracto.linkembed.service.management.MessageEmbedPostManage
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.GuildMessageChannel; import net.dv8tion.jda.api.entities.GuildMessageChannel;
import net.dv8tion.jda.api.entities.Message; import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.TextChannel;
import net.dv8tion.jda.api.entities.User; import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.interactions.commands.CommandInteraction; import net.dv8tion.jda.api.interactions.commands.CommandInteraction;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@@ -287,7 +286,7 @@ public class MessageEmbedServiceBean implements MessageEmbedService {
buttonConfigModel.setOrigin(MESSAGE_EMBED_DELETE_ORIGIN); buttonConfigModel.setOrigin(MESSAGE_EMBED_DELETE_ORIGIN);
buttonConfigModel.setPayloadType(MessageEmbedDeleteButtonPayload.class); buttonConfigModel.setPayloadType(MessageEmbedDeleteButtonPayload.class);
AServer server = serverManagementService.loadServer(serverId); AServer server = serverManagementService.loadServer(serverId);
componentPayloadManagementService.createPayload(buttonConfigModel, server); componentPayloadManagementService.createButtonPayload(buttonConfigModel, server);
self.loadUserAndPersistMessage(cachedMessage, embeddingUserInServerId, createdMessage, messageEmbeddedModel.getButtonConfigModel().getButtonId()); self.loadUserAndPersistMessage(cachedMessage, embeddingUserInServerId, createdMessage, messageEmbeddedModel.getButtonConfigModel().getButtonId());
return CompletableFuture.completedFuture(null); return CompletableFuture.completedFuture(null);
} else { } else {
@@ -358,7 +357,7 @@ public class MessageEmbedServiceBean implements MessageEmbedService {
private Boolean shouldMentionReferencedAuthor(Message message) { private Boolean shouldMentionReferencedAuthor(Message message) {
if(message.getReferencedMessage() != null) { if(message.getReferencedMessage() != null) {
return message.getMentionedUsers().contains(message.getReferencedMessage().getAuthor()); return message.getMentions().getMentions(Message.MentionType.USER).contains(message.getReferencedMessage().getAuthor());
} }
return false; return false;
} }

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="seedData/data.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@@ -0,0 +1,19 @@
<?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="linkEmbedsFeature" value="(SELECT id FROM feature WHERE key = 'linkEmbeds')"/>
<changeSet author="Sheldan" id="embed_message_context_command">
<insert tableName="context_command">
<column name="name" value="embed_message"/>
<column name="type" value="MESSAGE"/>
<column name="feature_id" valueComputed="${linkEmbedsFeature}"/>
</insert>
</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="context_command.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@@ -10,4 +10,5 @@
<include file="1.2.8-link-embed/collection.xml" relativeToChangelogFile="true"/> <include file="1.2.8-link-embed/collection.xml" relativeToChangelogFile="true"/>
<include file="1.2.12/collection.xml" relativeToChangelogFile="true"/> <include file="1.2.12/collection.xml" relativeToChangelogFile="true"/>
<include file="1.3.0/collection.xml" relativeToChangelogFile="true"/> <include file="1.3.0/collection.xml" relativeToChangelogFile="true"/>
<include file="1.4.0/collection.xml" relativeToChangelogFile="true"/>
</databaseChangeLog> </databaseChangeLog>

View File

@@ -1,6 +1,6 @@
package dev.sheldan.abstracto.linkembed.model.template; package dev.sheldan.abstracto.linkembed.model.template;
import dev.sheldan.abstracto.core.models.template.button.ButtonPayload; import dev.sheldan.abstracto.core.interaction.button.ButtonPayload;
import lombok.Builder; import lombok.Builder;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;

View File

@@ -2,7 +2,7 @@ package dev.sheldan.abstracto.linkembed.model.template;
import dev.sheldan.abstracto.core.models.cache.CachedMessage; import dev.sheldan.abstracto.core.models.cache.CachedMessage;
import dev.sheldan.abstracto.core.models.context.UserInitiatedServerContext; import dev.sheldan.abstracto.core.models.context.UserInitiatedServerContext;
import dev.sheldan.abstracto.core.models.template.button.ButtonConfigModel; import dev.sheldan.abstracto.core.interaction.button.ButtonConfigModel;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import lombok.experimental.SuperBuilder; import lombok.experimental.SuperBuilder;

View File

@@ -5,7 +5,8 @@ import dev.sheldan.abstracto.core.command.condition.CommandCondition;
import dev.sheldan.abstracto.core.command.config.*; import dev.sheldan.abstracto.core.command.config.*;
import dev.sheldan.abstracto.core.command.execution.CommandContext; import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult; import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.command.slash.parameter.SlashCommandParameterService; import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService;
import dev.sheldan.abstracto.core.config.FeatureDefinition; import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.interaction.InteractionService; import dev.sheldan.abstracto.core.interaction.InteractionService;
import dev.sheldan.abstracto.core.service.ChannelService; import dev.sheldan.abstracto.core.service.ChannelService;
@@ -29,6 +30,7 @@ import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import static dev.sheldan.abstracto.moderation.model.BanResult.NOTIFICATION_FAILED;
import static dev.sheldan.abstracto.moderation.service.BanService.BAN_EFFECT_KEY; import static dev.sheldan.abstracto.moderation.service.BanService.BAN_EFFECT_KEY;
@Component @Component
@@ -69,10 +71,14 @@ public class Ban extends AbstractConditionableCommand {
Member banningMember = commandContext.getAuthor(); Member banningMember = commandContext.getAuthor();
return banService.banUserWithNotification(user, reason, commandContext.getAuthor(), 0) return banService.banUserWithNotification(user, reason, commandContext.getAuthor(), 0)
.thenCompose(banResult -> { .thenCompose(banResult -> {
String errorNotification = templateService.renderSimpleTemplate(BAN_NOTIFICATION_NOT_POSSIBLE, guild.getIdLong()); if(banResult == NOTIFICATION_FAILED) {
return channelService.sendTextToChannel(errorNotification, message.getChannel()) String errorNotification = templateService.renderSimpleTemplate(BAN_NOTIFICATION_NOT_POSSIBLE, guild.getIdLong());
.thenAccept(message1 -> log.info("Notified about not being able to send ban notification in server {} and channel {} from user {}." return channelService.sendTextToChannel(errorNotification, message.getChannel())
, guild, message.getChannel().getIdLong(), banningMember.getIdLong())); .thenAccept(message1 -> log.info("Notified about not being able to send ban notification in server {} and channel {} from user {}."
, guild, message.getChannel().getIdLong(), banningMember.getIdLong()));
} else {
return CompletableFuture.completedFuture(null);
}
}) })
.thenApply(aVoid -> CommandResult.fromSuccess()); .thenApply(aVoid -> CommandResult.fromSuccess());
} }
@@ -83,14 +89,28 @@ public class Ban extends AbstractConditionableCommand {
if(slashCommandParameterService.hasCommandOptionWithFullType(USER_PARAMETER, event, OptionType.USER)) { if(slashCommandParameterService.hasCommandOptionWithFullType(USER_PARAMETER, event, OptionType.USER)) {
Member member = slashCommandParameterService.getCommandOption(USER_PARAMETER, event, User.class, Member.class); Member member = slashCommandParameterService.getCommandOption(USER_PARAMETER, event, User.class, Member.class);
return banService.banUserWithNotification(member.getUser(), reason, event.getMember(), 0) return banService.banUserWithNotification(member.getUser(), reason, event.getMember(), 0)
.thenCompose(banResult -> interactionService.replyEmbed(BAN_RESPONSE, event)) .thenCompose(banResult -> {
if(banResult == NOTIFICATION_FAILED) {
String errorNotification = templateService.renderSimpleTemplate(BAN_NOTIFICATION_NOT_POSSIBLE, event.getGuild().getIdLong());
return interactionService.replyString(errorNotification, event);
} else {
return interactionService.replyEmbed(BAN_RESPONSE, event);
}
})
.thenApply(aVoid -> CommandResult.fromSuccess()); .thenApply(aVoid -> CommandResult.fromSuccess());
} else { } else {
String userIdStr = slashCommandParameterService.getCommandOption(USER_PARAMETER, event, User.class, String.class); String userIdStr = slashCommandParameterService.getCommandOption(USER_PARAMETER, event, User.class, String.class);
Long userId = Long.parseLong(userIdStr); Long userId = Long.parseLong(userIdStr);
return userService.retrieveUserForId(userId) return userService.retrieveUserForId(userId)
.thenCompose(user -> banService.banUserWithNotification(user, reason, event.getMember(), 0)) .thenCompose(user -> banService.banUserWithNotification(user, reason, event.getMember(), 0))
.thenCompose(banResult -> interactionService.replyEmbed(BAN_RESPONSE, event)) .thenCompose(banResult -> {
if(banResult == NOTIFICATION_FAILED) {
String errorNotification = templateService.renderSimpleTemplate(BAN_NOTIFICATION_NOT_POSSIBLE, event.getGuild().getIdLong());
return interactionService.replyString(errorNotification, event);
} else {
return interactionService.replyEmbed(BAN_RESPONSE, event);
}
})
.thenApply(banResult -> CommandResult.fromSuccess()); .thenApply(banResult -> CommandResult.fromSuccess());
} }
} }

View File

@@ -4,7 +4,7 @@ import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand
import dev.sheldan.abstracto.core.command.config.CommandConfiguration; import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo; import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter; import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.config.SlashCommandConfig; import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
import dev.sheldan.abstracto.core.command.execution.CommandContext; import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult; import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.config.FeatureDefinition; import dev.sheldan.abstracto.core.config.FeatureDefinition;
@@ -64,7 +64,7 @@ public class DecayAllWarnings extends AbstractConditionableCommand {
SlashCommandConfig slashCommandConfig = SlashCommandConfig SlashCommandConfig slashCommandConfig = SlashCommandConfig
.builder() .builder()
.enabled(true) .enabled(true)
.rootCommandName(ModerationSlashCommandNames.MUTE) .rootCommandName(ModerationSlashCommandNames.WARN_DECAY)
.commandName(DECAY_ALL_WARNINGS_COMMAND) .commandName(DECAY_ALL_WARNINGS_COMMAND)
.build(); .build();

View File

@@ -4,7 +4,7 @@ import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand
import dev.sheldan.abstracto.core.command.config.CommandConfiguration; import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo; import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter; import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.config.SlashCommandConfig; import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
import dev.sheldan.abstracto.core.command.execution.CommandContext; import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult; import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.config.FeatureDefinition; import dev.sheldan.abstracto.core.config.FeatureDefinition;

View File

@@ -5,7 +5,8 @@ import dev.sheldan.abstracto.core.command.config.*;
import dev.sheldan.abstracto.core.command.config.validator.MinIntegerValueValidator; import dev.sheldan.abstracto.core.command.config.validator.MinIntegerValueValidator;
import dev.sheldan.abstracto.core.command.execution.CommandContext; import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult; import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.command.slash.parameter.SlashCommandParameterService; import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService;
import dev.sheldan.abstracto.core.config.FeatureDefinition; import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.interaction.InteractionService; import dev.sheldan.abstracto.core.interaction.InteractionService;
import dev.sheldan.abstracto.moderation.config.ModerationModuleDefinition; import dev.sheldan.abstracto.moderation.config.ModerationModuleDefinition;

View File

@@ -5,7 +5,8 @@ import dev.sheldan.abstracto.core.command.config.*;
import dev.sheldan.abstracto.core.command.config.validator.MinIntegerValueValidator; import dev.sheldan.abstracto.core.command.config.validator.MinIntegerValueValidator;
import dev.sheldan.abstracto.core.command.execution.CommandContext; import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult; import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.command.slash.parameter.SlashCommandParameterService; import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService;
import dev.sheldan.abstracto.core.config.FeatureDefinition; import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.interaction.InteractionService; import dev.sheldan.abstracto.core.interaction.InteractionService;
import dev.sheldan.abstracto.moderation.config.ModerationModuleDefinition; import dev.sheldan.abstracto.moderation.config.ModerationModuleDefinition;

View File

@@ -0,0 +1,98 @@
package dev.sheldan.abstracto.moderation.command;
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.execution.CommandResult;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.interaction.InteractionService;
import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService;
import dev.sheldan.abstracto.moderation.config.ModerationModuleDefinition;
import dev.sheldan.abstracto.moderation.config.ModerationSlashCommandNames;
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition;
import dev.sheldan.abstracto.moderation.service.InfractionService;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@Component
public class EditInfraction extends AbstractConditionableCommand {
private static final String EDIT_INFRACTION_COMMAND = "editInfraction";
private static final String REASON_PARAMETER = "newReason";
private static final String ID_PARAMETER = "id";
private static final String EDIT_INFRACTION_RESPONSE = "editInfraction_response";
@Autowired
private InteractionService interactionService;
@Autowired
private SlashCommandParameterService slashCommandParameterService;
@Autowired
private InfractionService infractionService;
@Override
public CompletableFuture<CommandResult> executeSlash(SlashCommandInteractionEvent event) {
Long infractionId = slashCommandParameterService.getCommandOption(ID_PARAMETER, event, Long.class, Integer.class).longValue();
String newReason = slashCommandParameterService.getCommandOption(REASON_PARAMETER, event, String.class);
return infractionService.editInfraction(infractionId, newReason, event.getGuild().getIdLong())
.thenCompose(unused -> interactionService.replyEmbed(EDIT_INFRACTION_RESPONSE, event))
.thenApply(interactionHook -> CommandResult.fromSuccess());
}
@Override
public CommandConfiguration getConfiguration() {
List<Parameter> parameters = new ArrayList<>();
Parameter idParameter = Parameter
.builder()
.name(ID_PARAMETER)
.type(Long.class)
.templated(true)
.build();
parameters.add(idParameter);
Parameter typeParameter = Parameter
.builder()
.name(REASON_PARAMETER)
.type(String.class)
.templated(true)
.build();
parameters.add(typeParameter);
SlashCommandConfig slashCommandConfig = SlashCommandConfig
.builder()
.enabled(true)
.rootCommandName(ModerationSlashCommandNames.INFRACTIONS)
.commandName("edit")
.build();
HelpInfo helpInfo = HelpInfo
.builder()
.templated(true)
.build();
return CommandConfiguration.builder()
.name(EDIT_INFRACTION_COMMAND)
.module(ModerationModuleDefinition.MODERATION)
.templated(true)
.async(true)
.causesReaction(false)
.slashCommandConfig(slashCommandConfig)
.supportsEmbedException(true)
.parameters(parameters)
.help(helpInfo)
.build();
}
@Override
public FeatureDefinition getFeature() {
return ModerationFeatureDefinition.INFRACTIONS;
}
}

View File

@@ -0,0 +1,212 @@
package dev.sheldan.abstracto.moderation.command;
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.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.exception.EntityGuildMismatchException;
import dev.sheldan.abstracto.core.interaction.InteractionService;
import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.models.template.display.MemberDisplay;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.service.PaginatorService;
import dev.sheldan.abstracto.core.service.UserService;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
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.ModerationModuleDefinition;
import dev.sheldan.abstracto.moderation.config.ModerationSlashCommandNames;
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition;
import dev.sheldan.abstracto.moderation.model.database.Infraction;
import dev.sheldan.abstracto.moderation.model.database.InfractionParameter;
import dev.sheldan.abstracto.moderation.model.template.command.InfractionEntry;
import dev.sheldan.abstracto.moderation.model.template.command.InfractionsModel;
import dev.sheldan.abstracto.moderation.service.management.InfractionManagementService;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
@Component
public class Infractions extends AbstractConditionableCommand {
private static final String INFRACTIONS_COMMAND = "infractions";
private static final String USER_PARAMETER = "user";
private static final String INFRACTIONS_RESPONSE_TEMPLATE = "infractions_display_response";
private static final String NO_INFRACTIONS_TEMPLATE_KEY = "infractions_no_infractions_found";
@Autowired
private InfractionManagementService infractionManagementService;
@Autowired
private UserInServerManagementService userInServerManagementService;
@Autowired
private ServerManagementService serverManagementService;
@Autowired
private TemplateService templateService;
@Autowired
private ChannelService channelService;
@Autowired
private PaginatorService paginatorService;
@Autowired
private InteractionService interactionService;
@Autowired
private SlashCommandParameterService slashCommandParameterService;
@Autowired
private UserService userService;
@Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
List<Infraction> infractions;
if(!commandContext.getParameters().getParameters().isEmpty()) {
Member member = (Member) commandContext.getParameters().getParameters().get(0);
if(!member.getGuild().equals(commandContext.getGuild())) {
throw new EntityGuildMismatchException();
}
infractions = infractionManagementService.getInfractionsForUser(userInServerManagementService.loadOrCreateUser(member));
} else {
AServer server = serverManagementService.loadServer(commandContext.getGuild());
infractions = infractionManagementService.getInfractionsForServer(server);
}
if(infractions.isEmpty()) {
MessageToSend messageToSend = templateService.renderEmbedTemplate(NO_INFRACTIONS_TEMPLATE_KEY, new Object(), commandContext.getGuild().getIdLong());
return FutureUtils.toSingleFutureGeneric(channelService.sendMessageToSendToChannel(messageToSend, commandContext.getChannel()))
.thenApply(unused -> CommandResult.fromSuccess());
} else {
List<InfractionEntry> convertedInfractions = fromInfractions(infractions);
InfractionsModel model = InfractionsModel
.builder()
.entries(convertedInfractions)
.build();
return paginatorService.createPaginatorFromTemplate(INFRACTIONS_RESPONSE_TEMPLATE, model, commandContext.getChannel(), commandContext.getAuthor().getIdLong())
.thenApply(unused -> CommandResult.fromSuccess());
}
}
@Override
public CompletableFuture<CommandResult> executeSlash(SlashCommandInteractionEvent event) {
List<Infraction> infractions;
if(slashCommandParameterService.hasCommandOptionWithFullType(USER_PARAMETER, event, OptionType.USER)) {
Member member = slashCommandParameterService.getCommandOption(USER_PARAMETER, event, User.class, Member.class);
if(!member.getGuild().equals(event.getGuild())) {
throw new EntityGuildMismatchException();
}
infractions = infractionManagementService.getInfractionsForUser(userInServerManagementService.loadOrCreateUser(member));
} else if(slashCommandParameterService.hasCommandOptionWithFullType(USER_PARAMETER, event, OptionType.STRING)){
String userIdStr = slashCommandParameterService.getCommandOption(USER_PARAMETER, event, User.class, String.class);
Long userId = Long.parseLong(userIdStr);
AUserInAServer userInServer = userInServerManagementService.createUserInServer(event.getGuild().getIdLong(), userId);
infractions = infractionManagementService.getInfractionsForUser(userInServer);
} else {
AServer server = serverManagementService.loadServer(event.getGuild());
infractions = infractionManagementService.getInfractionsForServer(server);
}
if(infractions.isEmpty()) {
MessageToSend messageToSend = templateService.renderEmbedTemplate(NO_INFRACTIONS_TEMPLATE_KEY, new Object(), event.getGuild().getIdLong());
return interactionService.replyMessageToSend(messageToSend, event)
.thenApply(interactionHook -> CommandResult.fromSuccess());
} else {
List<InfractionEntry> convertedInfractions = fromInfractions(infractions);
InfractionsModel model = InfractionsModel
.builder()
.entries(convertedInfractions)
.build();
return paginatorService.createPaginatorFromTemplate(INFRACTIONS_RESPONSE_TEMPLATE, model, event)
.thenApply(unused -> CommandResult.fromSuccess());
}
}
public List<InfractionEntry> fromInfractions(List<Infraction> infractions) {
return infractions
.stream()
.map(this::fromInfraction)
.collect(Collectors.toList());
}
private InfractionEntry fromInfraction(Infraction infraction) {
Map<String, String> parameters = infraction
.getParameters()
.stream()
.collect(Collectors.toMap(infractionParameter -> infractionParameter.getInfractionParameterId().getName(), InfractionParameter::getValue));
return InfractionEntry
.builder()
.infractionId(infraction.getId())
.serverId(infraction.getServer().getId())
.decayed(infraction.getDecayed())
.decayDate(infraction.getDecayedDate())
.parameters(parameters)
.creationDate(infraction.getCreated())
.infractionUser(MemberDisplay.fromAUserInAServer(infraction.getUser()))
.infractionCreationUser(MemberDisplay.fromAUserInAServer(infraction.getInfractionCreator()))
.reason(infraction.getDescription())
.type(infraction.getType())
.build();
}
@Override
public CommandConfiguration getConfiguration() {
List<Parameter> parameters = new ArrayList<>();
Parameter userParameter = Parameter
.builder()
.name(USER_PARAMETER)
.type(User.class)
.templated(true)
.optional(true)
.build();
parameters.add(userParameter);
SlashCommandConfig slashCommandConfig = SlashCommandConfig
.builder()
.enabled(true)
.rootCommandName(ModerationSlashCommandNames.INFRACTIONS)
.commandName("list")
.build();
HelpInfo helpInfo = HelpInfo
.builder()
.templated(true)
.build();
return CommandConfiguration.builder()
.name(INFRACTIONS_COMMAND)
.module(ModerationModuleDefinition.MODERATION)
.templated(true)
.async(true)
.causesReaction(false)
.slashCommandConfig(slashCommandConfig)
.supportsEmbedException(true)
.parameters(parameters)
.help(helpInfo)
.build();
}
@Override
public FeatureDefinition getFeature() {
return ModerationFeatureDefinition.INFRACTIONS;
}
}

View File

@@ -5,7 +5,8 @@ import dev.sheldan.abstracto.core.command.condition.CommandCondition;
import dev.sheldan.abstracto.core.command.config.*; import dev.sheldan.abstracto.core.command.config.*;
import dev.sheldan.abstracto.core.command.execution.CommandContext; import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult; import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.command.slash.parameter.SlashCommandParameterService; import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService;
import dev.sheldan.abstracto.core.config.FeatureDefinition; import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.exception.EntityGuildMismatchException; import dev.sheldan.abstracto.core.exception.EntityGuildMismatchException;
import dev.sheldan.abstracto.core.interaction.InteractionService; import dev.sheldan.abstracto.core.interaction.InteractionService;

View File

@@ -5,11 +5,11 @@ import dev.sheldan.abstracto.core.command.condition.CommandCondition;
import dev.sheldan.abstracto.core.command.config.*; import dev.sheldan.abstracto.core.command.config.*;
import dev.sheldan.abstracto.core.command.execution.CommandContext; import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult; import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.command.slash.parameter.SlashCommandParameterService; import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService;
import dev.sheldan.abstracto.core.config.FeatureDefinition; import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.exception.EntityGuildMismatchException; import dev.sheldan.abstracto.core.exception.EntityGuildMismatchException;
import dev.sheldan.abstracto.core.interaction.InteractionService; import dev.sheldan.abstracto.core.interaction.InteractionService;
import dev.sheldan.abstracto.core.models.ServerChannelMessage;
import dev.sheldan.abstracto.core.templating.service.TemplateService; import dev.sheldan.abstracto.core.templating.service.TemplateService;
import dev.sheldan.abstracto.core.utils.ParseUtils; import dev.sheldan.abstracto.core.utils.ParseUtils;
import dev.sheldan.abstracto.moderation.config.ModerationModuleDefinition; import dev.sheldan.abstracto.moderation.config.ModerationModuleDefinition;
@@ -18,7 +18,6 @@ import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefiniti
import dev.sheldan.abstracto.moderation.model.template.command.MuteContext; import dev.sheldan.abstracto.moderation.model.template.command.MuteContext;
import dev.sheldan.abstracto.moderation.service.MuteService; import dev.sheldan.abstracto.moderation.service.MuteService;
import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.GuildMessageChannel;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@@ -59,29 +58,19 @@ public class Mute extends AbstractConditionableCommand {
List<Object> parameters = commandContext.getParameters().getParameters(); List<Object> parameters = commandContext.getParameters().getParameters();
Member member = (Member) parameters.get(0); Member member = (Member) parameters.get(0);
Guild guild = commandContext.getGuild(); Guild guild = commandContext.getGuild();
GuildMessageChannel channel = commandContext.getChannel();
if(!member.getGuild().equals(guild)) { if(!member.getGuild().equals(guild)) {
throw new EntityGuildMismatchException(); throw new EntityGuildMismatchException();
} }
Duration duration = (Duration) parameters.get(1); Duration duration = (Duration) parameters.get(1);
String defaultReason = templateService.renderSimpleTemplate(MUTE_DEFAULT_REASON_TEMPLATE, guild.getIdLong()); String defaultReason = templateService.renderSimpleTemplate(MUTE_DEFAULT_REASON_TEMPLATE, guild.getIdLong());
String reason = parameters.size() == 3 ? (String) parameters.get(2) : defaultReason; String reason = parameters.size() == 3 ? (String) parameters.get(2) : defaultReason;
ServerChannelMessage context = ServerChannelMessage
.builder()
.serverId(guild.getIdLong())
.channelId(channel.getIdLong())
.messageId(commandContext.getMessage().getIdLong())
.build();
MuteContext muteLogModel = MuteContext MuteContext muteLogModel = MuteContext
.builder() .builder()
.muteDate(Instant.now())
.muteTargetDate(Instant.now().plus(duration)) .muteTargetDate(Instant.now().plus(duration))
.mutedUser(member) .mutedUser(member)
.channelId(commandContext.getChannel().getIdLong())
.reason(reason) .reason(reason)
.contextChannel(channel)
.message(commandContext.getMessage())
.mutingUser(commandContext.getAuthor()) .mutingUser(commandContext.getAuthor())
.context(context)
.build(); .build();
return muteService.muteMemberWithLog(muteLogModel) return muteService.muteMemberWithLog(muteLogModel)
.thenApply(aVoid -> CommandResult.fromSuccess()); .thenApply(aVoid -> CommandResult.fromSuccess());
@@ -90,7 +79,6 @@ public class Mute extends AbstractConditionableCommand {
@Override @Override
public CompletableFuture<CommandResult> executeSlash(SlashCommandInteractionEvent event) { public CompletableFuture<CommandResult> executeSlash(SlashCommandInteractionEvent event) {
Guild guild = event.getGuild(); Guild guild = event.getGuild();
GuildMessageChannel channel = event.getGuildChannel();
Member targetMember = slashCommandParameterService.getCommandOption(USER_PARAMETER, event, Member.class); Member targetMember = slashCommandParameterService.getCommandOption(USER_PARAMETER, event, Member.class);
String durationStr = slashCommandParameterService.getCommandOption(DURATION_PARAMETER, event, Duration.class, String.class); String durationStr = slashCommandParameterService.getCommandOption(DURATION_PARAMETER, event, Duration.class, String.class);
Duration duration = ParseUtils.parseDuration(durationStr); Duration duration = ParseUtils.parseDuration(durationStr);
@@ -100,20 +88,13 @@ public class Mute extends AbstractConditionableCommand {
} else { } else {
reason = templateService.renderSimpleTemplate(MUTE_DEFAULT_REASON_TEMPLATE, guild.getIdLong()); reason = templateService.renderSimpleTemplate(MUTE_DEFAULT_REASON_TEMPLATE, guild.getIdLong());
} }
ServerChannelMessage context = ServerChannelMessage
.builder()
.serverId(guild.getIdLong())
.channelId(channel.getIdLong())
.build();
MuteContext muteLogModel = MuteContext MuteContext muteLogModel = MuteContext
.builder() .builder()
.muteDate(Instant.now())
.muteTargetDate(Instant.now().plus(duration)) .muteTargetDate(Instant.now().plus(duration))
.mutedUser(targetMember) .mutedUser(targetMember)
.reason(reason) .reason(reason)
.contextChannel(channel) .channelId(event.getChannel().getIdLong())
.mutingUser(event.getMember()) .mutingUser(event.getMember())
.context(context)
.build(); .build();
return muteService.muteMemberWithLog(muteLogModel) return muteService.muteMemberWithLog(muteLogModel)
.thenCompose(unused -> interactionService.replyEmbed(MUTE_RESPONSE, event)) .thenCompose(unused -> interactionService.replyEmbed(MUTE_RESPONSE, event))
@@ -146,7 +127,11 @@ public class Mute extends AbstractConditionableCommand {
.build(); .build();
List<Parameter> parameters = Arrays.asList(userParameter, durationParameter, reasonParameter); List<Parameter> parameters = Arrays.asList(userParameter, durationParameter, reasonParameter);
HelpInfo helpInfo = HelpInfo.builder().templated(true).hasExample(true).build(); HelpInfo helpInfo = HelpInfo
.builder()
.templated(true)
.hasExample(true)
.build();
EffectConfig muteEffect = EffectConfig EffectConfig muteEffect = EffectConfig
.builder() .builder()
.position(0) .position(0)

View File

@@ -4,10 +4,10 @@ import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand
import dev.sheldan.abstracto.core.command.config.CommandConfiguration; import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo; import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter; import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.config.SlashCommandConfig; import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
import dev.sheldan.abstracto.core.command.execution.CommandContext; import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult; import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.command.slash.parameter.SlashCommandParameterService; import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService;
import dev.sheldan.abstracto.core.config.FeatureDefinition; import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.exception.EntityGuildMismatchException; import dev.sheldan.abstracto.core.exception.EntityGuildMismatchException;
import dev.sheldan.abstracto.core.interaction.InteractionService; import dev.sheldan.abstracto.core.interaction.InteractionService;

View File

@@ -4,7 +4,7 @@ import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand
import dev.sheldan.abstracto.core.command.config.CommandConfiguration; import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo; import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter; import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.config.SlashCommandConfig; import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
import dev.sheldan.abstracto.core.command.execution.CommandContext; import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult; import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.config.FeatureDefinition; import dev.sheldan.abstracto.core.config.FeatureDefinition;

View File

@@ -5,7 +5,8 @@ import dev.sheldan.abstracto.core.command.config.*;
import dev.sheldan.abstracto.core.command.config.validator.MinIntegerValueValidator; import dev.sheldan.abstracto.core.command.config.validator.MinIntegerValueValidator;
import dev.sheldan.abstracto.core.command.execution.CommandContext; import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult; import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.command.slash.parameter.SlashCommandParameterService; import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService;
import dev.sheldan.abstracto.core.config.FeatureDefinition; import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.exception.EntityGuildMismatchException; import dev.sheldan.abstracto.core.exception.EntityGuildMismatchException;
import dev.sheldan.abstracto.core.interaction.InteractionService; import dev.sheldan.abstracto.core.interaction.InteractionService;

View File

@@ -1,69 +0,0 @@
package dev.sheldan.abstracto.moderation.command;
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.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.exception.EntityGuildMismatchException;
import dev.sheldan.abstracto.core.models.database.ARole;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.service.management.RoleManagementService;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.moderation.config.ModerationModuleDefinition;
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition;
import dev.sheldan.abstracto.moderation.service.management.MuteRoleManagementService;
import net.dv8tion.jda.api.entities.Role;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
@Component
public class SetMuteRole extends AbstractConditionableCommand {
@Autowired
private MuteRoleManagementService muteRoleManagementService;
@Autowired
private RoleManagementService roleManagementService;
@Autowired
private ServerManagementService serverManagementService;
@Override
public CommandResult execute(CommandContext commandContext) {
Role jdaRole = (Role) commandContext.getParameters().getParameters().get(0);
if(!jdaRole.getGuild().equals(commandContext.getGuild())) {
throw new EntityGuildMismatchException();
}
ARole role = roleManagementService.findRole(jdaRole.getIdLong());
AServer server = serverManagementService.loadServer(commandContext.getGuild());
muteRoleManagementService.setMuteRoleForServer(server, role);
return CommandResult.fromSuccess();
}
@Override
public CommandConfiguration getConfiguration() {
List<Parameter> parameters = new ArrayList<>();
parameters.add(Parameter.builder().name("role").templated(true).type(Role.class).build());
HelpInfo helpInfo = HelpInfo.builder().templated(true).build();
return CommandConfiguration.builder()
.name("setMuteRole")
.module(ModerationModuleDefinition.MODERATION)
.templated(true)
.supportsEmbedException(true)
.causesReaction(true)
.parameters(parameters)
.help(helpInfo)
.build();
}
@Override
public FeatureDefinition getFeature() {
return ModerationFeatureDefinition.MUTING;
}
}

View File

@@ -4,10 +4,10 @@ import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand
import dev.sheldan.abstracto.core.command.config.CommandConfiguration; import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo; import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter; import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.config.SlashCommandConfig; import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
import dev.sheldan.abstracto.core.command.execution.CommandContext; import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult; import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.command.slash.parameter.SlashCommandParameterService; import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService;
import dev.sheldan.abstracto.core.config.FeatureDefinition; import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.exception.EntityGuildMismatchException; import dev.sheldan.abstracto.core.exception.EntityGuildMismatchException;
import dev.sheldan.abstracto.core.interaction.InteractionService; import dev.sheldan.abstracto.core.interaction.InteractionService;

View File

@@ -5,10 +5,10 @@ import dev.sheldan.abstracto.core.command.condition.CommandCondition;
import dev.sheldan.abstracto.core.command.config.CommandConfiguration; import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo; import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter; import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.config.SlashCommandConfig; import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
import dev.sheldan.abstracto.core.command.execution.CommandContext; import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult; import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.command.slash.parameter.SlashCommandParameterService; import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService;
import dev.sheldan.abstracto.core.config.FeatureDefinition; import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.interaction.InteractionService; import dev.sheldan.abstracto.core.interaction.InteractionService;
import dev.sheldan.abstracto.core.service.UserService; import dev.sheldan.abstracto.core.service.UserService;

View File

@@ -4,10 +4,10 @@ import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand
import dev.sheldan.abstracto.core.command.config.CommandConfiguration; import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo; import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter; import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.config.SlashCommandConfig; import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
import dev.sheldan.abstracto.core.command.execution.CommandContext; import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult; import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.command.slash.parameter.SlashCommandParameterService; import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService;
import dev.sheldan.abstracto.core.config.FeatureDefinition; import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.exception.EntityGuildMismatchException; import dev.sheldan.abstracto.core.exception.EntityGuildMismatchException;
import dev.sheldan.abstracto.core.interaction.InteractionService; import dev.sheldan.abstracto.core.interaction.InteractionService;
@@ -53,7 +53,7 @@ public class UnMute extends AbstractConditionableCommand {
throw new EntityGuildMismatchException(); throw new EntityGuildMismatchException();
} }
AUserInAServer userToUnMute = userInServerManagementService.loadOrCreateUser(member); AUserInAServer userToUnMute = userInServerManagementService.loadOrCreateUser(member);
return muteService.unMuteUser(userToUnMute).thenApply(aVoid -> return muteService.unMuteUser(userToUnMute, commandContext.getAuthor()).thenApply(aVoid ->
CommandResult.fromSuccess() CommandResult.fromSuccess()
); );
} }
@@ -65,7 +65,7 @@ public class UnMute extends AbstractConditionableCommand {
throw new EntityGuildMismatchException(); throw new EntityGuildMismatchException();
} }
AUserInAServer userToUnMute = userInServerManagementService.loadOrCreateUser(targetMember); AUserInAServer userToUnMute = userInServerManagementService.loadOrCreateUser(targetMember);
return muteService.unMuteUser(userToUnMute) return muteService.unMuteUser(userToUnMute, event.getMember())
.thenCompose(unused -> interactionService.replyEmbed(UN_MUTE_RESPONSE, event)) .thenCompose(unused -> interactionService.replyEmbed(UN_MUTE_RESPONSE, event))
.thenApply(interactionHook -> CommandResult.fromSuccess()); .thenApply(interactionHook -> CommandResult.fromSuccess());
} }

View File

@@ -4,10 +4,10 @@ import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand
import dev.sheldan.abstracto.core.command.config.CommandConfiguration; import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo; import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter; import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.config.SlashCommandConfig; import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
import dev.sheldan.abstracto.core.command.execution.CommandContext; import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult; import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.command.slash.parameter.SlashCommandParameterService; import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService;
import dev.sheldan.abstracto.core.config.FeatureDefinition; import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.exception.EntityGuildMismatchException; import dev.sheldan.abstracto.core.exception.EntityGuildMismatchException;
import dev.sheldan.abstracto.core.interaction.InteractionService; import dev.sheldan.abstracto.core.interaction.InteractionService;

View File

@@ -4,10 +4,10 @@ import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand
import dev.sheldan.abstracto.core.command.config.CommandConfiguration; import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo; import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter; import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.config.SlashCommandConfig; import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
import dev.sheldan.abstracto.core.command.execution.CommandContext; import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult; import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.command.slash.parameter.SlashCommandParameterService; import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService;
import dev.sheldan.abstracto.core.config.FeatureDefinition; import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.exception.EntityGuildMismatchException; import dev.sheldan.abstracto.core.exception.EntityGuildMismatchException;
import dev.sheldan.abstracto.core.interaction.InteractionService; import dev.sheldan.abstracto.core.interaction.InteractionService;

View File

@@ -5,7 +5,8 @@ import dev.sheldan.abstracto.core.command.condition.CommandCondition;
import dev.sheldan.abstracto.core.command.config.*; import dev.sheldan.abstracto.core.command.config.*;
import dev.sheldan.abstracto.core.command.execution.CommandContext; import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult; import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.command.slash.parameter.SlashCommandParameterService; import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService;
import dev.sheldan.abstracto.core.config.FeatureDefinition; import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.exception.EntityGuildMismatchException; import dev.sheldan.abstracto.core.exception.EntityGuildMismatchException;
import dev.sheldan.abstracto.core.interaction.InteractionService; import dev.sheldan.abstracto.core.interaction.InteractionService;

View File

@@ -4,10 +4,10 @@ import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand
import dev.sheldan.abstracto.core.command.config.CommandConfiguration; import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo; import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter; import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.config.SlashCommandConfig; import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
import dev.sheldan.abstracto.core.command.execution.CommandContext; import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult; import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.command.slash.parameter.SlashCommandParameterService; import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService;
import dev.sheldan.abstracto.core.config.FeatureDefinition; import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.exception.EntityGuildMismatchException; import dev.sheldan.abstracto.core.exception.EntityGuildMismatchException;
import dev.sheldan.abstracto.core.interaction.InteractionService; import dev.sheldan.abstracto.core.interaction.InteractionService;

View File

@@ -21,4 +21,9 @@ public class ModerationListenerConfig {
return executorService.setupExecutorFor("warningCreatedListener"); return executorService.setupExecutorFor("warningCreatedListener");
} }
@Bean(name = "reportMessageCreatedExecutor")
public TaskExecutor reportMessageCreatedExecutor() {
return executorService.setupExecutorFor("reportMessageCreatedListener");
}
} }

View File

@@ -5,7 +5,6 @@ import dev.sheldan.abstracto.core.models.ServerSpecificId;
import dev.sheldan.abstracto.core.models.database.AUserInAServer; import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.models.template.display.MemberDisplay; import dev.sheldan.abstracto.core.models.template.display.MemberDisplay;
import dev.sheldan.abstracto.core.service.MemberService; import dev.sheldan.abstracto.core.service.MemberService;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
import dev.sheldan.abstracto.core.utils.FutureUtils; import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.abstracto.moderation.model.database.Mute; import dev.sheldan.abstracto.moderation.model.database.Mute;
import dev.sheldan.abstracto.moderation.model.template.command.MuteEntry; import dev.sheldan.abstracto.moderation.model.template.command.MuteEntry;
@@ -25,9 +24,6 @@ public class MuteEntryConverter {
@Autowired @Autowired
private MemberService memberService; private MemberService memberService;
@Autowired
private UserInServerManagementService userInServerManagementService;
@Autowired @Autowired
private MuteManagementService muteManagementService; private MuteManagementService muteManagementService;

View File

@@ -5,14 +5,18 @@ import dev.sheldan.abstracto.core.listener.DefaultListenerResult;
import dev.sheldan.abstracto.core.listener.async.jda.AsyncJoinListener; import dev.sheldan.abstracto.core.listener.async.jda.AsyncJoinListener;
import dev.sheldan.abstracto.core.models.database.AUserInAServer; import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.models.listener.MemberJoinModel; import dev.sheldan.abstracto.core.models.listener.MemberJoinModel;
import dev.sheldan.abstracto.core.service.MemberService;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService; import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition; import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition;
import dev.sheldan.abstracto.moderation.model.database.Mute;
import dev.sheldan.abstracto.moderation.service.MuteService; import dev.sheldan.abstracto.moderation.service.MuteService;
import dev.sheldan.abstracto.moderation.service.management.MuteManagementService; import dev.sheldan.abstracto.moderation.service.management.MuteManagementService;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.Optional;
@Component @Component
@Slf4j @Slf4j
public class JoinMuteListener implements AsyncJoinListener { public class JoinMuteListener implements AsyncJoinListener {
@@ -21,17 +25,15 @@ public class JoinMuteListener implements AsyncJoinListener {
private MuteManagementService muteManagementService; private MuteManagementService muteManagementService;
@Autowired @Autowired
private MuteService muteService; private MemberService memberService;
@Autowired
private UserInServerManagementService userInServerManagementService;
@Override @Override
public DefaultListenerResult execute(MemberJoinModel model) { public DefaultListenerResult execute(MemberJoinModel model) {
AUserInAServer aUserInAServer = userInServerManagementService.loadOrCreateUser(model.getServerId(), model.getJoiningUser().getUserId()); Optional<Mute> optionalMute = muteManagementService.getAMuteOfOptional(model.getMember());
if(muteManagementService.hasActiveMute(aUserInAServer)) { if(optionalMute.isPresent()) {
log.info("Re-muting user {} which joined the server {}, because the mute has not ended yet.", model.getJoiningUser().getUserId(), model.getServerId()); log.info("Re-muting user {} which joined the server {}, because the mute has not ended yet.", model.getJoiningUser().getUserId(), model.getServerId());
muteService.applyMuteRole(aUserInAServer); Mute mute = optionalMute.get();
memberService.timeoutUser(model.getMember(), mute.getMuteTargetDate());
} }
return DefaultListenerResult.PROCESSED; return DefaultListenerResult.PROCESSED;
} }

View File

@@ -0,0 +1,138 @@
package dev.sheldan.abstracto.moderation.listener;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.listener.DefaultListenerResult;
import dev.sheldan.abstracto.core.listener.async.jda.AsyncMemberTimeoutUpdatedListener;
import dev.sheldan.abstracto.core.models.ServerUser;
import dev.sheldan.abstracto.core.models.listener.MemberTimeoutUpdatedModel;
import dev.sheldan.abstracto.core.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.moderation.config.feature.ModerationFeatureDefinition;
import dev.sheldan.abstracto.moderation.config.posttarget.MutingPostTarget;
import dev.sheldan.abstracto.moderation.model.template.command.MuteListenerModel;
import dev.sheldan.abstracto.moderation.service.MuteServiceBean;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.audit.ActionType;
import net.dv8tion.jda.api.audit.AuditLogEntry;
import net.dv8tion.jda.api.audit.AuditLogKey;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
@Component
@Slf4j
public class MemberTimeoutListener implements AsyncMemberTimeoutUpdatedListener {
@Autowired
private TemplateService templateService;
@Autowired
private PostTargetService postTargetService;
@Autowired
private MemberTimeoutListener self;
@Autowired
private MemberService memberService;
@Override
public DefaultListenerResult execute(MemberTimeoutUpdatedModel model) {
Guild guild = model.getGuild();
guild.retrieveAuditLogs()
.type(ActionType.MEMBER_UPDATE)
.limit(10)
.queue(auditLogEntries -> {
CompletableFuture<Void> notificationFuture = null;
if(auditLogEntries.isEmpty()) {
log.info("Did not find recent timeouts in guild {}.", model.getServerId());
notificationFuture = self.sendMutingUpdateNotification(model, null);
} else {
Optional<AuditLogEntry> timeoutEntryOptional = auditLogEntries
.stream()
.filter(auditLogEntry -> auditLogEntry.getChangeByKey(AuditLogKey.MEMBER_TIME_OUT) != null
&& auditLogEntry.getTargetIdLong() == model.getTimeoutUser().getUserId())
.findFirst();
if(timeoutEntryOptional.isPresent()) {
AuditLogEntry auditLogEntry = timeoutEntryOptional.get();
User responsibleUser = auditLogEntry.getUser();
if(guild.getSelfMember().getIdLong() != responsibleUser.getIdLong()) {
notificationFuture = self.sendMutingUpdateNotification(model, auditLogEntry);
}
} else {
notificationFuture = self.sendMutingUpdateNotification(model, null);
}
}
if(notificationFuture != null) {
notificationFuture.thenAccept(unused -> {
log.info("Sent notification about timeout change {} -> {} of user {} in server {}.",
model.getOldTimeout(), model.getNewTimeout(), model.getTimeoutUser().getUserId(), model.getServerId());
}).exceptionally(throwable -> {
log.info("Sent notification about timeout change {} -> {} of user {} in server {}.",
model.getOldTimeout(), model.getNewTimeout(), model.getTimeoutUser().getUserId(), model.getServerId());
return null;
});
}
});
return DefaultListenerResult.PROCESSED;
}
@Transactional
public CompletableFuture<Void> sendMutingUpdateNotification(MemberTimeoutUpdatedModel model, AuditLogEntry logEntry) {
User responsibleUser;
Guild guild = model.getGuild();
CompletableFuture<Member> future;
String reason;
if(logEntry != null) {
responsibleUser = logEntry.getUser();
if(responsibleUser != null) {
ServerUser responsibleServerUser = ServerUser
.builder()
.serverId(guild.getIdLong())
.isBot(responsibleUser.isBot())
.userId(responsibleUser.getIdLong())
.build();
future = memberService.retrieveMemberInServer(responsibleServerUser);
} else {
future = CompletableFuture.completedFuture(null);
}
reason = logEntry.getReason();
} else {
future = CompletableFuture.completedFuture(null);
reason = null;
}
CompletableFuture<Void> returningFuture = new CompletableFuture<>();
future.whenComplete((aVoid, throwable) -> {
try {
MuteListenerModel muteLogModel = MuteListenerModel
.builder()
.muteTargetDate(model.getNewTimeout() != null ? model.getNewTimeout().toInstant() : null)
.oldMuteTargetDate(model.getOldTimeout() != null ? model.getOldTimeout().toInstant() : null)
.mutingUser(future.isCompletedExceptionally() ? null : future.join())
.mutedUser(model.getMember())
.reason(reason)
.build();
MessageToSend message = templateService.renderEmbedTemplate(MuteServiceBean.MUTE_LOG_TEMPLATE, muteLogModel, guild.getIdLong());
FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(message, MutingPostTarget.MUTE_LOG, model.getServerId()));
returningFuture.complete(null);
} catch (Exception exception) {
log.error("Failed to log timeout update event for user {} in guild {}.", model.getTimeoutUser().getUserId(), model.getServerId(), exception);
}
});
return returningFuture;
}
@Override
public FeatureDefinition getFeature() {
return ModerationFeatureDefinition.MUTING;
}
}

View File

@@ -48,9 +48,14 @@ public class ReactionReportListener implements AsyncReactionAddedListener {
memberService.retrieveMemberInServer(model.getUserReacting()) memberService.retrieveMemberInServer(model.getUserReacting())
.thenCompose(member -> reactionService.removeReactionFromMessage(model.getReaction(), cachedMessage, member.getUser())) .thenCompose(member -> reactionService.removeReactionFromMessage(model.getReaction(), cachedMessage, member.getUser()))
.thenAccept(unused -> log.info("Removed report reaction on message {} in server {} in channel {}.", cachedMessage.getMessageId(), serverId, cachedMessage.getChannelId())); .thenAccept(unused -> log.info("Removed report reaction on message {} in server {} in channel {}.", cachedMessage.getMessageId(), serverId, cachedMessage.getChannelId()));
log.info("User {} in server {} reacted to report a message {} from channel {}.",
model.getUserReacting().getUserId(), model.getServerId(), cachedMessage.getMessageId(), cachedMessage.getChannelId()); if(!reactionReportService.allowedToReport(model.getUserReacting())) {
reactionReportService.createReactionReport(cachedMessage, model.getUserReacting()).exceptionally(throwable -> { log.info("User {} was reported on message {} in server {} within the cooldown. Ignoring.",
cachedMessage.getAuthor().getAuthorId(), cachedMessage.getMessageId(), cachedMessage.getServerId());
return DefaultListenerResult.IGNORED;
}
reactionReportService.createReactionReport(cachedMessage, model.getUserReacting(), null).exceptionally(throwable -> {
log.error("Failed to create reaction report in server {} on message {} in channel {}.", serverId, cachedMessage.getMessageId(), cachedMessage.getChannelId(), throwable); log.error("Failed to create reaction report in server {} on message {} in channel {}.", serverId, cachedMessage.getMessageId(), cachedMessage.getChannelId(), throwable);
return null; return null;
}); });

View File

@@ -4,7 +4,6 @@ import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.listener.DefaultListenerResult; import dev.sheldan.abstracto.core.listener.DefaultListenerResult;
import dev.sheldan.abstracto.core.listener.async.jda.AsyncUserBannedListener; import dev.sheldan.abstracto.core.listener.async.jda.AsyncUserBannedListener;
import dev.sheldan.abstracto.core.models.listener.UserBannedModel; import dev.sheldan.abstracto.core.models.listener.UserBannedModel;
import dev.sheldan.abstracto.core.service.FeatureModeService;
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;
@@ -27,9 +26,6 @@ import java.util.concurrent.CompletableFuture;
@Slf4j @Slf4j
public class UserBannedListener implements AsyncUserBannedListener { public class UserBannedListener implements AsyncUserBannedListener {
@Autowired
private FeatureModeService featureModeService;
@Autowired @Autowired
private TemplateService templateService; private TemplateService templateService;

View File

@@ -0,0 +1,117 @@
package dev.sheldan.abstracto.moderation.listener.infraction;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.config.ListenerPriority;
import dev.sheldan.abstracto.core.listener.DefaultListenerResult;
import dev.sheldan.abstracto.core.models.ServerUser;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.service.MemberService;
import dev.sheldan.abstracto.core.service.MessageService;
import dev.sheldan.abstracto.core.service.UserService;
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition;
import dev.sheldan.abstracto.moderation.listener.InfractionUpdatedDescriptionListener;
import dev.sheldan.abstracto.moderation.model.database.Infraction;
import dev.sheldan.abstracto.moderation.model.database.InfractionParameter;
import dev.sheldan.abstracto.moderation.model.listener.InfractionDescriptionEventModel;
import dev.sheldan.abstracto.moderation.model.template.command.BanLog;
import dev.sheldan.abstracto.moderation.service.BanService;
import dev.sheldan.abstracto.moderation.service.BanServiceBean;
import dev.sheldan.abstracto.moderation.service.management.InfractionManagementService;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.GuildMessageChannel;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.concurrent.CompletableFuture;
@Component
@Slf4j
public class BanReasonUpdatedListener implements InfractionUpdatedDescriptionListener {
@Autowired
private InfractionManagementService infractionManagementService;
@Autowired
private MemberService memberService;
@Autowired
private UserService userService;
@Autowired
private BanServiceBean banServiceBean;
@Autowired
private BanReasonUpdatedListener self;
@Autowired
private MessageService messageService;
@Autowired
private ChannelService channelService;
@Override
public CompletableFuture<DefaultListenerResult> execute(InfractionDescriptionEventModel model) {
Infraction infraction = infractionManagementService.loadInfraction(model.getInfractionId());
CompletableFuture<User> infractionUser = userService.retrieveUserForId(infraction.getUser().getUserReference().getId());
CompletableFuture<Member> creatorUser = memberService.retrieveMemberInServer(ServerUser.fromAUserInAServer(infraction.getInfractionCreator()));
CompletableFuture<DefaultListenerResult> returningFuture = new CompletableFuture<>();
Long infractionId = infraction.getId();
CompletableFuture.allOf(infractionUser, creatorUser)
.whenComplete((unused, throwable) -> {
if(throwable != null) {
log.warn("Failed to load members for infraction update of ban {} in server {}.", infractionId, model.getServerId(), throwable);
}
self.handleBanUpdate(model, infractionUser, creatorUser, returningFuture);
});
return returningFuture;
}
@Transactional
public void handleBanUpdate(InfractionDescriptionEventModel model, CompletableFuture<User> infractionUser, CompletableFuture<Member> infractionCreator, CompletableFuture<DefaultListenerResult> returningFuture) {
Infraction infraction = infractionManagementService.loadInfraction(model.getInfractionId());
GuildMessageChannel messageChannel = channelService.getMessageChannelFromServer(model.getServerId(), infraction.getLogChannel().getId());
Integer deletionDays = infraction
.getParameters()
.stream()
.filter(infractionParameter -> infractionParameter.getInfractionParameterId().getName().equals(BanService.INFRACTION_PARAMETER_DELETION_DAYS_KEY))
.findAny()
.map(InfractionParameter::getValue)
.map(Integer::parseInt)
.orElse(0);
BanLog banLog = BanLog
.builder()
.bannedUser(infractionUser.isCompletedExceptionally() ? null : infractionUser.join())
.banningMember(infractionCreator.isCompletedExceptionally() ? null : infractionCreator.join())
.deletionDays(deletionDays)
.reason(model.getNewDescription())
.build();
MessageToSend message = banServiceBean.renderBanMessage(banLog, model.getServerId());
messageService.editMessageInChannel(messageChannel, message, infraction.getLogMessageId())
.thenAccept(unused1 -> returningFuture.complete(DefaultListenerResult.PROCESSED))
.exceptionally(throwable1 -> {
returningFuture.complete(DefaultListenerResult.PROCESSED);
return null;
});
}
@Override
public Boolean handlesEvent(InfractionDescriptionEventModel model) {
return model.getType().equals(BanService.BAN_INFRACTION_TYPE);
}
@Override
public FeatureDefinition getFeature() {
return ModerationFeatureDefinition.MODERATION;
}
@Override
public Integer getPriority() {
return ListenerPriority.MEDIUM;
}
}

View File

@@ -0,0 +1,116 @@
package dev.sheldan.abstracto.moderation.listener.infraction;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.config.ListenerPriority;
import dev.sheldan.abstracto.core.listener.DefaultListenerResult;
import dev.sheldan.abstracto.core.models.ServerUser;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.service.GuildService;
import dev.sheldan.abstracto.core.service.MemberService;
import dev.sheldan.abstracto.core.service.MessageService;
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition;
import dev.sheldan.abstracto.moderation.listener.InfractionUpdatedDescriptionListener;
import dev.sheldan.abstracto.moderation.model.database.Infraction;
import dev.sheldan.abstracto.moderation.model.database.Warning;
import dev.sheldan.abstracto.moderation.model.listener.InfractionDescriptionEventModel;
import dev.sheldan.abstracto.moderation.model.template.command.WarnContext;
import dev.sheldan.abstracto.moderation.service.WarnServiceBean;
import dev.sheldan.abstracto.moderation.service.management.InfractionManagementService;
import dev.sheldan.abstracto.moderation.service.management.WarnManagementService;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.GuildMessageChannel;
import net.dv8tion.jda.api.entities.Member;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
@Component
@Slf4j
public class WarnReasonUpdatedListener implements InfractionUpdatedDescriptionListener {
@Autowired
private WarnManagementService warnManagementService;
@Autowired
private WarnServiceBean warnService;
@Autowired
private InfractionManagementService infractionManagementService;
@Autowired
private MemberService memberService;
@Autowired
private MessageService messageService;
@Autowired
private GuildService guildService;
@Autowired
private ChannelService channelService;
@Autowired
private WarnReasonUpdatedListener self;
@Override
public CompletableFuture<DefaultListenerResult> execute(InfractionDescriptionEventModel model) {
Optional<Warning> potentialWarning = warnManagementService.findWarnByInfraction(model.getInfractionId());
if(potentialWarning.isPresent()) {
Warning warning = potentialWarning.get();
Long warnId = warning.getWarnId().getId();
CompletableFuture<Member> warnedUser = memberService.retrieveMemberInServer(ServerUser.fromAUserInAServer(warning.getWarnedUser()));
CompletableFuture<Member> warningUser = memberService.retrieveMemberInServer(ServerUser.fromAUserInAServer(warning.getWarningUser()));
CompletableFuture<DefaultListenerResult> returningFuture = new CompletableFuture<>();
CompletableFuture.allOf(warnedUser, warningUser)
.whenComplete((unused, throwable) -> {
if(throwable != null) {
log.warn("Failed to load members for infraction update of warning {} in server {}.", warnId, model.getServerId(), throwable);
}
self.handleWarnUpdate(model, warnId, warnedUser, warningUser, returningFuture);
});
return returningFuture;
} else {
return CompletableFuture.completedFuture(DefaultListenerResult.IGNORED);
}
}
private void handleWarnUpdate(InfractionDescriptionEventModel model, Long warnId, CompletableFuture<Member> warnedUser, CompletableFuture<Member> warningUser, CompletableFuture<DefaultListenerResult> returningFuture) {
Guild guild = guildService.getGuildById(model.getServerId());
Infraction infraction = infractionManagementService.loadInfraction(model.getInfractionId());
GuildMessageChannel messageChannel = channelService.getMessageChannelFromServer(model.getServerId(), infraction.getLogChannel().getId());
WarnContext context = WarnContext
.builder()
.warnedMember(warnedUser.isCompletedExceptionally() ? null : warnedUser.join())
.member(warningUser.isCompletedExceptionally() ? null : warningUser.join())
.reason(model.getNewDescription())
.warnId(warnId)
.guild(guild)
.build();
MessageToSend message = warnService.renderMessageModel(context);
messageService.editMessageInChannel(messageChannel, message, infraction.getLogMessageId())
.thenAccept(unused1 -> returningFuture.complete(DefaultListenerResult.PROCESSED))
.exceptionally(throwable1 -> {
returningFuture.complete(DefaultListenerResult.PROCESSED);
return null;
});
}
@Override
public FeatureDefinition getFeature() {
return ModerationFeatureDefinition.WARNING;
}
@Override
public Boolean handlesEvent(InfractionDescriptionEventModel model) {
return model.getType().equals(WarnServiceBean.WARN_INFRACTION_TYPE);
}
@Override
public Integer getPriority() {
return ListenerPriority.MEDIUM;
}
}

View File

@@ -0,0 +1,86 @@
package dev.sheldan.abstracto.moderation.listener.interaction;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.interaction.InteractionService;
import dev.sheldan.abstracto.core.interaction.MessageContextConfig;
import dev.sheldan.abstracto.core.listener.DefaultListenerResult;
import dev.sheldan.abstracto.core.interaction.context.message.listener.MessageContextCommandListener;
import dev.sheldan.abstracto.core.models.ServerUser;
import dev.sheldan.abstracto.core.models.listener.interaction.MessageContextInteractionModel;
import dev.sheldan.abstracto.core.interaction.context.ContextCommandService;
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition;
import dev.sheldan.abstracto.moderation.service.ReactionReportService;
import dev.sheldan.abstracto.moderation.service.ReactionReportServiceBean;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Message;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import static dev.sheldan.abstracto.moderation.service.ReactionReportServiceBean.REACTION_REPORT_RESPONSE_TEMPLATE;
@Component
@Slf4j
public class ReportContextCommandListener implements MessageContextCommandListener {
@Autowired
private ContextCommandService contextCommandService;
@Autowired
private ReactionReportService reactionReportService;
@Autowired
private InteractionService interactionService;
@Override
public DefaultListenerResult execute(MessageContextInteractionModel model) {
Message targetMessage = model.getEvent().getTarget();
if(targetMessage.getAuthor().getIdLong() == model.getEvent().getUser().getIdLong()) {
interactionService.replyEmbed(ReactionReportServiceBean.REACTION_REPORT_OWN_MESSAGE_RESPONSE_TEMPLATE, new Object(), model.getEvent());
return DefaultListenerResult.IGNORED;
}
ServerUser userReporting = ServerUser
.builder()
.serverId(model.getServerId())
.userId(model.getEvent().getUser().getIdLong())
.isBot(model.getEvent().getUser().isBot())
.build();
if(!reactionReportService.allowedToReport(userReporting)) {
log.info("User {} was reported on message {} in server {} within the cooldown. Ignoring.",
targetMessage.getAuthor().getIdLong(), targetMessage.getIdLong(), targetMessage.getGuild().getIdLong());
interactionService.replyEmbed(ReactionReportServiceBean.REACTION_REPORT_COOLDOWN_RESPONSE_TEMPLATE, new Object(), model.getEvent());
return DefaultListenerResult.IGNORED;
}
reactionReportService.createReactionReport(targetMessage, userReporting, null).exceptionally(throwable -> {
log.error("Failed to create reaction report in server {} on message {} in channel {} with interaction.",
model.getServerId(), targetMessage.getIdLong(), model.getEvent().getChannel().getIdLong(), throwable);
return null;
});
interactionService.replyEmbed(REACTION_REPORT_RESPONSE_TEMPLATE, new Object(), model.getEvent());
return DefaultListenerResult.PROCESSED;
}
@Override
public MessageContextConfig getConfig() {
return MessageContextConfig
.builder()
.isTemplated(true)
.name("report_message")
.templateKey("report_message_context_menu_label")
.build();
}
@Override
public Boolean handlesEvent(MessageContextInteractionModel model) {
return contextCommandService.matchesGuildContextName(model, getConfig(), model.getServerId());
}
@Override
public FeatureDefinition getFeature() {
return ModerationFeatureDefinition.REPORT_REACTIONS;
}
}

View File

@@ -0,0 +1,89 @@
package dev.sheldan.abstracto.moderation.listener.interaction;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.config.ListenerPriority;
import dev.sheldan.abstracto.core.interaction.ComponentPayloadManagementService;
import dev.sheldan.abstracto.core.interaction.InteractionService;
import dev.sheldan.abstracto.core.interaction.modal.listener.ModalInteractionListener;
import dev.sheldan.abstracto.core.interaction.modal.listener.ModalInteractionListenerModel;
import dev.sheldan.abstracto.core.interaction.modal.listener.ModalInteractionListenerResult;
import dev.sheldan.abstracto.core.models.ServerUser;
import dev.sheldan.abstracto.core.service.MessageCache;
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition;
import dev.sheldan.abstracto.moderation.model.interaction.MessageReportModalPayload;
import dev.sheldan.abstracto.moderation.service.ReactionReportService;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.interactions.modals.ModalMapping;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import static dev.sheldan.abstracto.moderation.service.ReactionReportServiceBean.REACTION_REPORT_RESPONSE_TEMPLATE;
@Component
@Slf4j
public class ReportContextModalListener implements ModalInteractionListener {
@Autowired
private MessageCache messageCache;
@Autowired
private ReactionReportService reactionReportService;
@Autowired
private ComponentPayloadManagementService componentPayloadManagementService;
@Autowired
private InteractionService interactionService;
@Override
public ModalInteractionListenerResult execute(ModalInteractionListenerModel model) {
MessageReportModalPayload payload = (MessageReportModalPayload) model.getDeserializedPayload();
String context = model
.getEvent()
.getValues()
.stream()
.filter(modalMapping -> modalMapping.getId().equals(payload.getTextInputId()))
.map(ModalMapping::getAsString)
.findFirst()
.orElse(null);
messageCache.getMessageFromCache(payload.getServerId(), payload.getChannelId(), payload.getMessageId()).thenAccept(cachedMessage -> {
ServerUser userReporting = ServerUser
.builder()
.serverId(model.getServerId())
.userId(cachedMessage.getAuthor().getAuthorId())
.isBot(cachedMessage.getAuthor().getIsBot())
.build();
reactionReportService.createReactionReport(cachedMessage, userReporting, context)
.thenAccept(unused -> {
interactionService.replyEmbed(REACTION_REPORT_RESPONSE_TEMPLATE, new Object(), model.getEvent());
log.info("Handled modal for message report with id {} in guild {} in channel {} on message {}",
model.getEvent().getModalId(), payload.getServerId(), payload.getChannelId(), payload.getMessageId());
componentPayloadManagementService.deletePayload(payload.getModalId());
}).exceptionally(throwable -> {
log.error("Failed to create reaction report in server {} on message {} in channel {} with interaction.",
model.getServerId(), cachedMessage.getMessageId(), model.getEvent().getChannel().getIdLong(), throwable);
return null;
});
}).exceptionally(throwable -> {
log.error("Failed to load reported message.", throwable);
return null;
});
return ModalInteractionListenerResult.ACKNOWLEDGED;
}
@Override
public FeatureDefinition getFeature() {
return ModerationFeatureDefinition.REPORT_REACTIONS;
}
@Override
public Integer getPriority() {
return ListenerPriority.MEDIUM;
}
@Override
public Boolean handlesEvent(ModalInteractionListenerModel model) {
return model.getDeserializedPayload() instanceof MessageReportModalPayload && model.getEvent().isFromGuild();
}
}

View File

@@ -0,0 +1,134 @@
package dev.sheldan.abstracto.moderation.listener.interaction;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.interaction.ComponentPayloadManagementService;
import dev.sheldan.abstracto.core.interaction.ComponentService;
import dev.sheldan.abstracto.core.interaction.InteractionService;
import dev.sheldan.abstracto.core.interaction.MessageContextConfig;
import dev.sheldan.abstracto.core.interaction.modal.ModalConfigPayload;
import dev.sheldan.abstracto.core.interaction.modal.ModalService;
import dev.sheldan.abstracto.core.listener.DefaultListenerResult;
import dev.sheldan.abstracto.core.interaction.context.message.listener.MessageContextCommandListener;
import dev.sheldan.abstracto.core.models.ServerUser;
import dev.sheldan.abstracto.core.models.listener.interaction.MessageContextInteractionModel;
import dev.sheldan.abstracto.core.interaction.context.ContextCommandService;
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition;
import dev.sheldan.abstracto.moderation.model.interaction.MessageReportModalPayload;
import dev.sheldan.abstracto.moderation.model.template.listener.ReportInputModalModel;
import dev.sheldan.abstracto.moderation.service.ReactionReportService;
import dev.sheldan.abstracto.moderation.service.ReactionReportServiceBean;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Message;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
@Component
@Slf4j
public class ReportWithContextContextCommandListener implements MessageContextCommandListener {
@Autowired
private ContextCommandService contextCommandService;
@Autowired
private ModalService modalService;
@Autowired
private ComponentService componentService;
@Autowired
private ComponentPayloadManagementService componentPayloadManagementService;
@Autowired
private ReportWithContextContextCommandListener self;
@Autowired
private ReactionReportService reactionReportService;
@Autowired
private InteractionService interactionService;
private static final String REACTION_REPORT_MODAL_TEMPLATE = "reactionReport_input";
@Override
public DefaultListenerResult execute(MessageContextInteractionModel model) {
Message targetMessage = model.getEvent().getTarget();
if(targetMessage.getAuthor().getIdLong() == model.getEvent().getUser().getIdLong()) {
interactionService.replyEmbed(ReactionReportServiceBean.REACTION_REPORT_OWN_MESSAGE_RESPONSE_TEMPLATE, new Object(), model.getEvent());
return DefaultListenerResult.IGNORED;
}
ServerUser userReporting = ServerUser
.builder()
.serverId(model.getServerId())
.userId(model.getEvent().getUser().getIdLong())
.isBot(model.getEvent().getUser().isBot())
.build();
if(!reactionReportService.allowedToReport(userReporting)) {
log.info("User {} was reported on message {} in server {} within the cooldown. Ignoring.",
targetMessage.getAuthor().getIdLong(), targetMessage.getIdLong(), targetMessage.getGuild().getIdLong());
interactionService.replyEmbed(ReactionReportServiceBean.REACTION_REPORT_COOLDOWN_RESPONSE_TEMPLATE, new Object(), model.getEvent());
return DefaultListenerResult.IGNORED;
}
String modalId = componentService.generateComponentId();
String textInputId = componentService.generateComponentId();
ReportInputModalModel modalModel = ReportInputModalModel
.builder()
.modalId(modalId)
.inputComponentId(textInputId)
.build();
modalService.replyModal(model.getEvent(), REACTION_REPORT_MODAL_TEMPLATE, modalModel)
.thenAccept(unused -> {
log.info("Created modal for report on message {} from user {} in server {}.",
targetMessage.getIdLong(), targetMessage.getAuthor().getIdLong(), targetMessage.getGuild().getIdLong());
self.persistModalPayload(targetMessage, modalId, textInputId);
}).exceptionally(throwable -> {
log.error("Failed to create modal for report on message {} from user {} in server {}.",
targetMessage.getIdLong(), targetMessage.getAuthor().getIdLong(), targetMessage.getGuild().getIdLong());
return null;
});
return DefaultListenerResult.PROCESSED;
}
@Transactional
public void persistModalPayload(Message message, String modalId, String inputId) {
MessageReportModalPayload payload = MessageReportModalPayload
.builder()
.channelId(message.getChannel().getIdLong())
.messageId(message.getIdLong())
.serverId(message.getGuild().getIdLong())
.modalId(modalId)
.textInputId(inputId)
.build();
ModalConfigPayload payloadConfig = ModalConfigPayload
.builder()
.modalPayload(payload)
.origin(ReactionReportServiceBean.REACTION_REPORT_MODAL_ORIGIN)
.payloadType(payload.getClass())
.modalId(modalId)
.build();
componentPayloadManagementService.createModalPayload(payloadConfig, message.getGuild().getIdLong());
}
@Override
public MessageContextConfig getConfig() {
return MessageContextConfig
.builder()
.isTemplated(true)
.name("report_message_context")
.templateKey("report_message_with_context_context_menu_label")
.build();
}
@Override
public Boolean handlesEvent(MessageContextInteractionModel model) {
return contextCommandService.matchesGuildContextName(model, getConfig(), model.getServerId());
}
@Override
public FeatureDefinition getFeature() {
return ModerationFeatureDefinition.REPORT_REACTIONS;
}
}

View File

@@ -0,0 +1,46 @@
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.ServerUser;
import dev.sheldan.abstracto.core.models.cache.CachedMessage;
import dev.sheldan.abstracto.moderation.listener.ReportMessageCreatedListener;
import dev.sheldan.abstracto.moderation.model.listener.ReportMessageCreatedModel;
import net.dv8tion.jda.api.entities.Message;
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
public class ReportMessageCreatedListenerManager {
@Autowired(required = false)
private List<ReportMessageCreatedListener> listeners;
@Autowired
private ListenerService listenerService;
@Autowired
@Qualifier("reportMessageCreatedExecutor")
private TaskExecutor reportMessageCreatedExecutor;
public void sendReportMessageCreatedEvent(CachedMessage cachedMessage, Message message, ServerUser reporter) {
if(listeners == null || listeners.isEmpty()) {
return;
}
ReportMessageCreatedModel model = createEventModel(cachedMessage, message, reporter);
listeners.forEach(listener -> listenerService.executeFeatureAwareListener(listener, model, reportMessageCreatedExecutor));
}
private ReportMessageCreatedModel createEventModel(CachedMessage reportedMessage, Message message, ServerUser reporter) {
return ReportMessageCreatedModel
.builder()
.reportedMessage(ServerChannelMessage.fromCachedMessage(reportedMessage))
.reportMessage(ServerChannelMessage.fromMessage(message))
.reporter(reporter)
.build();
}
}

View File

@@ -0,0 +1,10 @@
package dev.sheldan.abstracto.moderation.repository;
import dev.sheldan.abstracto.moderation.model.database.InfractionParameter;
import dev.sheldan.abstracto.moderation.model.database.embedded.InfractionParameterId;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface InfractionParameterRepository extends JpaRepository<InfractionParameter, InfractionParameterId> {
}

View File

@@ -1,5 +1,6 @@
package dev.sheldan.abstracto.moderation.repository; package dev.sheldan.abstracto.moderation.repository;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.AUserInAServer; import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.moderation.model.database.Infraction; import dev.sheldan.abstracto.moderation.model.database.Infraction;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
@@ -10,4 +11,6 @@ import java.util.List;
@Repository @Repository
public interface InfractionRepository extends JpaRepository<Infraction, Long> { public interface InfractionRepository extends JpaRepository<Infraction, Long> {
List<Infraction> findByUserAndDecayedFalse(AUserInAServer aUserInAServer); List<Infraction> findByUserAndDecayedFalse(AUserInAServer aUserInAServer);
List<Infraction> findByUserOrderByCreated(AUserInAServer aUserInAServer);
List<Infraction> findByServerOrderByCreated(AServer server);
} }

View File

@@ -15,7 +15,7 @@ import java.util.Optional;
public interface MuteRepository extends JpaRepository<Mute, ServerSpecificId> { public interface MuteRepository extends JpaRepository<Mute, ServerSpecificId> {
boolean existsByMutedUserAndMuteEndedFalse(AUserInAServer userInAServer); boolean existsByMutedUserAndMuteEndedFalse(AUserInAServer userInAServer);
Mute findTopByMutedUserAndMuteEndedFalse(AUserInAServer userInAServer); Optional<Mute> findTopByMutedUserAndMuteEndedFalse(AUserInAServer userInAServer);
List<Mute> findAllByMutedUserAndMuteEndedFalseOrderByMuteId_IdDesc(AUserInAServer aUserInAServer); List<Mute> findAllByMutedUserAndMuteEndedFalseOrderByMuteId_IdDesc(AUserInAServer aUserInAServer);

View File

@@ -1,18 +0,0 @@
package dev.sheldan.abstracto.moderation.repository;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.moderation.model.database.MuteRole;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface MuteRoleRepository extends JpaRepository<MuteRole, Long> {
MuteRole findByRoleServer(AServer server);
List<MuteRole> findAllByRoleServer(AServer server);
boolean existsByRoleServer(AServer server);
}

View File

@@ -6,10 +6,11 @@ import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import java.time.Instant; import java.time.Instant;
import java.util.List;
import java.util.Optional; import java.util.Optional;
@Repository @Repository
public interface ReactionReportRepository extends JpaRepository<ReactionReport, Long> { public interface ReactionReportRepository extends JpaRepository<ReactionReport, Long> {
Optional<ReactionReport> findByReportedUserAndCreatedLessThan(AUserInAServer aUserInAServer, Instant maxCreated); List<ReactionReport> findByReportedUserAndCreatedLessThan(AUserInAServer aUserInAServer, Instant maxCreated);
} }

View File

@@ -4,7 +4,6 @@ import dev.sheldan.abstracto.core.models.ServerSpecificId;
import dev.sheldan.abstracto.core.models.database.AServer; import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.AUserInAServer; import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.moderation.model.database.Warning; import dev.sheldan.abstracto.moderation.model.database.Warning;
import org.jetbrains.annotations.NotNull;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
@@ -27,6 +26,6 @@ public interface WarnRepository extends JpaRepository<Warning, ServerSpecificId>
List<Warning> findByWarnedUser(AUserInAServer aUserInAServer); List<Warning> findByWarnedUser(AUserInAServer aUserInAServer);
@NotNull
Optional<Warning> findByWarnId_IdAndWarnId_ServerId(Long warnId, Long serverId); Optional<Warning> findByWarnId_IdAndWarnId_ServerId(Long warnId, Long serverId);
Optional<Warning> findByInfraction_Id(Long infractionId);
} }

View File

@@ -1,11 +1,16 @@
package dev.sheldan.abstracto.moderation.service; package dev.sheldan.abstracto.moderation.service;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.service.*; import dev.sheldan.abstracto.core.service.*;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
import dev.sheldan.abstracto.core.utils.FutureUtils; import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureConfig;
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition;
import dev.sheldan.abstracto.moderation.config.posttarget.ModerationPostTarget; import dev.sheldan.abstracto.moderation.config.posttarget.ModerationPostTarget;
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.moderation.model.BanResult; import dev.sheldan.abstracto.moderation.model.BanResult;
import dev.sheldan.abstracto.moderation.model.database.Infraction;
import dev.sheldan.abstracto.moderation.model.template.command.BanLog; import dev.sheldan.abstracto.moderation.model.template.command.BanLog;
import dev.sheldan.abstracto.moderation.model.template.command.BanNotificationModel; import dev.sheldan.abstracto.moderation.model.template.command.BanNotificationModel;
import dev.sheldan.abstracto.moderation.model.template.command.UnBanLog; import dev.sheldan.abstracto.moderation.model.template.command.UnBanLog;
@@ -13,8 +18,12 @@ import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.*; import net.dv8tion.jda.api.entities.*;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.time.Duration; import java.time.Duration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
@Component @Component
@@ -37,6 +46,18 @@ public class BanServiceBean implements BanService {
@Autowired @Autowired
private MessageService messageService; private MessageService messageService;
@Autowired
private FeatureFlagService featureFlagService;
@Autowired
private ConfigService configService;
@Autowired
private UserInServerManagementService userInServerManagementService;
@Autowired
private InfractionService infractionService;
@Override @Override
public CompletableFuture<BanResult> banUserWithNotification(User user, String reason, Member banningMember, Integer deletionDays) { public CompletableFuture<BanResult> banUserWithNotification(User user, String reason, Member banningMember, Integer deletionDays) {
BanLog banLog = BanLog BanLog banLog = BanLog
@@ -47,18 +68,31 @@ public class BanServiceBean implements BanService {
.reason(reason) .reason(reason)
.build(); .build();
Guild guild = banningMember.getGuild(); Guild guild = banningMember.getGuild();
CompletableFuture<BanResult> returningFuture = new CompletableFuture<>(); BanResult[] result = {BanResult.SUCCESSFUL};
sendBanNotification(user, reason, guild) return sendBanNotification(user, reason, guild)
.whenComplete((unused, throwable) -> banUser(guild, user, deletionDays, reason) .exceptionally(throwable -> {
.thenCompose(unused1 -> sendBanLogMessage(banLog, guild.getIdLong())) result[0] = BanResult.NOTIFICATION_FAILED;
.thenAccept(unused1 -> { return null;
if(throwable != null) { })
returningFuture.complete(BanResult.NOTIFICATION_FAILED); .thenCompose(unused -> banUser(guild, user, deletionDays, reason))
} else { .thenCompose(unused -> sendBanLogMessage(banLog, guild.getIdLong()))
returningFuture.complete(BanResult.SUCCESSFUL); .thenAccept(banLogMessage -> self.evaluateAndStoreInfraction(user, guild, reason, banningMember, banLogMessage, deletionDays))
} .thenApply(unused -> result[0]);
})); }
return returningFuture;
@Transactional
public CompletableFuture<Long> evaluateAndStoreInfraction(User user, Guild guild, String reason, Member banningMember, Message banLogMessage, Integer deletionDays) {
if(featureFlagService.getFeatureFlagValue(ModerationFeatureDefinition.INFRACTIONS, guild.getIdLong())) {
Long infractionPoints = configService.getLongValueOrConfigDefault(ModerationFeatureConfig.BAN_INFRACTION_POINTS, guild.getIdLong());
AUserInAServer bannedUser = userInServerManagementService.loadOrCreateUser(guild.getIdLong(), user.getIdLong());
AUserInAServer banningUser = userInServerManagementService.loadOrCreateUser(banningMember);
Map<String, String> parameters = new HashMap<>();
parameters.put(INFRACTION_PARAMETER_DELETION_DAYS_KEY, deletionDays.toString());
return infractionService.createInfractionWithNotification(bannedUser, infractionPoints, BAN_INFRACTION_TYPE, reason, banningUser, parameters, banLogMessage)
.thenApply(Infraction::getId);
} else {
return CompletableFuture.completedFuture(null);
}
} }
private CompletableFuture<Void> sendBanNotification(User user, String reason, Guild guild) { private CompletableFuture<Void> sendBanNotification(User user, String reason, Guild guild) {
@@ -102,10 +136,15 @@ public class BanServiceBean implements BanService {
.thenCompose(unused -> unbanUser(guild, user)); .thenCompose(unused -> unbanUser(guild, user));
} }
public CompletableFuture<Void> sendBanLogMessage(BanLog banLog, Long guildId) { public CompletableFuture<Message> sendBanLogMessage(BanLog banLog, Long guildId) {
MessageToSend banLogMessage = templateService.renderEmbedTemplate(BAN_LOG_TEMPLATE, banLog, guildId); MessageToSend banLogMessage = renderBanMessage(banLog, guildId);
log.debug("Sending ban log message in guild {}.", guildId); log.debug("Sending ban log message in guild {}.", guildId);
return FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(banLogMessage, ModerationPostTarget.BAN_LOG, guildId)); List<CompletableFuture<Message>> messageFutures = postTargetService.sendEmbedInPostTarget(banLogMessage, ModerationPostTarget.BAN_LOG, guildId);
return FutureUtils.toSingleFutureGeneric(messageFutures).thenApply(unused -> messageFutures.get(0).join());
}
public MessageToSend renderBanMessage(BanLog banLog, Long guildId) {
return templateService.renderEmbedTemplate(BAN_LOG_TEMPLATE, banLog, guildId);
} }
public CompletableFuture<Void> sendUnBanLogMessage(UnBanLog banLog, Long guildId, String template) { public CompletableFuture<Void> sendUnBanLogMessage(UnBanLog banLog, Long guildId, String template) {

View File

@@ -1,30 +1,36 @@
package dev.sheldan.abstracto.moderation.service; package dev.sheldan.abstracto.moderation.service;
import dev.sheldan.abstracto.core.Prioritized;
import dev.sheldan.abstracto.core.exception.EntityGuildMismatchException;
import dev.sheldan.abstracto.core.listener.ListenerService;
import dev.sheldan.abstracto.core.models.ServerUser; import dev.sheldan.abstracto.core.models.ServerUser;
import dev.sheldan.abstracto.core.models.database.AUserInAServer; import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.models.template.display.MemberDisplay; import dev.sheldan.abstracto.core.models.template.display.MemberDisplay;
import dev.sheldan.abstracto.core.service.ConfigService; 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.PostTargetService;
import dev.sheldan.abstracto.core.service.management.ChannelManagementService;
import dev.sheldan.abstracto.core.service.management.ConfigManagementService; import dev.sheldan.abstracto.core.service.management.ConfigManagementService;
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.core.utils.FutureUtils;
import dev.sheldan.abstracto.moderation.config.feature.InfractionFeatureConfig; import dev.sheldan.abstracto.moderation.config.feature.InfractionFeatureConfig;
import dev.sheldan.abstracto.moderation.config.posttarget.InfractionPostTarget; import dev.sheldan.abstracto.moderation.config.posttarget.InfractionPostTarget;
import dev.sheldan.abstracto.moderation.listener.InfractionUpdatedDescriptionListener;
import dev.sheldan.abstracto.moderation.listener.manager.InfractionLevelChangedListenerManager; import dev.sheldan.abstracto.moderation.listener.manager.InfractionLevelChangedListenerManager;
import dev.sheldan.abstracto.moderation.model.database.Infraction; import dev.sheldan.abstracto.moderation.model.database.Infraction;
import dev.sheldan.abstracto.moderation.model.listener.InfractionDescriptionEventModel;
import dev.sheldan.abstracto.moderation.model.template.InfractionLevelChangeModel; import dev.sheldan.abstracto.moderation.model.template.InfractionLevelChangeModel;
import dev.sheldan.abstracto.moderation.service.management.InfractionManagementService; import dev.sheldan.abstracto.moderation.service.management.InfractionManagementService;
import dev.sheldan.abstracto.moderation.service.management.InfractionParameterManagementService;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Message;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.util.Pair; import org.springframework.data.util.Pair;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.time.Instant; import java.time.Instant;
import java.util.ArrayList; import java.util.*;
import java.util.List;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -35,9 +41,6 @@ public class InfractionServiceBean implements InfractionService {
@Autowired @Autowired
private InfractionManagementService infractionManagementService; private InfractionManagementService infractionManagementService;
@Autowired
private FeatureFlagService featureFlagService;
@Autowired @Autowired
private ConfigService configService; private ConfigService configService;
@@ -56,6 +59,15 @@ public class InfractionServiceBean implements InfractionService {
@Autowired @Autowired
private InfractionLevelChangedListenerManager infractionLevelChangedListenerManager; private InfractionLevelChangedListenerManager infractionLevelChangedListenerManager;
@Autowired
private InfractionParameterManagementService infractionParameterManagementService;
@Autowired(required = false)
private List<InfractionUpdatedDescriptionListener> infractionDescriptionListeners;
@Autowired
private ListenerService listenerService;
private static final String INFRACTION_NOTIFICATION_TEMPLATE_KEY = "infraction_level_notification"; private static final String INFRACTION_NOTIFICATION_TEMPLATE_KEY = "infraction_level_notification";
@Override @Override
@@ -74,15 +86,31 @@ public class InfractionServiceBean implements InfractionService {
} }
@Override @Override
public CompletableFuture<Infraction> createInfractionWithNotification(AUserInAServer aUserInAServer, Long points) { public CompletableFuture<Infraction> createInfractionWithNotification(AUserInAServer target, Long points, String type, String description, AUserInAServer creator, Map<String, String> parameters, Message message) {
Infraction createdInfraction = infractionManagementService.createInfraction(aUserInAServer, points); Infraction createdInfraction = infractionManagementService.createInfraction(target, points, type, description, creator, message);
parameters.forEach((key, value) -> infractionParameterManagementService.createInfractionParameter(createdInfraction, key, value));
Long infractionId = createdInfraction.getId(); Long infractionId = createdInfraction.getId();
return createInfractionNotification(aUserInAServer, points) return createInfractionNotification(target, points, type, description)
.thenApply(aBoolean -> self.reloadInfraction(infractionId)); .thenApply(avoid -> self.reloadInfraction(infractionId));
} }
@Override @Override
public CompletableFuture<Void> createInfractionNotification(AUserInAServer aUserInAServer, Long points) { public CompletableFuture<Infraction> createInfractionWithNotification(AUserInAServer target, Long points, String type, String description, AUserInAServer creator, Map<String, String> parameters) {
return createInfractionWithNotification(target, points, type, description, creator, new HashMap<>(), null);
}
@Override
public CompletableFuture<Infraction> createInfractionWithNotification(AUserInAServer target, Long points, String type, String description, AUserInAServer creator, Message logMessage) {
return createInfractionWithNotification(target, points, type, description, creator, new HashMap<>(), logMessage);
}
@Override
public CompletableFuture<Infraction> createInfractionWithNotification(AUserInAServer target, Long points, String type, String description, AUserInAServer creator) {
return createInfractionWithNotification(target, points, type, description, creator, new HashMap<>(), null);
}
@Override
public CompletableFuture<Void> createInfractionNotification(AUserInAServer aUserInAServer, Long points, String type, String description) {
Long serverId = aUserInAServer.getServerReference().getId(); Long serverId = aUserInAServer.getServerReference().getId();
Long currentPoints = getActiveInfractionPointsForUser(aUserInAServer); Long currentPoints = getActiveInfractionPointsForUser(aUserInAServer);
Long newPoints = currentPoints + points; Long newPoints = currentPoints + points;
@@ -95,6 +123,8 @@ public class InfractionServiceBean implements InfractionService {
.member(MemberDisplay.fromAUserInAServer(aUserInAServer)) .member(MemberDisplay.fromAUserInAServer(aUserInAServer))
.newLevel(newLevel) .newLevel(newLevel)
.oldLevel(oldLevel) .oldLevel(oldLevel)
.type(type)
.description(description)
.oldPoints(currentPoints) .oldPoints(currentPoints)
.newPoints(newPoints) .newPoints(newPoints)
.build(); .build();
@@ -107,6 +137,38 @@ public class InfractionServiceBean implements InfractionService {
} }
@Override
public CompletableFuture<Void> editInfraction(Long infractionId, String newReason, Long serverId) {
Infraction infraction = infractionManagementService.loadInfraction(infractionId);
if(!infraction.getServer().getId().equals(serverId)) {
throw new EntityGuildMismatchException();
}
infraction.setDescription(newReason);
return notifyInfractionListeners(infraction);
}
private CompletableFuture<Void> notifyInfractionListeners(Infraction infraction) {
InfractionDescriptionEventModel model = getInfractionDescriptionModel(infraction);
return infractionDescriptionListeners
.stream()
.filter(listener -> listener.handlesEvent(model))
.max(Comparator.comparing(Prioritized::getPriority))
.map(listener -> listenerService.executeAsyncFeatureAwareListener(listener, model))
.orElse(CompletableFuture.completedFuture(null))
.thenApply(defaultListenerResult -> null);
}
private InfractionDescriptionEventModel getInfractionDescriptionModel(Infraction infraction) {
return InfractionDescriptionEventModel
.builder()
.infractionId(infraction.getId())
.newDescription(infraction.getDescription())
.userId(infraction.getUser().getUserReference().getId())
.serverId(infraction.getServer().getId())
.type(infraction.getType())
.build();
}
@Transactional @Transactional
public Infraction reloadInfraction(Long infractionId) { public Infraction reloadInfraction(Long infractionId) {
return infractionManagementService.loadInfraction(infractionId); return infractionManagementService.loadInfraction(infractionId);

View File

@@ -1,18 +1,27 @@
package dev.sheldan.abstracto.moderation.service; package dev.sheldan.abstracto.moderation.service;
import dev.sheldan.abstracto.core.service.FeatureModeService; import dev.sheldan.abstracto.core.models.database.AUserInAServer;
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.PostTargetService;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
import dev.sheldan.abstracto.core.utils.FutureUtils; import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureConfig;
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition;
import dev.sheldan.abstracto.moderation.config.posttarget.ModerationPostTarget; import dev.sheldan.abstracto.moderation.config.posttarget.ModerationPostTarget;
import dev.sheldan.abstracto.moderation.model.database.Infraction;
import dev.sheldan.abstracto.moderation.model.template.command.KickLogModel; import dev.sheldan.abstracto.moderation.model.template.command.KickLogModel;
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 lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
@Component @Component
@@ -27,18 +36,47 @@ public class KickServiceBean implements KickService {
@Autowired @Autowired
private PostTargetService postTargetService; private PostTargetService postTargetService;
@Autowired
private FeatureFlagService featureFlagService;
@Autowired
private ConfigService configService;
@Autowired
private UserInServerManagementService userInServerManagementService;
@Autowired
private InfractionService infractionService;
@Autowired
private KickServiceBean self;
@Override @Override
public CompletableFuture<Void> kickMember(Member member, String reason, KickLogModel kickLogModel) { public CompletableFuture<Void> kickMember(Member member, String reason, KickLogModel kickLogModel) {
Guild guild = member.getGuild(); Guild guild = member.getGuild();
log.info("Kicking user {} from guild {}", member.getUser().getIdLong(), guild.getIdLong()); log.info("Kicking user {} from guild {}", member.getUser().getIdLong(), guild.getIdLong());
CompletableFuture<Void> kickFuture = guild.kick(member, reason).submit(); CompletableFuture<Void> kickFuture = guild.kick(member, reason).submit();
CompletableFuture<Void> logFuture = this.sendKickLog(kickLogModel); CompletableFuture<Message> logFuture = this.sendKickLog(kickLogModel);
return CompletableFuture.allOf(kickFuture, logFuture); return CompletableFuture.allOf(kickFuture, logFuture)
.thenAccept(unused -> self.storeInfraction(member, reason, kickLogModel, guild, logFuture.join()));
} }
private CompletableFuture<Void> sendKickLog(KickLogModel kickLogModel) { @Transactional
public CompletableFuture<Long> storeInfraction(Member member, String reason, KickLogModel kickLogModel, Guild guild, Message logMessage) {
if(featureFlagService.getFeatureFlagValue(ModerationFeatureDefinition.INFRACTIONS, guild.getIdLong())) {
Long infractionPoints = configService.getLongValueOrConfigDefault(ModerationFeatureConfig.KICK_INFRACTION_POINTS, guild.getIdLong());
AUserInAServer kickedUser = userInServerManagementService.loadOrCreateUser(member);
AUserInAServer kickingUser = userInServerManagementService.loadOrCreateUser(kickLogModel.getMember());
return infractionService.createInfractionWithNotification(kickedUser, infractionPoints, KICK_INFRACTION_TYPE, reason, kickingUser, logMessage).thenApply(Infraction::getId);
} else {
return CompletableFuture.completedFuture(null);
}
}
private CompletableFuture<Message> sendKickLog(KickLogModel kickLogModel) {
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());
return FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(warnLogMessage, ModerationPostTarget.KICK_LOG, kickLogModel.getGuild().getIdLong())); List<CompletableFuture<Message>> messageFutures = postTargetService.sendEmbedInPostTarget(warnLogMessage, ModerationPostTarget.KICK_LOG, kickLogModel.getGuild().getIdLong());
return FutureUtils.toSingleFutureGeneric(messageFutures).thenApply(unused -> messageFutures.get(0).join());
} }
} }

View File

@@ -2,7 +2,7 @@ package dev.sheldan.abstracto.moderation.service;
import dev.sheldan.abstracto.core.models.AServerAChannelMessage; import dev.sheldan.abstracto.core.models.AServerAChannelMessage;
import dev.sheldan.abstracto.core.models.FullUserInServer; import dev.sheldan.abstracto.core.models.FullUserInServer;
import dev.sheldan.abstracto.core.models.ServerChannelMessage; import dev.sheldan.abstracto.core.models.ServerUser;
import dev.sheldan.abstracto.core.models.database.AChannel; import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.models.database.AServer; import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.AUserInAServer; import dev.sheldan.abstracto.core.models.database.AUserInAServer;
@@ -10,25 +10,27 @@ import dev.sheldan.abstracto.core.service.*;
import dev.sheldan.abstracto.core.service.management.ChannelManagementService; import dev.sheldan.abstracto.core.service.management.ChannelManagementService;
import dev.sheldan.abstracto.core.service.management.ServerManagementService; import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService; import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
import dev.sheldan.abstracto.core.utils.FutureUtils; import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition; import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition;
import dev.sheldan.abstracto.moderation.config.feature.mode.MutingMode; import dev.sheldan.abstracto.moderation.config.feature.MutingFeatureConfig;
import dev.sheldan.abstracto.moderation.config.posttarget.MutingPostTarget; import dev.sheldan.abstracto.moderation.config.posttarget.MutingPostTarget;
import dev.sheldan.abstracto.moderation.exception.MuteRoleNotSetupException;
import dev.sheldan.abstracto.moderation.exception.NoMuteFoundException; import dev.sheldan.abstracto.moderation.exception.NoMuteFoundException;
import dev.sheldan.abstracto.moderation.model.database.Infraction;
import dev.sheldan.abstracto.moderation.model.database.Mute; import dev.sheldan.abstracto.moderation.model.database.Mute;
import dev.sheldan.abstracto.moderation.model.database.MuteRole;
import dev.sheldan.abstracto.moderation.model.template.command.MuteContext; import dev.sheldan.abstracto.moderation.model.template.command.MuteContext;
import dev.sheldan.abstracto.moderation.model.template.command.MuteListenerModel;
import dev.sheldan.abstracto.moderation.model.template.command.MuteNotification; import dev.sheldan.abstracto.moderation.model.template.command.MuteNotification;
import dev.sheldan.abstracto.moderation.model.template.command.UnMuteLog; import dev.sheldan.abstracto.moderation.model.template.command.UnMuteLog;
import dev.sheldan.abstracto.moderation.service.management.MuteManagementService; import dev.sheldan.abstracto.moderation.service.management.MuteManagementService;
import dev.sheldan.abstracto.moderation.service.management.MuteRoleManagementService;
import dev.sheldan.abstracto.scheduling.model.JobParameters; import dev.sheldan.abstracto.scheduling.model.JobParameters;
import dev.sheldan.abstracto.scheduling.service.SchedulerService; import dev.sheldan.abstracto.scheduling.service.SchedulerService;
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.*; import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.GuildMessageChannel;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@@ -45,12 +47,6 @@ import java.util.concurrent.TimeUnit;
@Slf4j @Slf4j
public class MuteServiceBean implements MuteService { public class MuteServiceBean implements MuteService {
@Autowired
private MuteRoleManagementService muteRoleManagementService;
@Autowired
private RoleService roleService;
@Autowired @Autowired
private UserInServerManagementService userInServerManagementService; private UserInServerManagementService userInServerManagementService;
@@ -91,51 +87,38 @@ public class MuteServiceBean implements MuteService {
@Autowired @Autowired
private ServerManagementService serverManagementService; private ServerManagementService serverManagementService;
@Autowired
private FeatureModeService featureModeService;
@Autowired @Autowired
private ChannelService channelService; private ChannelService channelService;
@Autowired
private FeatureFlagService featureFlagService;
@Autowired
private ConfigService configService;
@Autowired
private InfractionService infractionService;
public static final String MUTE_LOG_TEMPLATE = "mute_log"; public static final String MUTE_LOG_TEMPLATE = "mute_log";
public static final String UN_MUTE_LOG_TEMPLATE = "unmute_log";
public static final String MUTE_NOTIFICATION_TEMPLATE = "mute_notification"; public static final String MUTE_NOTIFICATION_TEMPLATE = "mute_notification";
public static final String MUTE_COUNTER_KEY = "MUTES"; public static final String MUTE_COUNTER_KEY = "MUTES";
@Override @Override
public CompletableFuture<Void> muteMember(Member memberToMute, Member mutingMember, String reason, Instant unMuteDate, ServerChannelMessage message) { public CompletableFuture<Void> muteMember(Member memberToMute, String reason, Instant unMuteDate, Long channelId) {
FullUserInServer mutedUser = FullUserInServer FullUserInServer mutedUser = FullUserInServer
.builder() .builder()
.aUserInAServer(userInServerManagementService.loadOrCreateUser(memberToMute)) .aUserInAServer(userInServerManagementService.loadOrCreateUser(memberToMute))
.member(memberToMute) .member(memberToMute)
.build(); .build();
return muteUserInServer(mutedUser, reason, unMuteDate, channelId);
FullUserInServer mutingUser = FullUserInServer
.builder()
.aUserInAServer(userInServerManagementService.loadOrCreateUser(mutingMember))
.member(mutingMember)
.build();
return muteUserInServer(mutedUser, mutingUser, reason, unMuteDate, message);
} }
@Override @Override
public CompletableFuture<Void> muteUserInServer(FullUserInServer userBeingMuted, FullUserInServer userMuting, String reason, Instant unMuteDate, ServerChannelMessage message) { public CompletableFuture<Void> muteUserInServer(FullUserInServer userBeingMuted, String reason, Instant unMuteDate, Long channelId) {
AServer serverBeingMutedIn = userBeingMuted.getAUserInAServer().getServerReference();
if(!muteRoleManagementService.muteRoleForServerExists(serverBeingMutedIn)) {
log.error("Mute role for server {} has not been setup.", serverBeingMutedIn.getId());
throw new MuteRoleNotSetupException();
}
Member memberBeingMuted = userBeingMuted.getMember(); Member memberBeingMuted = userBeingMuted.getMember();
log.info("User {} mutes user {} in server {} until {}",
memberBeingMuted.getIdLong(), message.getServerId(), userMuting.getMember().getIdLong(), unMuteDate);
if(message.getMessageId() != null) {
log.info("because of message {} in channel {} in server {}", message.getMessageId(), message.getChannelId(), message.getServerId());
} else {
log.info("This mute was not triggered by a message.");
}
List<CompletableFuture<Void>> futures = new ArrayList<>(); List<CompletableFuture<Void>> futures = new ArrayList<>();
AUserInAServer userInServerBeingMuted = userBeingMuted.getAUserInAServer();
futures.add(applyMuteRole(userInServerBeingMuted)); futures.add(memberService.timeoutUser(userBeingMuted.getMember(), unMuteDate));
Guild guild = memberBeingMuted.getGuild(); Guild guild = memberBeingMuted.getGuild();
if(memberBeingMuted.getVoiceState() != null && memberBeingMuted.getVoiceState().getChannel() != null) { if(memberBeingMuted.getVoiceState() != null && memberBeingMuted.getVoiceState().getChannel() != null) {
futures.add(guild.kickVoiceMember(memberBeingMuted).submit()); futures.add(guild.kickVoiceMember(memberBeingMuted).submit());
@@ -146,17 +129,18 @@ public class MuteServiceBean implements MuteService {
.reason(reason) .reason(reason)
.serverName(guild.getName()) .serverName(guild.getName())
.build(); .build();
futures.add(sendMuteNotification(message, memberBeingMuted, muteNotification)); futures.add(sendMuteNotification(memberBeingMuted, muteNotification, channelId));
return FutureUtils.toSingleFutureGeneric(futures); return FutureUtils.toSingleFutureGeneric(futures);
} }
private CompletableFuture<Void> sendMuteNotification(ServerChannelMessage message, Member memberBeingMuted, MuteNotification muteNotification) { private CompletableFuture<Void> sendMuteNotification(Member memberBeingMuted, MuteNotification muteNotification, Long channelId) {
log.info("Notifying the user about the mute."); log.info("Notifying the user about the mute.");
CompletableFuture<Void> notificationFuture = new CompletableFuture<>(); CompletableFuture<Void> notificationFuture = new CompletableFuture<>();
String muteNotificationMessage = templateService.renderTemplate(MUTE_NOTIFICATION_TEMPLATE, muteNotification, message.getServerId()); Long guildId = memberBeingMuted.getGuild().getIdLong();
String muteNotificationMessage = templateService.renderTemplate(MUTE_NOTIFICATION_TEMPLATE, muteNotification, guildId);
CompletableFuture<Message> messageCompletableFuture = messageService.sendMessageToUser(memberBeingMuted.getUser(), muteNotificationMessage); CompletableFuture<Message> messageCompletableFuture = messageService.sendMessageToUser(memberBeingMuted.getUser(), muteNotificationMessage);
messageCompletableFuture.exceptionally(throwable -> { messageCompletableFuture.exceptionally(throwable -> {
GuildMessageChannel feedBackChannel = channelService.getMessageChannelFromServer(message.getServerId(), message.getChannelId()); GuildMessageChannel feedBackChannel = channelService.getMessageChannelFromServer(guildId, channelId);
channelService.sendTextToChannel(throwable.getMessage(), feedBackChannel).whenComplete((exceptionMessage, innerThrowable) -> { channelService.sendTextToChannel(throwable.getMessage(), feedBackChannel).whenComplete((exceptionMessage, innerThrowable) -> {
notificationFuture.complete(null); notificationFuture.complete(null);
log.info("Successfully notified user {} in server {} about mute.", memberBeingMuted.getId(), memberBeingMuted.getGuild().getId()); log.info("Successfully notified user {} in server {} about mute.", memberBeingMuted.getId(), memberBeingMuted.getGuild().getId());
@@ -172,23 +156,16 @@ public class MuteServiceBean implements MuteService {
return notificationFuture; return notificationFuture;
} }
private void createMuteObject(MuteContext muteContext, String triggerKey) { private void createMuteObject(MuteContext muteContext, String triggerKey, Long infractionId) {
AChannel channel = channelManagementService.loadChannel(muteContext.getContext().getChannelId()); AChannel channel = channelManagementService.loadChannel(muteContext.getChannelId());
AServerAChannelMessage origin = AServerAChannelMessage AServerAChannelMessage origin = AServerAChannelMessage
.builder() .builder()
.channel(channel) .channel(channel)
.server(channel.getServer()) .server(channel.getServer())
.messageId(muteContext.getContext().getMessageId())
.build(); .build();
AUserInAServer userInServerBeingMuted = userInServerManagementService.loadOrCreateUser(muteContext.getMutedUser()); AUserInAServer userInServerBeingMuted = userInServerManagementService.loadOrCreateUser(muteContext.getMutedUser());
AUserInAServer userInServerMuting = userInServerManagementService.loadOrCreateUser(muteContext.getMutingUser()); AUserInAServer userInServerMuting = userInServerManagementService.loadOrCreateUser(muteContext.getMutingUser());
muteManagementService.createMute(userInServerBeingMuted, userInServerMuting, muteContext.getReason(), muteContext.getMuteTargetDate(), origin, triggerKey, muteContext.getMuteId()); muteManagementService.createMute(userInServerBeingMuted, userInServerMuting, muteContext.getReason(), muteContext.getMuteTargetDate(), origin, triggerKey, muteContext.getMuteId(), infractionId);
}
@Override
public CompletableFuture<Void> applyMuteRole(AUserInAServer aUserInAServer) {
MuteRole muteRole = muteRoleManagementService.retrieveMuteRoleForServer(aUserInAServer.getServerReference());
return roleService.addRoleToUserAsync(aUserInAServer, muteRole.getRole());
} }
@Override @Override
@@ -217,6 +194,7 @@ public class MuteServiceBean implements MuteService {
@Override @Override
public void cancelUnMuteJob(Mute mute) { public void cancelUnMuteJob(Mute mute) {
if(mute.getTriggerKey() != null) { if(mute.getTriggerKey() != null) {
log.info("Cancelling un-mute job for mute {} in server {}.", mute.getMuteId().getId(), mute.getServer().getId());
schedulerService.stopTrigger(mute.getTriggerKey()); schedulerService.stopTrigger(mute.getTriggerKey());
} }
} }
@@ -224,107 +202,116 @@ public class MuteServiceBean implements MuteService {
@Override @Override
public CompletableFuture<Void> muteMemberWithLog(MuteContext context) { public CompletableFuture<Void> muteMemberWithLog(MuteContext context) {
log.debug("Muting member {} in server {}.", context.getMutedUser().getId(), context.getMutedUser().getGuild().getId()); log.debug("Muting member {} in server {}.", context.getMutedUser().getId(), context.getMutedUser().getGuild().getId());
AServer server = serverManagementService.loadOrCreate(context.getContext().getServerId()); AServer server = serverManagementService.loadOrCreate(context.getMutedUser().getGuild().getIdLong());
Long nextCounterValue = counterService.getNextCounterValue(server, MUTE_COUNTER_KEY); Long nextCounterValue = counterService.getNextCounterValue(server, MUTE_COUNTER_KEY);
context.setMuteId(nextCounterValue); context.setMuteId(nextCounterValue);
CompletableFuture<Void> mutingFuture = muteMember(context.getMutedUser(), context.getMutingUser(), context.getReason(), context.getMuteTargetDate(), context.getContext()); return muteMember(context.getMutedUser(), context.getReason(), context.getMuteTargetDate(), context.getChannelId())
CompletableFuture<Void> muteLogFuture = sendMuteLog(context, server); .thenCompose(unused -> self.sendMuteLog(context))
return CompletableFuture.allOf(mutingFuture, muteLogFuture).thenAccept(aVoid -> .thenCompose(logMessage -> self.evaluateAndStoreInfraction(context, logMessage))
self.persistMute(context) .thenAccept(infractionId -> self.persistMute(context, infractionId));
);
} }
@Transactional @Transactional
public void persistMute(MuteContext context) { public CompletableFuture<Long> evaluateAndStoreInfraction(MuteContext context, Message logMessage) {
String triggerKey = startUnMuteJobFor(context.getMuteTargetDate(), context.getMuteId(), context.getContext().getServerId()); Guild guild = context.getMutedUser().getGuild();
createMuteObject(context, triggerKey); if(featureFlagService.getFeatureFlagValue(ModerationFeatureDefinition.INFRACTIONS, guild.getIdLong())) {
Long infractionPoints = configService.getLongValueOrConfigDefault(MutingFeatureConfig.MUTE_INFRACTION_POINTS, guild.getIdLong());
AUserInAServer mutedUser = userInServerManagementService.loadOrCreateUser(context.getMutedUser());
AUserInAServer mutingUser = userInServerManagementService.loadOrCreateUser(context.getMutingUser());
Map<String, String> parameters = new HashMap<>();
parameters.put(INFRACTION_PARAMETER_DURATION_KEY, templateService.renderDuration(Duration.between(Instant.now(), context.getMuteTargetDate()), guild.getIdLong()));
return infractionService.createInfractionWithNotification(mutedUser, infractionPoints, MUTE_INFRACTION_TYPE, context.getReason(), mutingUser, parameters, logMessage)
.thenApply(Infraction::getId);
} else {
return CompletableFuture.completedFuture(null);
}
} }
private CompletableFuture<Void> sendMuteLog(MuteContext muteLogModel, AServer server) { @Transactional
public void persistMute(MuteContext context, Long infractionId) {
completelyUnMuteMember(context.getMutedUser());
String triggerKey = startUnMuteJobFor(context.getMuteTargetDate(), context.getMuteId(), context.getMutedUser().getGuild().getIdLong());
createMuteObject(context, triggerKey, infractionId);
}
@Transactional
public CompletableFuture<Message> sendMuteLog(MuteContext muteLogModel) {
MuteListenerModel model = MuteListenerModel
.builder()
.mutedUser(muteLogModel.getMutedUser())
.mutingUser(muteLogModel.getMutingUser())
.channelId(muteLogModel.getChannelId())
.oldMuteTargetDate(null)
.muteTargetDate(muteLogModel.getMuteTargetDate())
.reason(muteLogModel.getReason())
.build();
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, model, muteLogModel.getMutedUser().getIdLong());
return FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(message, MutingPostTarget.MUTE_LOG, muteLogModel.getContext().getServerId())); List<CompletableFuture<Message>> futures = postTargetService.sendEmbedInPostTarget(message, MutingPostTarget.MUTE_LOG, muteLogModel.getMutedUser().getGuild().getIdLong());
return FutureUtils.toSingleFutureGeneric(futures).thenApply(unused -> futures.get(0).join());
} }
private CompletableFuture<Void> sendUnMuteLogMessage(UnMuteLog muteLogModel, AServer server) { private CompletableFuture<Void> sendUnMuteLogMessage(UnMuteLog muteLogModel, AServer server) {
log.debug("Sending unMute log for mute {} to the mute posttarget in server {}", muteLogModel.getMute().getMuteId().getId(), server.getId()); MuteListenerModel model = MuteListenerModel
MessageToSend message = templateService.renderEmbedTemplate(UN_MUTE_LOG_TEMPLATE, muteLogModel, server.getId()); .builder()
.mutedUser(muteLogModel.getUnMutedUser())
.mutingUser(muteLogModel.getMutingUser())
.oldMuteTargetDate(muteLogModel.getMute() != null ? muteLogModel.getMute().getMuteTargetDate() : null)
.muteTargetDate(null)
.build();
if(muteLogModel.getMute() != null) {
log.debug("Sending unMute log for mute {} to the mute posttarget in server {}", muteLogModel.getMute().getMuteId().getId(), server.getId());
}
MessageToSend message = templateService.renderEmbedTemplate(MUTE_LOG_TEMPLATE, model, server.getId());
return FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(message, MutingPostTarget.MUTE_LOG, server.getId())); return FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(message, MutingPostTarget.MUTE_LOG, server.getId()));
} }
@Override @Override
@Transactional @Transactional
public CompletableFuture<Void> unMuteUser(AUserInAServer aUserInAServer) { public CompletableFuture<Void> unMuteUser(AUserInAServer userToUnmute, Member unMutingMember) {
if(!muteManagementService.hasActiveMute(aUserInAServer)) { boolean muteActive = muteManagementService.hasActiveMute(userToUnmute);
throw new NoMuteFoundException(); if(!muteActive) {
} CompletableFuture<Member> unMutedMemberFuture = memberService.retrieveMemberInServer(ServerUser.fromAUserInAServer(userToUnmute));
Mute mute = muteManagementService.getAMuteOf(aUserInAServer); return unMutedMemberFuture
Long muteId = mute.getMuteId().getId(); .thenCompose(member -> memberService.removeTimeout(member))
CompletableFuture<Member> mutingMemberFuture = memberService.getMemberInServerAsync(mute.getMutingUser()); .thenCompose(unused -> self.sendUnmuteLog(null, unMutingMember.getGuild(), unMutedMemberFuture.join(), unMutingMember));
CompletableFuture<Member> mutedMemberFuture = memberService.getMemberInServerAsync(mute.getMutedUser());
Guild guild = guildService.getGuildById(mute.getMuteId().getServerId());
return endMute(mute, false).thenCompose(unused ->
CompletableFuture.allOf(mutingMemberFuture, mutedMemberFuture)
).thenCompose(unused -> self.sendUnMuteLogForManualUnMute(muteId, mutingMemberFuture, mutedMemberFuture, guild));
}
@Transactional
public CompletableFuture<Void> sendUnMuteLogForManualUnMute(Long muteId, CompletableFuture<Member> mutingMemberFuture, CompletableFuture<Member> mutedMemberFuture, Guild guild) {
CompletableFuture<Void> completableFuture;
if(featureModeService.featureModeActive(ModerationFeatureDefinition.MUTING, guild.getIdLong(), MutingMode.MANUAL_UN_MUTE_LOGGING)) {
completableFuture = self.sendUnmuteLog(muteId, guild, mutingMemberFuture, mutedMemberFuture);
log.info("Sending un mute notification for manual un mute for mute {} in server {}.", muteId, guild.getIdLong());
} else { } else {
completableFuture = CompletableFuture.completedFuture(null); Mute mute = muteManagementService.getAMuteOf(userToUnmute);
log.info("Not sending unMute log, because feature mode {} in feature {} has been disabled for server {}.", MutingMode.MANUAL_UN_MUTE_LOGGING, ModerationFeatureDefinition.WARNING, guild.getIdLong()); return endMute(mute);
} }
return completableFuture;
} }
@Override @Override
public CompletableFuture<Void> endMute(Mute mute, Boolean sendNotification) { public CompletableFuture<Void> endMute(Mute mute) {
if(mute.getMuteEnded()) { if(mute.getMuteEnded()) {
log.info("Mute {} in server {} has already ended. Not unmuting.", mute.getMuteId().getId(), mute.getMuteId().getServerId()); log.info("Mute {} in server {} has already ended. Not unmuting.", mute.getMuteId().getId(), mute.getMuteId().getServerId());
return CompletableFuture.completedFuture(null); return CompletableFuture.completedFuture(null);
} }
Long muteId = mute.getMuteId().getId(); Long muteId = mute.getMuteId().getId();
Long serverId = mute.getMuteId().getServerId(); Guild guild = guildService.getGuildById(mute.getMuteId().getServerId());
AServer mutingServer = mute.getServer(); AServer mutingServer = mute.getServer();
log.info("UnMuting {} in server {}", mute.getMutedUser().getUserReference().getId(), mutingServer.getId()); log.info("UnMuting {} in server {}", mute.getMutedUser().getUserReference().getId(), mutingServer.getId());
MuteRole muteRole = muteRoleManagementService.retrieveMuteRoleForServer(mutingServer);
log.debug("Using the mute role {} mapping to role {}", muteRole.getId(), muteRole.getRole().getId());
Guild guild = guildService.getGuildById(mutingServer.getId());
CompletableFuture<Void> roleRemovalFuture = roleService.removeRoleFromUserAsync(mute.getMutedUser(), muteRole.getRole());
CompletableFuture<Member> mutingMemberFuture = memberService.getMemberInServerAsync(mute.getMutingUser());
CompletableFuture<Member> mutedMemberFuture = memberService.getMemberInServerAsync(mute.getMutedUser()); CompletableFuture<Member> mutedMemberFuture = memberService.getMemberInServerAsync(mute.getMutedUser());
CompletableFuture<Void> finalFuture = new CompletableFuture<>(); CompletableFuture<Member> mutingMemberFuture = memberService.getMemberInServerAsync(mute.getMutingUser());
CompletableFuture.allOf(mutingMemberFuture, mutedMemberFuture, roleRemovalFuture, mutingMemberFuture, mutedMemberFuture).handle((aVoid, throwable) -> { return CompletableFuture.allOf(mutedMemberFuture, mutingMemberFuture)
if(sendNotification) { .thenAccept(member -> memberService.removeTimeout(mutedMemberFuture.join()))
self.sendUnmuteLog(muteId, guild, mutingMemberFuture, mutedMemberFuture).thenAccept(aVoid1 -> .thenCompose(unused -> self.sendUnmuteLog(muteId, guild, mutingMemberFuture, mutedMemberFuture));
finalFuture.complete(null)
).exceptionally(throwable1 -> {
log.error("Unmute log failed to send for mute {} in server {}.", muteId, serverId, throwable1);
finalFuture.completeExceptionally(null);
return null;
});
} else {
finalFuture.complete(null);
}
return null;
}).exceptionally(throwable -> {
finalFuture.completeExceptionally(throwable);
return null;
});
return finalFuture;
} }
@Transactional @Transactional
public CompletableFuture<Void> sendUnmuteLog(Long muteId, Guild guild, CompletableFuture<Member> mutingMemberFuture, CompletableFuture<Member> mutedMemberFuture) { public CompletableFuture<Void> sendUnmuteLog(Long muteId, Guild guild, CompletableFuture<Member> mutingMemberFuture, CompletableFuture<Member> mutedMemberFuture) {
Mute mute = muteManagementService.findMute(muteId, guild.getIdLong());
AServer mutingServer = serverManagementService.loadServer(guild.getIdLong());
Member mutingMember = !mutingMemberFuture.isCompletedExceptionally() ? mutingMemberFuture.join() : null; Member mutingMember = !mutingMemberFuture.isCompletedExceptionally() ? mutingMemberFuture.join() : null;
Member mutedMember = !mutedMemberFuture.isCompletedExceptionally() ? mutedMemberFuture.join() : null; Member mutedMember = !mutedMemberFuture.isCompletedExceptionally() ? mutedMemberFuture.join() : null;
return sendUnmuteLog(muteId, guild, mutedMember, mutingMember);
}
@Transactional
public CompletableFuture<Void> sendUnmuteLog(Long muteId, Guild guild, Member mutedMember, Member mutingMember) {
Mute mute = null;
if(muteId != null) {
mute = muteManagementService.findMute(muteId, guild.getIdLong());
}
AServer mutingServer = serverManagementService.loadServer(guild.getIdLong());
UnMuteLog unMuteLog = UnMuteLog UnMuteLog unMuteLog = UnMuteLog
.builder() .builder()
.mute(mute) .mute(mute)
@@ -333,9 +320,11 @@ public class MuteServiceBean implements MuteService {
.guild(guild) .guild(guild)
.build(); .build();
CompletableFuture<Void> notificationFuture = sendUnMuteLogMessage(unMuteLog, mutingServer); CompletableFuture<Void> notificationFuture = sendUnMuteLogMessage(unMuteLog, mutingServer);
return CompletableFuture.allOf(notificationFuture).thenAccept(aVoid -> return CompletableFuture.allOf(notificationFuture).thenAccept(aVoid -> {
self.endMuteInDatabase(muteId, guild.getIdLong()) if(muteId != null) {
); self.endMuteInDatabase(muteId, guild.getIdLong());
}
});
} }
@Transactional @Transactional
@@ -344,7 +333,6 @@ public class MuteServiceBean implements MuteService {
muteOptional.ifPresent(mute -> muteOptional.ifPresent(mute ->
completelyUnMuteUser(mute.getMutedUser()) completelyUnMuteUser(mute.getMutedUser())
); );
} }
@Override @Override
@@ -353,7 +341,7 @@ public class MuteServiceBean implements MuteService {
log.info("UnMuting the mute {} in server {}", muteId, serverId); log.info("UnMuting the mute {} in server {}", muteId, serverId);
Optional<Mute> muteOptional = muteManagementService.findMuteOptional(muteId, serverId); Optional<Mute> muteOptional = muteManagementService.findMuteOptional(muteId, serverId);
if(muteOptional.isPresent()) { if(muteOptional.isPresent()) {
return endMute(muteOptional.get(), true); return endMute(muteOptional.get());
} else { } else {
throw new NoMuteFoundException(); throw new NoMuteFoundException();
} }
@@ -375,10 +363,4 @@ public class MuteServiceBean implements MuteService {
completelyUnMuteUser(userInServerManagementService.loadOrCreateUser(member)); completelyUnMuteUser(userInServerManagementService.loadOrCreateUser(member));
} }
@Override
public CompletableFuture<Void> muteMemberWithoutContext(Member member) {
AUserInAServer aUserInAServer = userInServerManagementService.loadOrCreateUser(member);
return applyMuteRole(aUserInAServer);
}
} }

View File

@@ -3,14 +3,15 @@ package dev.sheldan.abstracto.moderation.service;
import dev.sheldan.abstracto.core.models.ServerUser; import dev.sheldan.abstracto.core.models.ServerUser;
import dev.sheldan.abstracto.core.models.cache.CachedMessage; import dev.sheldan.abstracto.core.models.cache.CachedMessage;
import dev.sheldan.abstracto.core.models.database.AUserInAServer; import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.service.ChannelService; import dev.sheldan.abstracto.core.service.*;
import dev.sheldan.abstracto.core.service.ConfigService;
import dev.sheldan.abstracto.core.service.PostTargetService;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService; import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
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.core.utils.FutureUtils;
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition;
import dev.sheldan.abstracto.moderation.config.feature.mode.ReportReactionMode;
import dev.sheldan.abstracto.moderation.config.posttarget.ReactionReportPostTarget; import dev.sheldan.abstracto.moderation.config.posttarget.ReactionReportPostTarget;
import dev.sheldan.abstracto.moderation.listener.manager.ReportMessageCreatedListenerManager;
import dev.sheldan.abstracto.moderation.model.database.ModerationUser; import dev.sheldan.abstracto.moderation.model.database.ModerationUser;
import dev.sheldan.abstracto.moderation.model.database.ReactionReport; import dev.sheldan.abstracto.moderation.model.database.ReactionReport;
import dev.sheldan.abstracto.moderation.model.template.listener.ReportReactionNotificationModel; import dev.sheldan.abstracto.moderation.model.template.listener.ReportReactionNotificationModel;
@@ -19,7 +20,6 @@ import dev.sheldan.abstracto.moderation.service.management.ReactionReportManagem
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.GuildMessageChannel; import net.dv8tion.jda.api.entities.GuildMessageChannel;
import net.dv8tion.jda.api.entities.Message; import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.TextChannel;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@@ -59,46 +59,60 @@ public class ReactionReportServiceBean implements ReactionReportService {
@Autowired @Autowired
private ReactionReportServiceBean self; private ReactionReportServiceBean self;
@Autowired
private CacheEntityService cacheEntityService;
@Autowired
private FeatureModeService featureModeService;
@Autowired
private ReportMessageCreatedListenerManager reportMessageCreatedListenerManager;
private static final String REACTION_REPORT_TEMPLATE_KEY = "reactionReport_notification"; private static final String REACTION_REPORT_TEMPLATE_KEY = "reactionReport_notification";
public static final String REACTION_REPORT_MODAL_ORIGIN = "reportMessageModal";
public static final String REACTION_REPORT_RESPONSE_TEMPLATE = "reactionReport_response";
public static final String REACTION_REPORT_COOLDOWN_RESPONSE_TEMPLATE = "reactionReport_cooldown_response";
public static final String REACTION_REPORT_OWN_MESSAGE_RESPONSE_TEMPLATE = "reactionReport_own_message_response";
@Override @Override
public CompletableFuture<Void> createReactionReport(CachedMessage reportedMessage, ServerUser reporter) { public CompletableFuture<Void> createReactionReport(CachedMessage reportedMessage, ServerUser reporter, String context) {
AUserInAServer reportedUser = userInServerManagementService.loadOrCreateUser(reportedMessage.getAuthorAsServerUser()); AUserInAServer reportedUser = userInServerManagementService.loadOrCreateUser(reportedMessage.getAuthorAsServerUser());
AUserInAServer reportingUser = userInServerManagementService.loadOrCreateUser(reporter);
Optional<ModerationUser> moderationUserOptional = moderationUserManagementService.findModerationUser(reportingUser);
Long serverId = reporter.getServerId(); Long serverId = reporter.getServerId();
log.info("User {} in server {} was reported on message {}", reportedMessage.getAuthor().getAuthorId(), serverId, reportedMessage.getMessageId());
Long cooldownSeconds = configService.getLongValueOrConfigDefault(REACTION_REPORT_COOLDOWN, serverId); Long cooldownSeconds = configService.getLongValueOrConfigDefault(REACTION_REPORT_COOLDOWN, serverId);
Duration maxAge = Duration.of(cooldownSeconds, ChronoUnit.SECONDS); Duration maxAge = Duration.of(cooldownSeconds, ChronoUnit.SECONDS);
if(moderationUserOptional.isPresent()) {
ModerationUser reporterModerationUser = moderationUserOptional.get();
Instant minAllowedReportTime = Instant.now().minus(maxAge);
if(reporterModerationUser.getLastReportTimeStamp() != null && reporterModerationUser.getLastReportTimeStamp().isAfter(minAllowedReportTime)) {
log.info("User {} in server {} reported user {} within the cooldown. Ignoring.", reporter.getUserId(), serverId, reportedMessage.getAuthor().getAuthorId());
return CompletableFuture.completedFuture(null);
}
}
log.info("User {} in server {} reported user {}..", reporter.getUserId(), serverId, reportedMessage.getAuthor().getAuthorId());
Optional<ReactionReport> recentReportOptional = reactionReportManagementService.findRecentReactionReportAboutUser(reportedUser, maxAge); Optional<ReactionReport> recentReportOptional = reactionReportManagementService.findRecentReactionReportAboutUser(reportedUser, maxAge);
if(!recentReportOptional.isPresent()) { boolean singularMessage = featureModeService.featureModeActive(ModerationFeatureDefinition.REPORT_REACTIONS, serverId, ReportReactionMode.SINGULAR_MESSAGE);
ReportReactionNotificationModel model = ReportReactionNotificationModel boolean anonym = featureModeService.featureModeActive(ModerationFeatureDefinition.REPORT_REACTIONS, reporter.getServerId(), ReportReactionMode.ANONYMOUS);
.builder() if(recentReportOptional.isPresent() && singularMessage) {
.reportCount(1)
.reportedMessage(reportedMessage)
.build();
MessageToSend messageToSend = templateService.renderEmbedTemplate(REACTION_REPORT_TEMPLATE_KEY, model, serverId);
List<CompletableFuture<Message>> messageFutures = postTargetService.sendEmbedInPostTarget(messageToSend, ReactionReportPostTarget.REACTION_REPORTS, serverId);
return FutureUtils.toSingleFutureGeneric(messageFutures)
.thenAccept(unused -> self.createReactionReportInDb(reportedMessage, messageFutures.get(0).join(), reporter));
} else {
ReactionReport report = recentReportOptional.get(); ReactionReport report = recentReportOptional.get();
log.info("Report is already present in channel {} with message {}. Updating field.", report.getReportChannel().getId(), report.getReportMessageId()); log.info("Report is already present in channel {} with message {}. Updating field.", report.getReportChannel().getId(), report.getReportMessageId());
report.setReportCount(report.getReportCount() + 1); report.setReportCount(report.getReportCount() + 1);
GuildMessageChannel reportTextChannel = channelService.getMessageChannelFromServer(serverId, report.getReportChannel().getId()); GuildMessageChannel reportTextChannel = channelService.getMessageChannelFromServer(serverId, report.getReportChannel().getId());
return channelService.editFieldValueInMessage(reportTextChannel, report.getReportMessageId(), 0, report.getReportCount().toString()) return channelService.editFieldValueInMessage(reportTextChannel, report.getReportMessageId(), 0, report.getReportCount().toString())
.thenAccept(message -> self.updateModerationUserReportCooldown(reporter)); .thenAccept(message -> self.updateModerationUserReportCooldown(reporter));
} else {
ReportReactionNotificationModel model = ReportReactionNotificationModel
.builder()
.reportCount(1)
.context(context)
.singularMessage(singularMessage)
.reportedMessage(reportedMessage)
.build();
MessageToSend messageToSend = templateService.renderEmbedTemplate(REACTION_REPORT_TEMPLATE_KEY, model, serverId);
List<CompletableFuture<Message>> messageFutures = postTargetService.sendEmbedInPostTarget(messageToSend, ReactionReportPostTarget.REACTION_REPORTS, serverId);
return FutureUtils.toSingleFutureGeneric(messageFutures)
.thenAccept(unused -> reportMessageCreatedListenerManager.sendReportMessageCreatedEvent(reportedMessage, messageFutures.get(0).join(), anonym ? null : reporter))
.thenAccept(unused -> self.createReactionReportInDb(reportedMessage, messageFutures.get(0).join(), reporter));
} }
} }
@Override
public CompletableFuture<Void> createReactionReport(Message message, ServerUser reporter, String context) {
return cacheEntityService.buildCachedMessageFromMessage(message)
.thenCompose(cachedMessage -> createReactionReport(cachedMessage, reporter, context));
}
@Transactional @Transactional
public void createReactionReportInDb(CachedMessage cachedMessage, Message reportMessage, ServerUser reporter) { public void createReactionReportInDb(CachedMessage cachedMessage, Message reportMessage, ServerUser reporter) {
if(reportMessage == null) { if(reportMessage == null) {
@@ -112,15 +126,35 @@ public class ReactionReportServiceBean implements ReactionReportService {
@Transactional @Transactional
public void updateModerationUserReportCooldown(ServerUser reporter) { public void updateModerationUserReportCooldown(ServerUser reporter) {
AUserInAServer reporterAUserInServer = userInServerManagementService.loadOrCreateUser(reporter); if(!featureModeService.featureModeActive(ModerationFeatureDefinition.REPORT_REACTIONS, reporter.getServerId(), ReportReactionMode.ANONYMOUS)) {
Optional<ModerationUser> optionalModerationUser = moderationUserManagementService.findModerationUser(reporterAUserInServer); AUserInAServer reporterAUserInServer = userInServerManagementService.loadOrCreateUser(reporter);
Instant reportTimeStamp = Instant.now(); Optional<ModerationUser> optionalModerationUser = moderationUserManagementService.findModerationUser(reporterAUserInServer);
if(optionalModerationUser.isPresent()) { Instant reportTimeStamp = Instant.now();
log.info("Updating last report time of user {}.", reporter.getUserId()); if(optionalModerationUser.isPresent()) {
optionalModerationUser.get().setLastReportTimeStamp(reportTimeStamp); log.info("Updating last report time of user {}.", reporter.getUserId());
} else { optionalModerationUser.get().setLastReportTimeStamp(reportTimeStamp);
log.info("Creating new moderation user instance for user {} to track report cooldowns.", reporter.getUserId()); } else {
moderationUserManagementService.createModerationUserWithReportTimeStamp(reporterAUserInServer, reportTimeStamp); log.info("Creating new moderation user instance for user {} to track report cooldowns.", reporter.getUserId());
moderationUserManagementService.createModerationUserWithReportTimeStamp(reporterAUserInServer, reportTimeStamp);
}
} }
} }
@Override
public boolean allowedToReport(ServerUser reporter) {
Long cooldownSeconds = configService.getLongValueOrConfigDefault(REACTION_REPORT_COOLDOWN, reporter.getServerId());
Duration maxAge = Duration.of(cooldownSeconds, ChronoUnit.SECONDS);
AUserInAServer reportingUser = userInServerManagementService.loadOrCreateUser(reporter);
Optional<ModerationUser> moderationUserOptional = moderationUserManagementService.findModerationUser(reportingUser);
if(moderationUserOptional.isPresent()) {
ModerationUser reporterModerationUser = moderationUserOptional.get();
Instant minAllowedReportTime = Instant
.now()
.minus(maxAge);
if(reporterModerationUser.getLastReportTimeStamp() != null) {
return !reporterModerationUser.getLastReportTimeStamp().isAfter(minAllowedReportTime);
}
}
return true;
}
} }

View File

@@ -111,8 +111,8 @@ public class WarnServiceBean implements WarnService {
@Override @Override
public CompletableFuture<Void> notifyAndLogFullUserWarning(WarnContext context) { public CompletableFuture<Void> notifyAndLogFullUserWarning(WarnContext context) {
AServer server = serverManagementService.loadOrCreate(context.getGuild().getIdLong()); Long serverId = context.getGuild().getIdLong();
Long warningId = counterService.getNextCounterValue(server, WARNINGS_COUNTER_KEY); Long warningId = counterService.getNextCounterValue(serverId, WARNINGS_COUNTER_KEY);
context.setWarnId(warningId); context.setWarnId(warningId);
Member warnedMember = context.getWarnedMember(); Member warnedMember = context.getWarnedMember();
Member warningMember = context.getMember(); Member warningMember = context.getMember();
@@ -124,29 +124,39 @@ public class WarnServiceBean implements WarnService {
.warnId(warningId) .warnId(warningId)
.serverName(guild.getName()) .serverName(guild.getName())
.build(); .build();
Long serverId = server.getId(); String warnNotificationMessage = templateService.renderTemplate(WARN_NOTIFICATION_TEMPLATE, warnNotification, serverId);
String warnNotificationMessage = templateService.renderTemplate(WARN_NOTIFICATION_TEMPLATE, warnNotification, server.getId()); return messageService.sendMessageToUser(warnedMember.getUser(), warnNotificationMessage)
List<CompletableFuture> futures = new ArrayList<>(); .exceptionally(throwable -> {
CompletableFuture<Void> notificationFuture = new CompletableFuture<>(); log.warn("Failed to notify user {} of warning {} in guild {}.", warnedMember.getId(), warningId, serverId);
messageService.sendMessageToUser(warnedMember.getUser(), warnNotificationMessage).whenComplete((message, throwable) -> { return null;
if(throwable != null) { })
log.warn("Failed to notify user {} of warning {} in guild {}.", warnedMember.getId(), warningId, serverId); .thenCompose(message -> self.sendWarningLog(context))
} .thenCompose(logMessage -> self.evaluateInfraction(context, logMessage))
notificationFuture.complete(null); .thenAccept(context::setInfractionId);
}); }
futures.add(notificationFuture);
log.debug("Logging warning for server {}.", server.getId()); @Transactional
public CompletableFuture<Message> sendWarningLog(WarnContext context) {
MessageToSend message = renderMessageModel(context);
List<CompletableFuture<Message>> futures = postTargetService.sendEmbedInPostTarget(message, WarningPostTarget.WARN_LOG, context.getGuild().getIdLong());
return FutureUtils.toSingleFutureGeneric(futures).thenCompose(unused -> futures.get(0));
}
@Transactional
public CompletableFuture<Long> evaluateInfraction(WarnContext context, Message logMessage) {
Long serverId = context.getGuild().getIdLong();
if(featureFlagService.getFeatureFlagValue(ModerationFeatureDefinition.INFRACTIONS, serverId)) { if(featureFlagService.getFeatureFlagValue(ModerationFeatureDefinition.INFRACTIONS, serverId)) {
Long infractionPoints = configService.getLongValueOrConfigDefault(WarningFeatureConfig.WARN_INFRACTION_POINTS, serverId); Long infractionPoints = configService.getLongValueOrConfigDefault(WarningFeatureConfig.WARN_INFRACTION_POINTS, serverId);
AUserInAServer warnedUser = userInServerManagementService.loadOrCreateUser(warnedMember); AServer server = serverManagementService.loadServer(context.getGuild());
warnedUser.setServerReference(server); AUserInAServer warnedUser = userInServerManagementService.loadOrCreateUser(server, context.getWarnedMember().getIdLong());
futures.add(infractionService.createInfractionWithNotification(warnedUser, infractionPoints) AUserInAServer warningUser = userInServerManagementService.loadOrCreateUser(server, context.getMember().getIdLong());
.thenAccept(infraction -> context.setInfractionId(infraction.getId()))); // both user could create the server object, we need to make sure we have the same reference
warnedUser.setServerReference(warningUser.getServerReference());
return infractionService.createInfractionWithNotification(warnedUser, infractionPoints, WARN_INFRACTION_TYPE, context.getReason(), warningUser, logMessage)
.thenApply(Infraction::getId);
} else {
return CompletableFuture.completedFuture(null);
} }
MessageToSend message = templateService.renderEmbedTemplate(WARN_LOG_TEMPLATE, context, server.getId());
futures.addAll(postTargetService.sendEmbedInPostTarget(message, WarningPostTarget.WARN_LOG, context.getGuild().getIdLong()));
return FutureUtils.toSingleFuture(futures);
} }
@Override @Override
@@ -298,6 +308,10 @@ public class WarnServiceBean implements WarnService {
} }
} }
public MessageToSend renderMessageModel(WarnContext warnContext) {
return templateService.renderEmbedTemplate(WARN_LOG_TEMPLATE, warnContext, warnContext.getGuild().getIdLong());
}
private CompletableFuture<Void> logDecayedWarnings(AServer server, List<Warning> warningsToDecay) { private CompletableFuture<Void> logDecayedWarnings(AServer server, List<Warning> warningsToDecay) {
log.debug("Loading members decaying {} warnings in server {}.", warningsToDecay.size(), server.getId()); log.debug("Loading members decaying {} warnings in server {}.", warningsToDecay.size(), server.getId());
HashMap<ServerSpecificId, FutureMemberPair> warningMembers = new HashMap<>(); HashMap<ServerSpecificId, FutureMemberPair> warningMembers = new HashMap<>();

View File

@@ -1,25 +1,44 @@
package dev.sheldan.abstracto.moderation.service.management; package dev.sheldan.abstracto.moderation.service.management;
import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.AUserInAServer; import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.service.management.ChannelManagementService;
import dev.sheldan.abstracto.moderation.model.database.Infraction; import dev.sheldan.abstracto.moderation.model.database.Infraction;
import dev.sheldan.abstracto.moderation.repository.InfractionRepository; import dev.sheldan.abstracto.moderation.repository.InfractionRepository;
import net.dv8tion.jda.api.entities.Message;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.List; import java.util.List;
@Component @Component
public class InfractionManagementServiceBean implements InfractionManagementService{ public class InfractionManagementServiceBean implements InfractionManagementService {
@Autowired @Autowired
private InfractionRepository infractionRepository; private InfractionRepository infractionRepository;
@Autowired
private ChannelManagementService channelManagementService;
@Override @Override
public Infraction createInfraction(AUserInAServer aUserInAServer, Long points) { public Infraction createInfraction(AUserInAServer target, Long points, String type, String description, AUserInAServer creator, Message message) {
AChannel channel;
if(message != null) {
channel = channelManagementService.loadChannel(message.getChannel().getIdLong());
} else {
channel = null;
}
Infraction infraction = Infraction Infraction infraction = Infraction
.builder() .builder()
.user(aUserInAServer) .user(target)
.server(aUserInAServer.getServerReference()) .infractionCreator(creator)
.server(target.getServerReference())
.decayed(false)
.logChannel(channel)
.logMessageId(message != null ? message.getIdLong() : null)
.type(type)
.description(description)
.points(points) .points(points)
.build(); .build();
return infractionRepository.save(infraction); return infractionRepository.save(infraction);
@@ -30,6 +49,16 @@ public class InfractionManagementServiceBean implements InfractionManagementServ
return infractionRepository.findByUserAndDecayedFalse(aUserInAServer); return infractionRepository.findByUserAndDecayedFalse(aUserInAServer);
} }
@Override
public List<Infraction> getInfractionsForUser(AUserInAServer aUserInAServer) {
return infractionRepository.findByUserOrderByCreated(aUserInAServer);
}
@Override
public List<Infraction> getInfractionsForServer(AServer server) {
return infractionRepository.findByServerOrderByCreated(server);
}
@Override @Override
public Infraction loadInfraction(Long infraction) { public Infraction loadInfraction(Long infraction) {
return infractionRepository.getOne(infraction); return infractionRepository.getOne(infraction);

View File

@@ -0,0 +1,27 @@
package dev.sheldan.abstracto.moderation.service.management;
import dev.sheldan.abstracto.moderation.model.database.Infraction;
import dev.sheldan.abstracto.moderation.model.database.InfractionParameter;
import dev.sheldan.abstracto.moderation.model.database.embedded.InfractionParameterId;
import dev.sheldan.abstracto.moderation.repository.InfractionParameterRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class InfractionParameterManagementServiceBean implements InfractionParameterManagementService{
@Autowired
private InfractionParameterRepository infractionParameterRepository;
@Override
public InfractionParameter createInfractionParameter(Infraction infraction, String key, String value) {
InfractionParameterId id = new InfractionParameterId(infraction.getId(), key);
InfractionParameter parameter = InfractionParameter
.builder()
.infractionParameterId(id)
.value(value)
.infraction(infraction)
.build();
return infractionParameterRepository.save(parameter);
}
}

View File

@@ -6,6 +6,8 @@ import dev.sheldan.abstracto.core.models.ServerSpecificId;
import dev.sheldan.abstracto.core.models.database.AServer; import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.AUserInAServer; import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService; import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
import dev.sheldan.abstracto.moderation.exception.NoMuteFoundException;
import dev.sheldan.abstracto.moderation.model.database.Infraction;
import dev.sheldan.abstracto.moderation.model.database.Mute; import dev.sheldan.abstracto.moderation.model.database.Mute;
import dev.sheldan.abstracto.moderation.repository.MuteRepository; import dev.sheldan.abstracto.moderation.repository.MuteRepository;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@@ -27,11 +29,20 @@ public class MuteManagementServiceBean implements MuteManagementService {
@Autowired @Autowired
private UserInServerManagementService userInServerManagementService; private UserInServerManagementService userInServerManagementService;
@Autowired
private InfractionManagementService infractionManagementService;
@Override @Override
public Mute createMute(AUserInAServer mutedUser, AUserInAServer mutingUser, String reason, Instant unMuteDate, AServerAChannelMessage muteMessage, String triggerKey, Long muteId) { public Mute createMute(AUserInAServer mutedUser, AUserInAServer mutingUser, String reason, Instant unMuteDate, AServerAChannelMessage muteMessage, String triggerKey, Long muteId, Long infractionId) {
log.debug("Creating mute for user {} executed by user {} in server {}, user will be un-muted at {}", log.debug("Creating mute for user {} executed by user {} in server {}, user will be un-muted at {}",
mutedUser.getUserReference().getId(), mutingUser.getUserReference().getId(), mutedUser.getServerReference().getId(), unMuteDate); mutedUser.getUserReference().getId(), mutingUser.getUserReference().getId(), mutedUser.getServerReference().getId(), unMuteDate);
ServerSpecificId id = new ServerSpecificId(muteMessage.getServer().getId(), muteId); Infraction infraction;
if(infractionId != null) {
infraction = infractionManagementService.loadInfraction(infractionId);
} else {
infraction = null;
}
ServerSpecificId id = new ServerSpecificId(mutedUser.getServerReference().getId(), muteId);
Mute mute = Mute Mute mute = Mute
.builder() .builder()
.mutedUser(mutedUser) .mutedUser(mutedUser)
@@ -41,6 +52,7 @@ public class MuteManagementServiceBean implements MuteManagementService {
.mutingChannel(muteMessage.getChannel()) .mutingChannel(muteMessage.getChannel())
.messageId(muteMessage.getMessageId()) .messageId(muteMessage.getMessageId())
.reason(reason) .reason(reason)
.infraction(infraction)
.triggerKey(triggerKey) .triggerKey(triggerKey)
.muteId(id) .muteId(id)
.muteEnded(false) .muteEnded(false)
@@ -76,6 +88,11 @@ public class MuteManagementServiceBean implements MuteManagementService {
@Override @Override
public Mute getAMuteOf(AUserInAServer userInAServer) { public Mute getAMuteOf(AUserInAServer userInAServer) {
return getAMuteOfOptional(userInAServer).orElseThrow(NoMuteFoundException::new);
}
@Override
public Optional<Mute> getAMuteOfOptional(AUserInAServer userInAServer) {
return muteRepository.findTopByMutedUserAndMuteEndedFalse(userInAServer); return muteRepository.findTopByMutedUserAndMuteEndedFalse(userInAServer);
} }
@@ -84,6 +101,11 @@ public class MuteManagementServiceBean implements MuteManagementService {
return getAMuteOf(userInServerManagementService.loadOrCreateUser(member)); return getAMuteOf(userInServerManagementService.loadOrCreateUser(member));
} }
@Override
public Optional<Mute> getAMuteOfOptional(Member member) {
return getAMuteOfOptional(userInServerManagementService.loadOrCreateUser(member));
}
@Override @Override
public List<Mute> getAllActiveMutesOf(AUserInAServer aUserInAServer) { public List<Mute> getAllActiveMutesOf(AUserInAServer aUserInAServer) {
return muteRepository.findAllByMutedUserAndMuteEndedFalseOrderByMuteId_IdDesc(aUserInAServer); return muteRepository.findAllByMutedUserAndMuteEndedFalseOrderByMuteId_IdDesc(aUserInAServer);

View File

@@ -1,59 +0,0 @@
package dev.sheldan.abstracto.moderation.service.management;
import dev.sheldan.abstracto.core.models.database.ARole;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.moderation.model.database.MuteRole;
import dev.sheldan.abstracto.moderation.repository.MuteRoleRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
@Slf4j
public class MuteRoleManagementServiceBean implements MuteRoleManagementService {
@Autowired
private MuteRoleRepository muteRoleRepository;
@Override
public MuteRole retrieveMuteRoleForServer(AServer server) {
return muteRoleRepository.findByRoleServer(server);
}
@Override
public MuteRole createMuteRoleForServer(AServer server, ARole role) {
log.debug("Creating mute role for server {} to be role {}", server.getId(), role.getId());
MuteRole muteRole = MuteRole
.builder()
.role(role)
.roleServer(server)
.build();
return muteRoleRepository.save(muteRole);
}
@Override
public List<MuteRole> retrieveMuteRolesForServer(AServer server) {
return muteRoleRepository.findAllByRoleServer(server);
}
@Override
public MuteRole setMuteRoleForServer(AServer server, ARole role) {
log.info("Setting muted role for server {} to role {}", server.getId(), role.getId());
if(!muteRoleForServerExists(server)) {
log.debug("Mute role did not exist yet, updating for server {}.", server.getId());
return createMuteRoleForServer(server, role);
} else {
MuteRole existing = retrieveMuteRoleForServer(server);
log.debug("Updating mute role for server {} to be role {} instead.", server.getId(), role.getId());
existing.setRole(role);
return existing;
}
}
@Override
public boolean muteRoleForServerExists(AServer server) {
return muteRoleRepository.existsByRoleServer(server);
}
}

View File

@@ -13,6 +13,7 @@ import org.springframework.stereotype.Component;
import java.time.Duration; import java.time.Duration;
import java.time.Instant; import java.time.Instant;
import java.util.List;
import java.util.Optional; import java.util.Optional;
@Component @Component
@@ -30,7 +31,8 @@ public class ReactionReportManagementServiceBean implements ReactionReportManage
@Override @Override
public Optional<ReactionReport> findRecentReactionReportAboutUser(AUserInAServer aUserInAServer, Duration maxAge) { public Optional<ReactionReport> findRecentReactionReportAboutUser(AUserInAServer aUserInAServer, Duration maxAge) {
Instant maxCreation = Instant.now().minus(maxAge); Instant maxCreation = Instant.now().minus(maxAge);
return repository.findByReportedUserAndCreatedLessThan(aUserInAServer, maxCreation); List<ReactionReport> foundReports = repository.findByReportedUserAndCreatedLessThan(aUserInAServer, maxCreation);
return foundReports.isEmpty() ? Optional.empty() : Optional.of(foundReports.get(0));
} }
@Override @Override

View File

@@ -104,5 +104,10 @@ public class WarnManagementServiceBean implements WarnManagementService {
warnRepository.delete(warning); warnRepository.delete(warning);
} }
@Override
public Optional<Warning> findWarnByInfraction(Long infractionId) {
return warnRepository.findByInfraction_Id(infractionId);
}
} }

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="tables/tables.xml" relativeToChangelogFile="true"/>
<include file="seedData/data.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@@ -0,0 +1,40 @@
<?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="moderationModule" value="(SELECT id FROM module WHERE name = 'moderation')"/>
<property name="infractionsFeature" value="(SELECT id FROM feature WHERE key = 'infractions')"/>
<changeSet author="Sheldan" id="moderation_infraction-commands">
<insert tableName="command">
<column name="name" value="infractions"/>
<column name="module_id" valueComputed="${moderationModule}"/>
<column name="feature_id" valueComputed="${infractionsFeature}"/>
</insert>
<insert tableName="command">
<column name="name" value="editInfraction"/>
<column name="module_id" valueComputed="${moderationModule}"/>
<column name="feature_id" valueComputed="${infractionsFeature}"/>
</insert>
</changeSet>
<changeSet author="Sheldan" id="moderation_setMuteRole-cleanup">
<delete tableName="command_in_server_allowed_role">
<where>command_in_server_id in (select cs.command_in_server_id from command_in_server cs inner join command c on c.id = cs.command_id and c.name = 'setMuteRole') </where>
</delete>
<delete tableName="command_in_server_alias">
<where>command_in_server_id in (select cs.command_in_server_id from command_in_server cs inner join command c on c.id = cs.command_id and c.name = 'setMuteRole') </where>
</delete>
<delete tableName="command_in_server">
<where>command_id=(select id from command where name = 'setMuteRole')</where>
</delete>
<delete tableName="command">
<where>name='setMuteRole'</where>
</delete>
</changeSet>
</databaseChangeLog>

View File

@@ -0,0 +1,24 @@
<?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="moderationFeature" value="(SELECT id FROM feature WHERE key = 'moderation')"/>
<changeSet author="Sheldan" id="moderation_context_command">
<insert tableName="context_command">
<column name="name" value="report_message"/>
<column name="type" value="MESSAGE"/>
<column name="feature_id" valueComputed="${moderationFeature}"/>
</insert>
<insert tableName="context_command">
<column name="name" value="report_message_context"/>
<column name="type" value="MESSAGE"/>
<column name="feature_id" valueComputed="${moderationFeature}"/>
</insert>
</changeSet>
</databaseChangeLog>

View File

@@ -0,0 +1,12 @@
<?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="context_command.xml" relativeToChangelogFile="true"/>
<include file="command.xml" relativeToChangelogFile="true"/>
<include file="feature_mode.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@@ -0,0 +1,16 @@
<?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="delete_manual_unmuting_log_feature_mode">
<delete tableName="feature_mode">
<where>feature_mode='manualUnMuteLogging'</where>
</delete>
</changeSet>
</databaseChangeLog>

View File

@@ -0,0 +1,37 @@
<?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-type-description">
<addColumn tableName="infraction">
<column name="type" type="VARCHAR(32)">
<constraints nullable="false"/>
</column>
<column name="description" type="VARCHAR(2048)">
<constraints nullable="true"/>
</column>
<column name="log_message_id" type="BIGINT">
<constraints nullable="true"/>
</column>
<column name="log_channel_id" type="BIGINT">
<constraints nullable="true"/>
</column>
<column name="infraction_creator_user_in_server_id" type="BIGINT">
<constraints nullable="false"/>
</column>
</addColumn>
<addNotNullConstraint columnName="decayed"
tableName="infraction"
validate="true"/>
<addForeignKeyConstraint baseColumnNames="infraction_creator_user_in_server_id" baseTableName="infraction" constraintName="fk_infraction_creator_user"
deferrable="false" initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION"
referencedColumnNames="user_in_server_id" referencedTableName="user_in_server" validate="true"/>
<addForeignKeyConstraint baseColumnNames="log_channel_id" baseTableName="infraction" constraintName="fk_infraction_log_channel"
deferrable="false" initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION"
referencedColumnNames="id" referencedTableName="channel" validate="true"/>
</changeSet>
</databaseChangeLog>

View File

@@ -0,0 +1,30 @@
<?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_parameter-table">
<createTable tableName="infraction_parameter">
<column name="key" type="VARCHAR(255)">
<constraints nullable="false"/>
</column>
<column name="value" type="VARCHAR(255)">
<constraints nullable="false"/>
</column>
<column name="infraction_id" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="created" type="TIMESTAMP WITHOUT TIME ZONE">
<constraints nullable="true"/>
</column>
<column name="updated" type="TIMESTAMP WITHOUT TIME ZONE"/>
</createTable>
<addPrimaryKey columnNames="infraction_id, key" tableName="infraction_parameter" constraintName="pk_infraction_parameter" validate="true"/>
<addForeignKeyConstraint baseColumnNames="infraction_id" baseTableName="infraction_parameter" constraintName="fk_infraction_parameter_infraction"
deferrable="false" initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION"
referencedColumnNames="id" referencedTableName="infraction" validate="true"/>
</changeSet>
</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="mute-infraction_id">
<addColumn tableName="mute">
<column name="infraction_id" type="BIGINT" />
</addColumn>
<addForeignKeyConstraint baseColumnNames="infraction_id" baseTableName="mute" constraintName="fk_mute_infraction"
deferrable="false" initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION"
referencedColumnNames="id" referencedTableName="infraction" validate="true"/>
</changeSet>
</databaseChangeLog>

View File

@@ -0,0 +1,12 @@
<?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="infraction_parameter.xml" relativeToChangelogFile="true"/>
<include file="mute.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@@ -13,4 +13,5 @@
<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"/> <include file="1.3.9/collection.xml" relativeToChangelogFile="true"/>
<include file="1.3.10/collection.xml" relativeToChangelogFile="true"/> <include file="1.3.10/collection.xml" relativeToChangelogFile="true"/>
<include file="1.4.0/collection.xml" relativeToChangelogFile="true"/>
</databaseChangeLog> </databaseChangeLog>

View File

@@ -49,6 +49,14 @@ abstracto.featureModes.infractionReporting.featureName=infractions
abstracto.featureModes.infractionReporting.mode=infractionReporting abstracto.featureModes.infractionReporting.mode=infractionReporting
abstracto.featureModes.infractionReporting.enabled=true abstracto.featureModes.infractionReporting.enabled=true
abstracto.featureModes.anonymousReportReactions.featureName=reportReactions
abstracto.featureModes.anonymousReportReactions.mode=anonymousReportReactions
abstracto.featureModes.anonymousReportReactions.enabled=false
abstracto.featureModes.singularReportReactions.featureName=reportReactions
abstracto.featureModes.singularReportReactions.mode=singularReportReactions
abstracto.featureModes.singularReportReactions.enabled=false
abstracto.systemConfigs.infractionLvl1.name=infractionLvl1 abstracto.systemConfigs.infractionLvl1.name=infractionLvl1
abstracto.systemConfigs.infractionLvl1.longValue=10 abstracto.systemConfigs.infractionLvl1.longValue=10
@@ -68,7 +76,16 @@ abstracto.systemConfigs.infractionLevels.name=infractionLevels
abstracto.systemConfigs.infractionLevels.longValue=5 abstracto.systemConfigs.infractionLevels.longValue=5
abstracto.systemConfigs.warnInfractionPoints.name=warnInfractionPoints abstracto.systemConfigs.warnInfractionPoints.name=warnInfractionPoints
abstracto.systemConfigs.warnInfractionPoints.longValue=0 abstracto.systemConfigs.warnInfractionPoints.longValue=50
abstracto.systemConfigs.banInfractionPoints.name=banInfractionPoints
abstracto.systemConfigs.banInfractionPoints.longValue=150
abstracto.systemConfigs.kickInfractionPoints.name=kickInfractionPoints
abstracto.systemConfigs.kickInfractionPoints.longValue=20
abstracto.systemConfigs.muteInfractionPoints.name=muteInfractionPoints
abstracto.systemConfigs.muteInfractionPoints.longValue=10
abstracto.featureModes.automaticWarnDecayLogging.featureName=warnDecay abstracto.featureModes.automaticWarnDecayLogging.featureName=warnDecay
abstracto.featureModes.automaticWarnDecayLogging.mode=automaticWarnDecayLogging abstracto.featureModes.automaticWarnDecayLogging.mode=automaticWarnDecayLogging
@@ -78,6 +95,3 @@ abstracto.featureModes.notifyMemberWarningDecays.featureName=warnDecay
abstracto.featureModes.notifyMemberWarningDecays.mode=notifyMemberWarningDecays abstracto.featureModes.notifyMemberWarningDecays.mode=notifyMemberWarningDecays
abstracto.featureModes.notifyMemberWarningDecays.enabled=true abstracto.featureModes.notifyMemberWarningDecays.enabled=true
abstracto.featureModes.manualUnMuteLogging.featureName=muting
abstracto.featureModes.manualUnMuteLogging.mode=manualUnMuteLogging
abstracto.featureModes.manualUnMuteLogging.enabled=true

View File

@@ -1,60 +0,0 @@
package dev.sheldan.abstracto.moderation.command.mute;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.models.database.ARole;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.service.management.RoleManagementService;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.core.test.command.CommandConfigValidator;
import dev.sheldan.abstracto.core.test.command.CommandTestUtilities;
import dev.sheldan.abstracto.moderation.command.SetMuteRole;
import dev.sheldan.abstracto.moderation.service.management.MuteRoleManagementService;
import net.dv8tion.jda.api.entities.Role;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import java.util.Arrays;
import static org.mockito.Mockito.*;
@RunWith(MockitoJUnitRunner.class)
public class SetMuteRoleTest {
@InjectMocks
private SetMuteRole testUnit;
@Mock
private MuteRoleManagementService muteRoleManagementService;
@Mock
private RoleManagementService roleManagementService;
@Mock
private ServerManagementService serverManagementService;
@Test
public void testExecuteCommand() {
Role role = Mockito.mock(Role.class);
Long roleId = 5L;
when(role.getIdLong()).thenReturn(roleId);
ARole aRole = Mockito.mock(ARole.class);
when(roleManagementService.findRole(roleId)).thenReturn(aRole);
CommandContext parameters = CommandTestUtilities.getWithParameters(Arrays.asList(role));
when(role.getGuild()).thenReturn(parameters.getGuild());
AServer server = Mockito.mock(AServer.class);
when(serverManagementService.loadServer(parameters.getGuild())).thenReturn(server);
CommandResult result = testUnit.execute(parameters);
verify(muteRoleManagementService, times(1)).setMuteRoleForServer(server, aRole);
CommandTestUtilities.checkSuccessfulCompletion(result);
}
@Test
public void validateCommand() {
CommandConfigValidator.validateCommandConfiguration(testUnit.getConfiguration());
}
}

View File

@@ -1,69 +0,0 @@
package dev.sheldan.abstracto.moderation.command.mute;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
import dev.sheldan.abstracto.core.test.command.CommandConfigValidator;
import dev.sheldan.abstracto.core.test.command.CommandTestUtilities;
import dev.sheldan.abstracto.moderation.command.UnMute;
import dev.sheldan.abstracto.moderation.exception.NoMuteFoundException;
import dev.sheldan.abstracto.moderation.service.MuteService;
import dev.sheldan.abstracto.moderation.service.management.MuteManagementService;
import net.dv8tion.jda.api.entities.Member;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import java.util.Arrays;
import java.util.concurrent.CompletableFuture;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
public class UnMuteTest {
@InjectMocks
private UnMute testUnit;
@Mock
private MuteService muteService;
@Mock
private MuteManagementService muteManagementService;
@Mock
private Member memberToUnMute;
@Mock
private UserInServerManagementService userInServerManagementService;
@Test
public void testUnMuteCommand() {
CommandContext parameters = CommandTestUtilities.getWithParameters(Arrays.asList(memberToUnMute));
when(memberToUnMute.getGuild()).thenReturn(parameters.getGuild());
AUserInAServer user = Mockito.mock(AUserInAServer.class);
when(userInServerManagementService.loadOrCreateUser(memberToUnMute)).thenReturn(user);
when(muteService.unMuteUser(user)).thenReturn(CompletableFuture.completedFuture(null));
CompletableFuture<CommandResult> result = testUnit.executeAsync(parameters);
CommandTestUtilities.checkSuccessfulCompletionAsync(result);
}
@Test(expected = NoMuteFoundException.class)
public void testUnMuteCommandWithoutExistingMute() {
CommandContext parameters = CommandTestUtilities.getWithParameters(Arrays.asList(memberToUnMute));
when(memberToUnMute.getGuild()).thenReturn(parameters.getGuild());
AUserInAServer user = Mockito.mock(AUserInAServer.class);
when(userInServerManagementService.loadOrCreateUser(memberToUnMute)).thenReturn(user);
when(muteService.unMuteUser(user)).thenThrow(new NoMuteFoundException());
testUnit.executeAsync(parameters);
}
@Test
public void validateCommand() {
CommandConfigValidator.validateCommandConfiguration(testUnit.getConfiguration());
}
}

View File

@@ -1,67 +0,0 @@
package dev.sheldan.abstracto.moderation.listener;
import dev.sheldan.abstracto.core.models.ServerUser;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.models.listener.MemberJoinModel;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
import dev.sheldan.abstracto.moderation.service.MuteService;
import dev.sheldan.abstracto.moderation.service.management.MuteManagementService;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import static org.mockito.Mockito.*;
@RunWith(MockitoJUnitRunner.class)
public class JoinMuteListenerTest {
@InjectMocks
private JoinMuteListener testUnit;
@Mock
private MuteManagementService muteManagementService;
@Mock
private MuteService muteService;
@Mock
private UserInServerManagementService userInServerManagementService;
@Mock
private AUserInAServer joiningUser;
@Mock
private ServerUser serverUser;
@Mock
private MemberJoinModel model;
private static final Long SERVER_ID = 3L;
private static final Long USER_ID = 4L;
@Test
public void testNonMutedUserJoins() {
when(serverUser.getUserId()).thenReturn(USER_ID);
when(userInServerManagementService.loadOrCreateUser(SERVER_ID, USER_ID)).thenReturn(joiningUser);
when(muteManagementService.hasActiveMute(joiningUser)).thenReturn(false);
when(model.getServerId()).thenReturn(SERVER_ID);
when(model.getJoiningUser()).thenReturn(serverUser);
testUnit.execute(model);
verify(muteService, times(0)).applyMuteRole(joiningUser);
}
@Test
public void testMutedUserJoins() {
when(model.getServerId()).thenReturn(SERVER_ID);
when(serverUser.getUserId()).thenReturn(USER_ID);
when(userInServerManagementService.loadOrCreateUser(SERVER_ID, USER_ID)).thenReturn(joiningUser);
when(muteManagementService.hasActiveMute(joiningUser)).thenReturn(true);
when(model.getJoiningUser()).thenReturn(serverUser);
testUnit.execute(model);
verify(muteService, times(1)).applyMuteRole(joiningUser);
}
}

View File

@@ -1,60 +0,0 @@
package dev.sheldan.abstracto.moderation.service;
import dev.sheldan.abstracto.core.service.FeatureModeService;
import dev.sheldan.abstracto.core.service.PostTargetService;
import dev.sheldan.abstracto.moderation.config.posttarget.ModerationPostTarget;
import dev.sheldan.abstracto.moderation.model.template.command.KickLogModel;
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.requests.restaction.AuditableRestAction;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import java.util.concurrent.CompletableFuture;
import static org.mockito.Mockito.*;
@RunWith(MockitoJUnitRunner.class)
public class KickServiceBeanTest {
@InjectMocks
private KickServiceBean testUnit;
@Mock
private TemplateService templateService;
@Mock
private PostTargetService postTargetService;
@Mock
private FeatureModeService featureModeService;
private static final Long SERVER_ID = 1L;
@Test
public void testKickMemberWithLog() {
User user = Mockito.mock(User.class);
Member member = Mockito.mock(Member.class);
when(member.getUser()).thenReturn(user);
when(user.getIdLong()).thenReturn(6L);
Guild mockedGuild = Mockito.mock(Guild.class);
when(mockedGuild.getIdLong()).thenReturn(SERVER_ID);
when(member.getGuild()).thenReturn(mockedGuild);
String reason = "reason";
AuditableRestAction<Void> mockedAction = Mockito.mock(AuditableRestAction.class);
when(mockedGuild.kick(member, reason)).thenReturn(mockedAction);
when(mockedAction.submit()).thenReturn(CompletableFuture.completedFuture(null));
KickLogModel model = Mockito.mock(KickLogModel.class);
when(model.getGuild()).thenReturn(mockedGuild);
testUnit.kickMember(member, reason, model);
verify(postTargetService, times(0)).sendEmbedInPostTarget(any(MessageToSend.class), eq(ModerationPostTarget.KICK_LOG), eq(SERVER_ID));
verify(templateService, times(1)).renderEmbedTemplate(KickServiceBean.KICK_LOG_TEMPLATE, model, SERVER_ID);
}
}

View File

@@ -1,458 +0,0 @@
package dev.sheldan.abstracto.moderation.service;
import dev.sheldan.abstracto.core.models.FullUserInServer;
import dev.sheldan.abstracto.core.models.ServerChannelMessage;
import dev.sheldan.abstracto.core.models.ServerSpecificId;
import dev.sheldan.abstracto.core.models.database.*;
import dev.sheldan.abstracto.core.service.*;
import dev.sheldan.abstracto.core.service.management.ChannelManagementService;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
import dev.sheldan.abstracto.moderation.config.posttarget.MutingPostTarget;
import dev.sheldan.abstracto.moderation.exception.MuteRoleNotSetupException;
import dev.sheldan.abstracto.moderation.exception.NoMuteFoundException;
import dev.sheldan.abstracto.moderation.model.database.Mute;
import dev.sheldan.abstracto.moderation.model.database.MuteRole;
import dev.sheldan.abstracto.moderation.model.template.command.MuteContext;
import dev.sheldan.abstracto.moderation.model.template.command.MuteNotification;
import dev.sheldan.abstracto.moderation.service.management.MuteManagementService;
import dev.sheldan.abstracto.moderation.service.management.MuteRoleManagementService;
import dev.sheldan.abstracto.scheduling.service.SchedulerService;
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.MessageChannel;
import net.dv8tion.jda.api.entities.User;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import java.time.Duration;
import java.time.Instant;
import java.util.Arrays;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ScheduledExecutorService;
import static dev.sheldan.abstracto.moderation.service.MuteServiceBean.MUTE_NOTIFICATION_TEMPLATE;
import static org.mockito.Mockito.*;
@RunWith(MockitoJUnitRunner.class)
public class MuteServiceBeanTest {
@InjectMocks
private MuteServiceBean testUnit;
@Mock
private MuteRoleManagementService muteRoleManagementService;
@Mock
private RoleService roleService;
@Mock
private UserInServerManagementService userInServerManagementService;
@Mock
private SchedulerService schedulerService;
@Mock
private MuteManagementService muteManagementService;
@Mock
private TemplateService templateService;
@Mock
private GuildService guildService;
@Mock
private MemberService memberService;
@Mock
private MessageService messageService;
@Mock
private PostTargetService postTargetService;
@Mock
private MuteServiceBean self;
@Mock
private ScheduledExecutorService executorService;
@Mock
private ChannelManagementService channelManagementService;
@Mock
private AUserInAServer userBeingMuted;
@Mock
private AUserInAServer userMuting;
@Mock
private User jdaUserBeingMuted;
@Mock
private Member memberBeingMuted;
@Mock
private Member memberMuting;
@Mock
private AServer server;
@Mock
private AChannel aChannel;
@Mock
private MessageChannel channel;
@Mock
private ServerChannelMessage cause;
@Mock
private Guild guild;
@Mock
private ARole aRole;
@Mock
private MuteRole muteRole;
@Mock
private MessageToSend messageToSend;
@Mock
private AUser user;
@Mock
private Mute mute;
@Mock
private ServerManagementService serverManagementService;
@Mock
private CounterService counterService;
@Mock
private FeatureModeService featureModeService;
private static final Long CHANNEL_ID = 8L;
private static final String REASON = "reason";
private static final String NOTIFICATION_TEXT = "text";
private static final String TRIGGER = "trigger";
public static final Long MUTE_ID = 6L;
public static final Long SERVER_ID = 7L;
public static final Long USER_MUTING_ID = 4L;
public static final Long USER_BEING_MUTED_ID = 3L;
@Test
public void testMuteUserWithScheduler() {
Instant unMuteDate = longerMute();
when(cause.getServerId()).thenReturn(SERVER_ID);
FullUserInServer mutedUser = Mockito.mock(FullUserInServer.class);
FullUserInServer mutingUser = Mockito.mock(FullUserInServer.class);
setupFullUsers(mutedUser, mutingUser);
when(muteRole.getRole()).thenReturn(aRole);
when(memberBeingMuted.getUser()).thenReturn(jdaUserBeingMuted);
when(muteRoleManagementService.muteRoleForServerExists(server)).thenReturn(true);
when(muteRoleManagementService.retrieveMuteRoleForServer(server)).thenReturn(muteRole);
when(templateService.renderTemplate(eq(MUTE_NOTIFICATION_TEMPLATE), any(MuteNotification.class), eq(SERVER_ID))).thenReturn(NOTIFICATION_TEXT);
when(messageService.sendMessageToUser(jdaUserBeingMuted, NOTIFICATION_TEXT)).thenReturn(CompletableFuture.completedFuture(null));
when(roleService.addRoleToUserAsync(userBeingMuted, aRole)).thenReturn(CompletableFuture.completedFuture(null));
CompletableFuture<Void> future = testUnit.muteUserInServer(mutedUser, mutingUser, REASON, unMuteDate, cause);
future.join();
Assert.assertFalse(future.isCompletedExceptionally());
}
private void setupFullUsers(FullUserInServer mutedUser, FullUserInServer mutingUser) {
when(memberBeingMuted.getGuild()).thenReturn(guild);
when(memberBeingMuted.getUser()).thenReturn(jdaUserBeingMuted);
when(mutedUser.getAUserInAServer()).thenReturn(userBeingMuted);
when(userBeingMuted.getServerReference()).thenReturn(server);
when(mutedUser.getMember()).thenReturn(memberBeingMuted);
when(mutingUser.getMember()).thenReturn(memberMuting);
when(memberBeingMuted.getGuild()).thenReturn(guild);
}
@Test
public void testMuteWithDirectUnMute() {
when(memberBeingMuted.getGuild()).thenReturn(guild);
when(memberBeingMuted.getUser()).thenReturn(jdaUserBeingMuted);
FullUserInServer mutedUser = Mockito.mock(FullUserInServer.class);
FullUserInServer mutingUser = Mockito.mock(FullUserInServer.class);
setupFullUsers(mutedUser, mutingUser);
Instant unMuteDate = shorterMute();
when(cause.getServerId()).thenReturn(SERVER_ID);
when(memberBeingMuted.getGuild()).thenReturn(guild);
when(memberBeingMuted.getUser()).thenReturn(jdaUserBeingMuted);
when(muteRoleManagementService.muteRoleForServerExists(server)).thenReturn(true);
when(muteRoleManagementService.retrieveMuteRoleForServer(server)).thenReturn(muteRole);
when(muteRole.getRole()).thenReturn(aRole);
String notificationText = "text";
when(templateService.renderTemplate(eq(MUTE_NOTIFICATION_TEMPLATE), any(MuteNotification.class), eq(SERVER_ID))).thenReturn(notificationText);
when(messageService.sendMessageToUser(memberBeingMuted.getUser(), notificationText)).thenReturn(CompletableFuture.completedFuture(null));
when(roleService.addRoleToUserAsync(userBeingMuted, muteRole.getRole())).thenReturn(CompletableFuture.completedFuture(null));
CompletableFuture<Void> future = testUnit.muteUserInServer(mutedUser, mutingUser, REASON, unMuteDate, cause);
future.join();
Assert.assertFalse(future.isCompletedExceptionally() );
verifyDirectMute();
}
@Test(expected = MuteRoleNotSetupException.class)
public void testMuteUserWithoutMuteRole() {
FullUserInServer mutedUser = Mockito.mock(FullUserInServer.class);
when(mutedUser.getAUserInAServer()).thenReturn(userBeingMuted);
when(userBeingMuted.getServerReference()).thenReturn(server);
when(muteRoleManagementService.muteRoleForServerExists(server)).thenReturn(false);
FullUserInServer mutingUser = Mockito.mock(FullUserInServer.class);
ServerChannelMessage serverChannelMessage = mock(ServerChannelMessage.class);
testUnit.muteUserInServer(mutedUser, mutingUser, REASON, longerMute(), serverChannelMessage);
}
@Test
public void testCancelUnMuteJob() {
when(mute.getTriggerKey()).thenReturn(TRIGGER);
testUnit.cancelUnMuteJob(mute);
verify(schedulerService, times(1)).stopTrigger(TRIGGER);
}
@Test
public void testCancelNotExistingJob() {
testUnit.cancelUnMuteJob(mute);
verify(schedulerService, times(0)).stopTrigger(anyString());
}
@Test
public void testMuteMember() {
when(userInServerManagementService.loadOrCreateUser(memberBeingMuted)).thenReturn(userBeingMuted);
when(userBeingMuted.getServerReference()).thenReturn(server);
when(userInServerManagementService.loadOrCreateUser(memberMuting)).thenReturn(userMuting);
Instant unMuteDate = shorterMute();
when(cause.getServerId()).thenReturn(SERVER_ID);
when(memberBeingMuted.getGuild()).thenReturn(guild);
when(memberBeingMuted.getUser()).thenReturn(jdaUserBeingMuted);
when(muteRoleManagementService.muteRoleForServerExists(server)).thenReturn(true);
when(muteRoleManagementService.retrieveMuteRoleForServer(server)).thenReturn(muteRole);
String notificationText = "text";
when(templateService.renderTemplate(eq(MUTE_NOTIFICATION_TEMPLATE), any(MuteNotification.class), eq(SERVER_ID))).thenReturn(notificationText);
when(messageService.sendMessageToUser(memberBeingMuted.getUser(), notificationText)).thenReturn(CompletableFuture.completedFuture(null));
when(roleService.addRoleToUserAsync(userBeingMuted, muteRole.getRole())).thenReturn(CompletableFuture.completedFuture(null));
testUnit.muteMember(memberBeingMuted, memberMuting, REASON, unMuteDate, cause);
verifyDirectMute();
}
@Test
public void testMuteMemberWithLog() {
when(userInServerManagementService.loadOrCreateUser(memberBeingMuted)).thenReturn(userBeingMuted);
when(userBeingMuted.getServerReference()).thenReturn(server);
when(userInServerManagementService.loadOrCreateUser(memberMuting)).thenReturn(userMuting);
Instant unMuteDate = shorterMute();
when(memberBeingMuted.getGuild()).thenReturn(guild);
when(memberBeingMuted.getUser()).thenReturn(jdaUserBeingMuted);
when(muteRoleManagementService.muteRoleForServerExists(server)).thenReturn(true);
when(muteRoleManagementService.retrieveMuteRoleForServer(server)).thenReturn(muteRole);
ServerChannelMessage serverChannelMessage = Mockito.mock(ServerChannelMessage.class);
when(serverChannelMessage.getServerId()).thenReturn(SERVER_ID);
MuteContext muteLog = Mockito.mock(MuteContext.class);
when(muteLog.getMutedUser()).thenReturn(memberBeingMuted);
when(muteLog.getMutingUser()).thenReturn(memberMuting);
when(muteLog.getContext()).thenReturn(serverChannelMessage);
when(muteLog.getMuteTargetDate()).thenReturn(unMuteDate);
when(server.getId()).thenReturn(SERVER_ID);
when(serverManagementService.loadOrCreate(SERVER_ID)).thenReturn(server);
String notificationText = "text";
when(templateService.renderTemplate(eq(MUTE_NOTIFICATION_TEMPLATE), any(MuteNotification.class), eq(SERVER_ID))).thenReturn(notificationText);
when(messageService.sendMessageToUser(memberBeingMuted.getUser(), notificationText)).thenReturn(CompletableFuture.completedFuture(null));
when(templateService.renderEmbedTemplate(eq(MuteServiceBean.MUTE_LOG_TEMPLATE), any(MuteContext.class), eq(SERVER_ID))).thenReturn(messageToSend);
when(roleService.addRoleToUserAsync(userBeingMuted, muteRole.getRole())).thenReturn(CompletableFuture.completedFuture(null));
testUnit.muteMemberWithLog(muteLog);
verifyDirectMute();
verify(templateService, times(1)).renderEmbedTemplate(eq(MuteServiceBean.MUTE_LOG_TEMPLATE), any(MuteContext.class), eq(SERVER_ID));
verify(postTargetService, times(1)).sendEmbedInPostTarget(messageToSend, MutingPostTarget.MUTE_LOG, SERVER_ID);
}
@Test
public void testMuteMemberWithoutLog() {
when(userInServerManagementService.loadOrCreateUser(memberBeingMuted)).thenReturn(userBeingMuted);
when(userBeingMuted.getServerReference()).thenReturn(server);
when(userInServerManagementService.loadOrCreateUser(memberMuting)).thenReturn(userMuting);
Instant unMuteDate = shorterMute();
when(memberBeingMuted.getGuild()).thenReturn(guild);
when(memberBeingMuted.getUser()).thenReturn(jdaUserBeingMuted);
when(muteRoleManagementService.muteRoleForServerExists(server)).thenReturn(true);
when(muteRoleManagementService.retrieveMuteRoleForServer(server)).thenReturn(muteRole);
ServerChannelMessage serverChannelMessage = Mockito.mock(ServerChannelMessage.class);
when(serverChannelMessage.getServerId()).thenReturn(SERVER_ID);
MuteContext muteLog = Mockito.mock(MuteContext.class);
when(muteLog.getMutedUser()).thenReturn(memberBeingMuted);
when(muteLog.getMutingUser()).thenReturn(memberMuting);
when(muteLog.getContext()).thenReturn(serverChannelMessage);
when(muteLog.getMuteTargetDate()).thenReturn(unMuteDate);
when(serverManagementService.loadOrCreate(SERVER_ID)).thenReturn(server);
String notificationText = "text";
when(templateService.renderTemplate(eq(MUTE_NOTIFICATION_TEMPLATE), any(MuteNotification.class), eq(SERVER_ID))).thenReturn(notificationText);
when(messageService.sendMessageToUser(memberBeingMuted.getUser(), notificationText)).thenReturn(CompletableFuture.completedFuture(null));
when(roleService.addRoleToUserAsync(userBeingMuted, muteRole.getRole())).thenReturn(CompletableFuture.completedFuture(null));
testUnit.muteMemberWithLog(muteLog);
verifyDirectMute();
verify(postTargetService, times(0)).sendEmbedInPostTarget(messageToSend, MutingPostTarget.MUTE_LOG, SERVER_ID);
}
@Test
public void testUnMuteMemberWhoseMuteEnded() {
when(mute.getMuteEnded()).thenReturn(true);
when(mute.getMutedUser()).thenReturn(userBeingMuted);
when(muteManagementService.hasActiveMute(userBeingMuted)).thenReturn(true);
when(muteManagementService.getAMuteOf(userBeingMuted)).thenReturn(mute);
when(mute.getMuteId()).thenReturn(new ServerSpecificId(SERVER_ID, MUTE_ID));
when(guildService.getGuildById(SERVER_ID)).thenReturn(guild);
testUnit.unMuteUser(userBeingMuted);
verifyNoUnMuteHappened();
}
@Test
public void testEndMute() {
setupUnMuteMocks();
when(mute.getMutedUser()).thenReturn(userBeingMuted);
when(userBeingMuted.getUserReference()).thenReturn(user);
when(mute.getMutingUser()).thenReturn(userMuting);
when(mute.getServer()).thenReturn(server);
when(muteRoleManagementService.retrieveMuteRoleForServer(server)).thenReturn(muteRole);
when(muteRole.getRole()).thenReturn(aRole);
when(muteManagementService.findMuteOptional(MUTE_ID, SERVER_ID)).thenReturn(Optional.of(mute));
when(roleService.removeRoleFromUserAsync(userBeingMuted, aRole)).thenReturn(CompletableFuture.completedFuture(null));
when(memberService.getMemberInServerAsync(userBeingMuted)).thenReturn(CompletableFuture.completedFuture(memberBeingMuted));
when(memberService.getMemberInServerAsync(userMuting)).thenReturn(CompletableFuture.completedFuture(memberMuting));
testUnit.endMute(MUTE_ID, SERVER_ID);
verify(self, times(1)).sendUnmuteLog(eq(MUTE_ID), any(Guild.class), any(CompletableFuture.class), any(CompletableFuture.class));
}
@Test
public void testSendUnmuteLog() {
when(guild.getIdLong()).thenReturn(SERVER_ID);
when(muteManagementService.findMute(MUTE_ID, SERVER_ID)).thenReturn(mute);
when(mute.getMuteId()).thenReturn(new ServerSpecificId(SERVER_ID, MUTE_ID));
when(serverManagementService.loadServer(SERVER_ID)).thenReturn(server);
testUnit.sendUnmuteLog(MUTE_ID, guild, CompletableFuture.completedFuture(memberMuting), CompletableFuture.completedFuture(memberBeingMuted));
verify(self, times(1)).endMuteInDatabase(MUTE_ID, SERVER_ID);
}
@Test(expected = NoMuteFoundException.class)
public void testEndNonExistingMute() {
when(muteManagementService.findMuteOptional(MUTE_ID, SERVER_ID)).thenReturn(Optional.empty());
testUnit.endMute(MUTE_ID, SERVER_ID);
}
@Test
public void testUnMuteMemberInGuild() {
executeUnMuteWithLogTest();
}
@Test
public void testCompletelyUnMuteNotMutedUser() {
when(userBeingMuted.getUserReference()).thenReturn(user);
when(userBeingMuted.getServerReference()).thenReturn(server);
when(muteManagementService.getAllActiveMutesOf(userBeingMuted)).thenReturn(Arrays.asList());
testUnit.completelyUnMuteUser(userBeingMuted);
verify(muteManagementService, times(0)).saveMute(any(Mute.class));
}
@Test
public void testCompletelyUnMuteNotScheduledMuteUser() {
when(userBeingMuted.getUserReference()).thenReturn(user);
when(userBeingMuted.getServerReference()).thenReturn(server);
when(muteManagementService.getAllActiveMutesOf(userBeingMuted)).thenReturn(Arrays.asList(mute));
testUnit.completelyUnMuteUser(userBeingMuted);
verify(muteManagementService, times(1)).saveMute(any(Mute.class));
verify(schedulerService, times(0)).stopTrigger(anyString());
}
@Test
public void testCompletelyUnMuteScheduledMuteUser() {
when(mute.getTriggerKey()).thenReturn(TRIGGER);
when(userBeingMuted.getUserReference()).thenReturn(user);
when(userBeingMuted.getServerReference()).thenReturn(server);
when(muteManagementService.getAllActiveMutesOf(userBeingMuted)).thenReturn(Arrays.asList(mute));
testUnit.completelyUnMuteUser(userBeingMuted);
verify(muteManagementService, times(1)).saveMute(any(Mute.class));
verify(schedulerService, times(1)).stopTrigger(TRIGGER);
}
@Test
public void testCompletelyUnMuteMember() {
when(userBeingMuted.getUserReference()).thenReturn(user);
when(userBeingMuted.getServerReference()).thenReturn(server);
when(mute.getTriggerKey()).thenReturn(TRIGGER);
when(muteManagementService.getAllActiveMutesOf(userBeingMuted)).thenReturn(Arrays.asList(mute));
when(userInServerManagementService.loadOrCreateUser(memberBeingMuted)).thenReturn(userBeingMuted);
testUnit.completelyUnMuteMember(memberBeingMuted);
verify(muteManagementService, times(1)).saveMute(any(Mute.class));
verify(schedulerService, times(1)).stopTrigger(TRIGGER);
}
@Test
public void verifyScheduling() {
Instant unMuteDate = shorterMute();
MuteContext muteLog = Mockito.mock(MuteContext.class);
when(muteLog.getMuteTargetDate()).thenReturn(unMuteDate);
when(muteLog.getMuteId()).thenReturn(MUTE_ID);
ServerChannelMessage serverContext = Mockito.mock(ServerChannelMessage.class);
when(serverContext.getServerId()).thenReturn(SERVER_ID);
when(serverContext.getChannelId()).thenReturn(CHANNEL_ID);
when(channelManagementService.loadChannel(CHANNEL_ID)).thenReturn(aChannel);
when(muteLog.getContext()).thenReturn(serverContext);
testUnit.persistMute(muteLog);
verify(executorService, times(1)).schedule(any(Runnable.class), anyLong(), any());
}
private void verifyNoUnMuteHappened() {
verify(muteManagementService, times(0)).saveMute(any(Mute.class));
verify(roleService, times(0)).removeRoleFromUser(eq(userBeingMuted), any(ARole.class));
verify(postTargetService, times(0)).sendEmbedInPostTarget(any(MessageToSend.class), eq(MutingPostTarget.MUTE_LOG), eq(SERVER_ID));
}
private void executeUnMuteWithLogTest() {
when(userBeingMuted.getUserReference()).thenReturn(user);
when(mute.getMutedUser()).thenReturn(userBeingMuted);
when(mute.getMutingUser()).thenReturn(userMuting);
when(mute.getServer()).thenReturn(server);
when(muteRoleManagementService.retrieveMuteRoleForServer(server)).thenReturn(muteRole);
when(muteRole.getRole()).thenReturn(aRole);
setupUnMuteMocks();
when(roleService.removeRoleFromUserAsync(userBeingMuted, aRole)).thenReturn(CompletableFuture.completedFuture(null));
when(memberService.getMemberInServerAsync(userBeingMuted)).thenReturn(CompletableFuture.completedFuture(memberBeingMuted));
when(memberService.getMemberInServerAsync(userMuting)).thenReturn(CompletableFuture.completedFuture(memberMuting));
testUnit.unMuteUser(userBeingMuted);
}
private void setupUnMuteMocks() {
when(mute.getMuteId()).thenReturn(new ServerSpecificId(SERVER_ID, MUTE_ID));
when(muteManagementService.getAMuteOf(userBeingMuted)).thenReturn(mute);
when(muteManagementService.hasActiveMute(userBeingMuted)).thenReturn(true);
when(muteRoleManagementService.retrieveMuteRoleForServer(server)).thenReturn(muteRole);
when(guildService.getGuildById(server.getId())).thenReturn(guild);
}
private void verifyDirectMute() {
verify(messageService, times(1)).sendMessageToUser(jdaUserBeingMuted, NOTIFICATION_TEXT);
}
private Instant longerMute() {
return Instant.now().plus(Duration.ofHours(1));
}
private Instant shorterMute() {
return Instant.now().plus(Duration.ofSeconds(4));
}
}

View File

@@ -1,270 +0,0 @@
package dev.sheldan.abstracto.moderation.service;
import dev.sheldan.abstracto.core.models.ServerSpecificId;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.AUser;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.models.property.SystemConfigProperty;
import dev.sheldan.abstracto.core.service.*;
import dev.sheldan.abstracto.core.service.management.DefaultConfigManagementService;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.core.test.command.CommandTestUtilities;
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition;
import dev.sheldan.abstracto.moderation.config.feature.WarningDecayFeatureConfig;
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.WarningPostTarget;
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.service.management.WarnManagementService;
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.User;
import org.junit.Assert;
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;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import static dev.sheldan.abstracto.moderation.service.WarnServiceBean.WARNINGS_COUNTER_KEY;
import static dev.sheldan.abstracto.moderation.service.WarnServiceBean.WARN_LOG_TEMPLATE;
import static org.mockito.Mockito.*;
@RunWith(MockitoJUnitRunner.class)
public class WarnServiceBeanTest {
public static final long WARN_ID = 8L;
@InjectMocks
private WarnServiceBean testUnit;
@Mock
private WarnManagementService warnManagementService;
@Mock
private PostTargetService postTargetService;
@Mock
private TemplateService templateService;
@Mock
private MemberService memberService;
@Mock
private ConfigService configService;
@Mock
private WarnServiceBean self;
@Mock
private Member warnedMember;
@Mock
private Member warningMember;
@Mock
private Member secondWarnedMember;
@Mock
private Guild guild;
@Mock
private MessageToSend messageToSend;
@Mock
private User warnedSimpleUser;
@Mock
private WarnContext context;
@Captor
private ArgumentCaptor<WarnDecayLogModel> warnDecayLogModelArgumentCaptor;
@Captor
private ArgumentCaptor<WarnNotification> notificationCaptor;
@Mock
private AServer server;
@Mock
private AUserInAServer warningUser;
@Mock
private AUserInAServer firstWarnedUser;
@Mock
private AUserInAServer secondWarnedUser;
@Mock
private AUser firstAUser;
@Mock
private AUser secondAUser;
@Mock
private AUser thirdAUser;
@Mock
private Warning firstWarning;
@Mock
private Warning secondWarning;
@Mock
private ServerManagementService serverManagementService;
@Mock
private CounterService counterService;
@Mock
private MessageService messageService;
@Mock
private FeatureModeService featureModeService;
@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;
@Test
public void testDecayWarning() {
Instant date = Instant.now();
when(firstWarning.getWarnId()).thenReturn(new ServerSpecificId(SERVER_ID, 4L));
testUnit.decayWarning(firstWarning, date);
verify(firstWarning, times(1)).setDecayed(true);
verify(firstWarning, times(1)).setDecayDate(date);
}
@Test
public void testDecayWarningsForServer() {
setupWarnDecay();
when(featureModeService.featureModeActive(ModerationFeatureDefinition.AUTOMATIC_WARN_DECAY, server, WarnDecayMode.AUTOMATIC_WARN_DECAY_LOG)).thenReturn(true);
testUnit.decayWarningsForServer(server);
verify(self, times(1)).renderAndSendWarnDecayLogs(eq(SERVER_ID), any());
}
@Test
public void testDecayWarningsForServerWithoutLog() {
setupWarnDecay();
when(featureModeService.featureModeActive(ModerationFeatureDefinition.AUTOMATIC_WARN_DECAY, server, WarnDecayMode.AUTOMATIC_WARN_DECAY_LOG)).thenReturn(false);
testUnit.decayWarningsForServer(server);
verify(self, times(0)).renderAndSendWarnDecayLogs(eq(SERVER_ID), any());
}
@Test
public void testDecayAllWarningsForServer() {
setupWarnDecay();
when(featureModeService.featureModeActive(ModerationFeatureDefinition.WARNING, server, WarningMode.WARN_DECAY_LOG)).thenReturn(true);
testUnit.decayAllWarningsForServer(server);
verify(self, times(1)).renderAndSendWarnDecayLogs(eq(SERVER_ID), any());
}
@Test
public void testDecayAllWarningsForServerWithoutLog() {
setupWarnDecay();
when(featureModeService.featureModeActive(ModerationFeatureDefinition.WARNING, server, WarningMode.WARN_DECAY_LOG)).thenReturn(false);
testUnit.decayAllWarningsForServer(server);
verify(self, times(0)).renderAndSendWarnDecayLogs(eq(SERVER_ID), any());
}
@Test
public void testDecayAllWarningsWithoutWarningsWithoutLog() {
List<Warning> warnings = Collections.emptyList();
when(server.getId()).thenReturn(SERVER_ID);
when(warnManagementService.getActiveWarningsInServerOlderThan(eq(server), any(Instant.class))).thenReturn(warnings);
when(featureModeService.featureModeActive(ModerationFeatureDefinition.WARNING, server, WarningMode.WARN_DECAY_LOG)).thenReturn(false);
testUnit.decayAllWarningsForServer(server);
verify(self, times(0)).renderAndSendWarnDecayLogs(eq(SERVER_ID), any());
}
@Test
public void testDecayAllWarningsWithoutWarningsWithLog() {
List<Warning> warnings = Collections.emptyList();
when(server.getId()).thenReturn(SERVER_ID);
when(warnManagementService.getActiveWarningsInServerOlderThan(eq(server), any(Instant.class))).thenReturn(warnings);
when(featureModeService.featureModeActive(ModerationFeatureDefinition.WARNING, server, WarningMode.WARN_DECAY_LOG)).thenReturn(true);
testUnit.decayAllWarningsForServer(server);
verify(self, times(1)).renderAndSendWarnDecayLogs(eq(SERVER_ID), any());
}
@Test
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());
}
private void setupWarnContext() {
when(guild.getIdLong()).thenReturn(SERVER_ID);
when(context.getGuild()).thenReturn(guild);
when(context.getWarnedMember()).thenReturn(warnedMember);
when(context.getMember()).thenReturn(warningMember);
when(counterService.getNextCounterValue(server, WARNINGS_COUNTER_KEY)).thenReturn(WARN_ID);
}
private void setupMocksForWarning() {
setupWarnings();
when(warnedMember.getGuild()).thenReturn(guild);
when(guild.getName()).thenReturn(GUILD_NAME);
when(guild.getIdLong()).thenReturn(SERVER_ID);
when(warnedMember.getUser()).thenReturn(warnedSimpleUser);
when(templateService.renderEmbedTemplate(eq(WARN_LOG_TEMPLATE), warnDecayLogModelArgumentCaptor.capture(), eq(SERVER_ID))).thenReturn(messageToSend);
when(messageService.sendMessageToUser(eq(warnedMember.getUser()), any())).thenReturn(CompletableFuture.completedFuture(null));
when(postTargetService.sendEmbedInPostTarget(messageToSend, WarningPostTarget.WARN_LOG, SERVER_ID)).thenReturn(CommandTestUtilities.messageFutureList());
when(templateService.renderTemplate(eq(WarnServiceBean.WARN_NOTIFICATION_TEMPLATE), notificationCaptor.capture(), eq(SERVER_ID))).thenReturn(NOTIFICATION_TEXT);
when(serverManagementService.loadOrCreate(SERVER_ID)).thenReturn(server);
}
private void setupWarnings() {
when(firstWarning.getWarningUser()).thenReturn(warningUser);
when(secondWarning.getWarningUser()).thenReturn(warningUser);
when(warningUser.getServerReference()).thenReturn(server);
when(warningUser.getUserReference()).thenReturn(thirdAUser);
when(firstWarning.getWarnedUser()).thenReturn(firstWarnedUser);
when(firstWarnedUser.getServerReference()).thenReturn(server);
when(firstWarnedUser.getUserReference()).thenReturn(firstAUser);
when(secondWarning.getWarnedUser()).thenReturn(secondWarnedUser);
when(secondWarnedUser.getServerReference()).thenReturn(server);
when(secondWarnedUser.getUserReference()).thenReturn(secondAUser);
when(firstWarning.getWarnId()).thenReturn(new ServerSpecificId(SERVER_ID, WARN_ID));
when(secondWarning.getWarnId()).thenReturn(new ServerSpecificId(SERVER_ID, 9L));
when(server.getId()).thenReturn(SERVER_ID);
}
private void setupWarnDecay() {
setupWarnings();
SystemConfigProperty defaultDecayDays = Mockito.mock(SystemConfigProperty.class);
Long defaultDayCount = 4L;
when(defaultDecayDays.getLongValue()).thenReturn(defaultDayCount);
when(defaultConfigManagementService.getDefaultConfig(WarningDecayFeatureConfig.DECAY_DAYS_KEY)).thenReturn(defaultDecayDays);
when(configService.getLongValue(WarningDecayFeatureConfig.DECAY_DAYS_KEY, SERVER_ID, defaultDayCount)).thenReturn(5L);
List<Warning> warnings = Arrays.asList(firstWarning, secondWarning);
when(memberService.getMemberInServerAsync(warningUser)).thenReturn(CompletableFuture.completedFuture(warningMember));
when(memberService.getMemberInServerAsync(firstWarnedUser)).thenReturn(CompletableFuture.completedFuture(warnedMember));
when(memberService.getMemberInServerAsync(secondWarnedUser)).thenReturn(CompletableFuture.completedFuture(secondWarnedMember));
when(warnManagementService.getActiveWarningsInServerOlderThan(eq(server), any(Instant.class))).thenReturn(warnings);
}
}

View File

@@ -1,151 +0,0 @@
package dev.sheldan.abstracto.moderation.service.management;
import dev.sheldan.abstracto.core.models.AServerAChannelMessage;
import dev.sheldan.abstracto.core.models.ServerSpecificId;
import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.AUser;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
import dev.sheldan.abstracto.moderation.model.database.Mute;
import dev.sheldan.abstracto.moderation.repository.MuteRepository;
import net.dv8tion.jda.api.entities.Member;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.*;
import org.mockito.junit.MockitoJUnitRunner;
import java.time.Instant;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import static org.mockito.Mockito.*;
@RunWith(MockitoJUnitRunner.class)
public class MuteManagementServiceBeanTest {
@InjectMocks
private MuteManagementServiceBean testUnit;
@Mock
private MuteRepository muteRepository;
@Mock
private UserInServerManagementService userInServerManagementService;
@Captor
private ArgumentCaptor<Mute> muteArgumentCaptor;
private static final Long SERVER_ID = 1L;
private static final Long MUTE_ID = 2L;
@Test
public void testCreateMute() {
AServer server = Mockito.mock(AServer.class);
long messageId = 9L;
AChannel channel = Mockito.mock(AChannel.class);
AUserInAServer mutingUser = Mockito.mock(AUserInAServer.class);
AUserInAServer mutedUser = Mockito.mock(AUserInAServer.class);
AUser user = Mockito.mock(AUser.class);
when(mutedUser.getUserReference()).thenReturn(user);
when(mutedUser.getServerReference()).thenReturn(server);
AUser secondUser = Mockito.mock(AUser.class);
when(mutingUser.getUserReference()).thenReturn(secondUser);
String reason = "reason";
String triggerKey = "key";
Instant unMuteDate = Instant.now();
AServerAChannelMessage muteMessage = Mockito.mock(AServerAChannelMessage.class);
when(muteMessage.getMessageId()).thenReturn(messageId);
when(muteMessage.getServer()).thenReturn(server);
when(muteMessage.getChannel()).thenReturn(channel);
testUnit.createMute(mutedUser, mutingUser, reason, unMuteDate, muteMessage, triggerKey, 8L);
verify(muteRepository, times(1)).save(muteArgumentCaptor.capture());
Mute createdMute = muteArgumentCaptor.getValue();
Assert.assertEquals(reason, createdMute.getReason());
Assert.assertEquals(mutingUser, createdMute.getMutingUser());
Assert.assertEquals(mutedUser, createdMute.getMutedUser());
Assert.assertEquals(server, createdMute.getServer());
Assert.assertFalse(createdMute.getMuteEnded());
Assert.assertEquals(messageId, createdMute.getMessageId().longValue());
Assert.assertEquals(channel, createdMute.getMutingChannel());
Assert.assertEquals(unMuteDate, createdMute.getMuteTargetDate());
}
@Test
public void testFindMute() {
Mute mute = Mockito.mock(Mute.class);
ServerSpecificId muteId = Mockito.mock(ServerSpecificId.class);
when(mute.getMuteId()).thenReturn(muteId);
when(muteId.getId()).thenReturn(MUTE_ID);
when(muteRepository.findByMuteId_IdAndMuteId_ServerId(MUTE_ID, SERVER_ID)).thenReturn(Optional.of(mute));
Optional<Mute> foundMuteOptional = testUnit.findMuteOptional(MUTE_ID, SERVER_ID);
Assert.assertTrue(foundMuteOptional.isPresent());
foundMuteOptional.ifPresent(foundMute -> Assert.assertEquals(MUTE_ID, foundMute.getMuteId().getId()));
}
@Test
public void testFindNonExistingMute() {
when(muteRepository.findByMuteId_IdAndMuteId_ServerId(MUTE_ID, SERVER_ID)).thenReturn(Optional.empty());
Optional<Mute> foundMuteOptional = testUnit.findMuteOptional(MUTE_ID, SERVER_ID);
Assert.assertFalse(foundMuteOptional.isPresent());
}
@Test
public void testSaveMute() {
Mute mute = Mockito.mock(Mute.class);
testUnit.saveMute(mute);
verify(muteRepository, times(1)).save(mute);
}
@Test
public void testGetMuteOfUser() {
AUserInAServer userInAServer = Mockito.mock(AUserInAServer.class);
Mute mute = Mockito.mock(Mute.class);
when(muteRepository.findTopByMutedUserAndMuteEndedFalse(userInAServer)).thenReturn(mute);
Mute aMuteOf = testUnit.getAMuteOf(userInAServer);
Assert.assertEquals(mute, aMuteOf);
}
@Test
public void testGetMuteOfMember() {
AUserInAServer userInAServer = Mockito.mock(AUserInAServer.class);
Mute mute = Mockito.mock(Mute.class);
Member member = Mockito.mock(Member.class);
when(userInServerManagementService.loadOrCreateUser(member)).thenReturn(userInAServer);
when(muteRepository.findTopByMutedUserAndMuteEndedFalse(userInAServer)).thenReturn(mute);
Mute aMuteOf = testUnit.getAMuteOf(member);
Assert.assertEquals(mute, aMuteOf);
}
@Test
public void testGetAllMutesOf() {
AUserInAServer userInAServer = Mockito.mock(AUserInAServer.class);
Mute mute = Mockito.mock(Mute.class);
Mute mute2 = Mockito.mock(Mute.class);
when(muteRepository.findAllByMutedUserAndMuteEndedFalseOrderByMuteId_IdDesc(userInAServer)).thenReturn(Arrays.asList(mute, mute2));
List<Mute> allMutesOf = testUnit.getAllActiveMutesOf(userInAServer);
Assert.assertEquals(2, allMutesOf.size());
Assert.assertEquals(mute, allMutesOf.get(0));
Assert.assertEquals(mute2, allMutesOf.get(1));
}
@Test
public void testHasActiveMute() {
checkExist(true);
}
@Test
public void testHasNoActiveMute() {
checkExist(false);
}
private void checkExist(boolean value) {
AUserInAServer userInAServer = Mockito.mock(AUserInAServer.class);
when(muteRepository.existsByMutedUserAndMuteEndedFalse(userInAServer)).thenReturn(value);
boolean result = testUnit.hasActiveMute(userInAServer);
Assert.assertEquals(value, result);
}
}

View File

@@ -1,95 +0,0 @@
package dev.sheldan.abstracto.moderation.service.management;
import dev.sheldan.abstracto.core.models.database.ARole;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.moderation.model.database.MuteRole;
import dev.sheldan.abstracto.moderation.repository.MuteRoleRepository;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import java.util.Arrays;
import java.util.List;
import static org.mockito.Mockito.*;
@RunWith(MockitoJUnitRunner.class)
public class MuteRoleManagementServiceBeanTest {
@InjectMocks
private MuteRoleManagementServiceBean testUnit;
@Mock
private MuteRoleRepository muteRoleRepository;
@Mock
private AServer server;
@Test
public void testRetrieveMuteRoleForServer() {
MuteRole role = Mockito.mock(MuteRole.class);
when(muteRoleRepository.findByRoleServer(server)).thenReturn(role);
MuteRole muteRole = testUnit.retrieveMuteRoleForServer(server);
Assert.assertEquals(role, muteRole);
}
@Test
public void testServeHasMuteRole() {
when(muteRoleRepository.existsByRoleServer(server)).thenReturn(true);
Assert.assertTrue(testUnit.muteRoleForServerExists(server));
}
@Test
public void testCreateMuteRoleForServer() {
ARole role = Mockito.mock(ARole.class);
ArgumentCaptor<MuteRole> muteRoleCaptor = ArgumentCaptor.forClass(MuteRole.class);
MuteRole savedRole = Mockito.mock(MuteRole.class);
when(muteRoleRepository.save(muteRoleCaptor.capture())).thenReturn(savedRole);
MuteRole muteRoleForServer = testUnit.createMuteRoleForServer(server, role);
Assert.assertEquals(savedRole, muteRoleForServer);
Assert.assertEquals(role, muteRoleCaptor.getValue().getRole());
}
@Test
public void testRetrieveRolesForServer() {
List<MuteRole> existingRoles = Arrays.asList(Mockito.mock(MuteRole.class), Mockito.mock(MuteRole.class));
when(muteRoleRepository.findAllByRoleServer(server)).thenReturn(existingRoles);
List<MuteRole> foundRoles = testUnit.retrieveMuteRolesForServer(server);
Assert.assertEquals(existingRoles.size(), foundRoles.size());
for (int i = 0; i < existingRoles.size(); i++) {
MuteRole existingRole = existingRoles.get(i);
MuteRole foundRole = foundRoles.get(i);
Assert.assertEquals(existingRole, foundRole);
}
}
@Test
public void testSetMuteRoleWithoutPrevious() {
ARole role = Mockito.mock(ARole.class);
when(muteRoleRepository.existsByRoleServer(server)).thenReturn(false);
ArgumentCaptor<MuteRole> muteRoleCaptor = ArgumentCaptor.forClass(MuteRole.class);
MuteRole savedRole = Mockito.mock(MuteRole.class);
when(muteRoleRepository.save(muteRoleCaptor.capture())).thenReturn(savedRole);
MuteRole muteRole = testUnit.setMuteRoleForServer(server, role);
Assert.assertEquals(savedRole, muteRole);
Assert.assertEquals(role, muteRoleCaptor.getValue().getRole());
}
@Test
public void testSetMuteRoleWithPrevious() {
ARole role = Mockito.mock(ARole.class);
when(muteRoleRepository.existsByRoleServer(server)).thenReturn(true);
MuteRole existingRole = Mockito.mock(MuteRole.class);
when(muteRoleRepository.findByRoleServer(server)).thenReturn(existingRole);
testUnit.setMuteRoleForServer(server, role);
verify(existingRole, times(1)).setRole(role);
}
}

View File

@@ -3,6 +3,7 @@ package dev.sheldan.abstracto.moderation.config;
public class ModerationSlashCommandNames { public class ModerationSlashCommandNames {
public static final String MODERATION = "moderation"; public static final String MODERATION = "moderation";
public static final String WARNINGS = "warnings"; public static final String WARNINGS = "warnings";
public static final String INFRACTIONS = "infractions";
public static final String USER_NOTES = "usernotes"; public static final String USER_NOTES = "usernotes";
public static final String MUTE = "mute"; public static final String MUTE = "mute";
public static final String WARN_DECAY = "warningdecay"; public static final String WARN_DECAY = "warningdecay";

View File

@@ -12,6 +12,9 @@ import java.util.List;
@Component @Component
public class ModerationFeatureConfig implements FeatureConfig { public class ModerationFeatureConfig implements FeatureConfig {
public static final String BAN_INFRACTION_POINTS = "banInfractionPoints";
public static final String KICK_INFRACTION_POINTS = "kickInfractionPoints";
@Override @Override
public FeatureDefinition getFeature() { public FeatureDefinition getFeature() {
return ModerationFeatureDefinition.MODERATION; return ModerationFeatureDefinition.MODERATION;
@@ -22,4 +25,8 @@ public class ModerationFeatureConfig implements FeatureConfig {
return Arrays.asList(ModerationPostTarget.BAN_LOG, ModerationPostTarget.KICK_LOG, ModerationPostTarget.UN_BAN_LOG); return Arrays.asList(ModerationPostTarget.BAN_LOG, ModerationPostTarget.KICK_LOG, ModerationPostTarget.UN_BAN_LOG);
} }
@Override
public List<String> getRequiredSystemConfigKeys() {
return Arrays.asList(KICK_INFRACTION_POINTS, BAN_INFRACTION_POINTS);
}
} }

View File

@@ -2,9 +2,7 @@ package dev.sheldan.abstracto.moderation.config.feature;
import dev.sheldan.abstracto.core.config.FeatureConfig; import dev.sheldan.abstracto.core.config.FeatureConfig;
import dev.sheldan.abstracto.core.config.FeatureDefinition; import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.config.FeatureMode;
import dev.sheldan.abstracto.core.config.PostTargetEnum; import dev.sheldan.abstracto.core.config.PostTargetEnum;
import dev.sheldan.abstracto.moderation.config.feature.mode.MutingMode;
import dev.sheldan.abstracto.moderation.config.posttarget.MutingPostTarget; import dev.sheldan.abstracto.moderation.config.posttarget.MutingPostTarget;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@@ -14,6 +12,8 @@ import java.util.List;
@Component @Component
public class MutingFeatureConfig implements FeatureConfig { public class MutingFeatureConfig implements FeatureConfig {
public static final String MUTE_INFRACTION_POINTS = "muteInfractionPoints";
@Override @Override
public FeatureDefinition getFeature() { public FeatureDefinition getFeature() {
return ModerationFeatureDefinition.MUTING; return ModerationFeatureDefinition.MUTING;
@@ -25,7 +25,7 @@ public class MutingFeatureConfig implements FeatureConfig {
} }
@Override @Override
public List<FeatureMode> getAvailableModes() { public List<String> getRequiredSystemConfigKeys() {
return Arrays.asList(MutingMode.MANUAL_UN_MUTE_LOGGING); return Arrays.asList(MUTE_INFRACTION_POINTS);
} }
} }

View File

@@ -2,7 +2,9 @@ package dev.sheldan.abstracto.moderation.config.feature;
import dev.sheldan.abstracto.core.config.FeatureConfig; import dev.sheldan.abstracto.core.config.FeatureConfig;
import dev.sheldan.abstracto.core.config.FeatureDefinition; import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.config.FeatureMode;
import dev.sheldan.abstracto.core.config.PostTargetEnum; import dev.sheldan.abstracto.core.config.PostTargetEnum;
import dev.sheldan.abstracto.moderation.config.feature.mode.ReportReactionMode;
import dev.sheldan.abstracto.moderation.config.posttarget.ReactionReportPostTarget; import dev.sheldan.abstracto.moderation.config.posttarget.ReactionReportPostTarget;
import dev.sheldan.abstracto.moderation.service.ReactionReportService; import dev.sheldan.abstracto.moderation.service.ReactionReportService;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@@ -33,4 +35,9 @@ public class ReportReactionFeatureConfig implements FeatureConfig {
public List<String> getRequiredSystemConfigKeys() { public List<String> getRequiredSystemConfigKeys() {
return Arrays.asList(ReactionReportService.REACTION_REPORT_COOLDOWN); return Arrays.asList(ReactionReportService.REACTION_REPORT_COOLDOWN);
} }
@Override
public List<FeatureMode> getAvailableModes() {
return Arrays.asList(ReportReactionMode.SINGULAR_MESSAGE, ReportReactionMode.ANONYMOUS);
}
} }

Some files were not shown because too many files have changed in this diff Show More