[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,7 +5,8 @@ import dev.sheldan.abstracto.core.command.condition.CommandCondition;
import dev.sheldan.abstracto.core.command.config.*;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.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.interaction.InteractionService;
import dev.sheldan.abstracto.core.service.ChannelService;
@@ -29,6 +30,7 @@ import java.util.Arrays;
import java.util.List;
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;
@Component
@@ -69,10 +71,14 @@ public class Ban extends AbstractConditionableCommand {
Member banningMember = commandContext.getAuthor();
return banService.banUserWithNotification(user, reason, commandContext.getAuthor(), 0)
.thenCompose(banResult -> {
String errorNotification = templateService.renderSimpleTemplate(BAN_NOTIFICATION_NOT_POSSIBLE, guild.getIdLong());
return channelService.sendTextToChannel(errorNotification, message.getChannel())
.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()));
if(banResult == NOTIFICATION_FAILED) {
String errorNotification = templateService.renderSimpleTemplate(BAN_NOTIFICATION_NOT_POSSIBLE, guild.getIdLong());
return channelService.sendTextToChannel(errorNotification, message.getChannel())
.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());
}
@@ -83,14 +89,28 @@ public class Ban extends AbstractConditionableCommand {
if(slashCommandParameterService.hasCommandOptionWithFullType(USER_PARAMETER, event, OptionType.USER)) {
Member member = slashCommandParameterService.getCommandOption(USER_PARAMETER, event, User.class, Member.class);
return banService.banUserWithNotification(member.getUser(), reason, event.getMember(), 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());
} else {
String userIdStr = slashCommandParameterService.getCommandOption(USER_PARAMETER, event, User.class, String.class);
Long userId = Long.parseLong(userIdStr);
return userService.retrieveUserForId(userId)
.thenCompose(user -> banService.banUserWithNotification(user, reason, event.getMember(), 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());
}
}

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.HelpInfo;
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.CommandResult;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
@@ -64,7 +64,7 @@ public class DecayAllWarnings extends AbstractConditionableCommand {
SlashCommandConfig slashCommandConfig = SlashCommandConfig
.builder()
.enabled(true)
.rootCommandName(ModerationSlashCommandNames.MUTE)
.rootCommandName(ModerationSlashCommandNames.WARN_DECAY)
.commandName(DECAY_ALL_WARNINGS_COMMAND)
.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.HelpInfo;
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.CommandResult;
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.execution.CommandContext;
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.interaction.InteractionService;
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.execution.CommandContext;
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.interaction.InteractionService;
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.execution.CommandContext;
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.exception.EntityGuildMismatchException;
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.execution.CommandContext;
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.exception.EntityGuildMismatchException;
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.utils.ParseUtils;
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.service.MuteService;
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.events.interaction.command.SlashCommandInteractionEvent;
import org.springframework.beans.factory.annotation.Autowired;
@@ -59,29 +58,19 @@ public class Mute extends AbstractConditionableCommand {
List<Object> parameters = commandContext.getParameters().getParameters();
Member member = (Member) parameters.get(0);
Guild guild = commandContext.getGuild();
GuildMessageChannel channel = commandContext.getChannel();
if(!member.getGuild().equals(guild)) {
throw new EntityGuildMismatchException();
}
Duration duration = (Duration) parameters.get(1);
String defaultReason = templateService.renderSimpleTemplate(MUTE_DEFAULT_REASON_TEMPLATE, guild.getIdLong());
String reason = parameters.size() == 3 ? (String) parameters.get(2) : defaultReason;
ServerChannelMessage context = ServerChannelMessage
.builder()
.serverId(guild.getIdLong())
.channelId(channel.getIdLong())
.messageId(commandContext.getMessage().getIdLong())
.build();
MuteContext muteLogModel = MuteContext
.builder()
.muteDate(Instant.now())
.muteTargetDate(Instant.now().plus(duration))
.mutedUser(member)
.channelId(commandContext.getChannel().getIdLong())
.reason(reason)
.contextChannel(channel)
.message(commandContext.getMessage())
.mutingUser(commandContext.getAuthor())
.context(context)
.build();
return muteService.muteMemberWithLog(muteLogModel)
.thenApply(aVoid -> CommandResult.fromSuccess());
@@ -90,7 +79,6 @@ public class Mute extends AbstractConditionableCommand {
@Override
public CompletableFuture<CommandResult> executeSlash(SlashCommandInteractionEvent event) {
Guild guild = event.getGuild();
GuildMessageChannel channel = event.getGuildChannel();
Member targetMember = slashCommandParameterService.getCommandOption(USER_PARAMETER, event, Member.class);
String durationStr = slashCommandParameterService.getCommandOption(DURATION_PARAMETER, event, Duration.class, String.class);
Duration duration = ParseUtils.parseDuration(durationStr);
@@ -100,20 +88,13 @@ public class Mute extends AbstractConditionableCommand {
} else {
reason = templateService.renderSimpleTemplate(MUTE_DEFAULT_REASON_TEMPLATE, guild.getIdLong());
}
ServerChannelMessage context = ServerChannelMessage
.builder()
.serverId(guild.getIdLong())
.channelId(channel.getIdLong())
.build();
MuteContext muteLogModel = MuteContext
.builder()
.muteDate(Instant.now())
.muteTargetDate(Instant.now().plus(duration))
.mutedUser(targetMember)
.reason(reason)
.contextChannel(channel)
.channelId(event.getChannel().getIdLong())
.mutingUser(event.getMember())
.context(context)
.build();
return muteService.muteMemberWithLog(muteLogModel)
.thenCompose(unused -> interactionService.replyEmbed(MUTE_RESPONSE, event))
@@ -146,7 +127,11 @@ public class Mute extends AbstractConditionableCommand {
.build();
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
.builder()
.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.HelpInfo;
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.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.exception.EntityGuildMismatchException;
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.HelpInfo;
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.CommandResult;
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.execution.CommandContext;
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.exception.EntityGuildMismatchException;
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.HelpInfo;
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.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.exception.EntityGuildMismatchException;
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.HelpInfo;
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.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.interaction.InteractionService;
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.HelpInfo;
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.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.exception.EntityGuildMismatchException;
import dev.sheldan.abstracto.core.interaction.InteractionService;
@@ -53,7 +53,7 @@ public class UnMute extends AbstractConditionableCommand {
throw new EntityGuildMismatchException();
}
AUserInAServer userToUnMute = userInServerManagementService.loadOrCreateUser(member);
return muteService.unMuteUser(userToUnMute).thenApply(aVoid ->
return muteService.unMuteUser(userToUnMute, commandContext.getAuthor()).thenApply(aVoid ->
CommandResult.fromSuccess()
);
}
@@ -65,7 +65,7 @@ public class UnMute extends AbstractConditionableCommand {
throw new EntityGuildMismatchException();
}
AUserInAServer userToUnMute = userInServerManagementService.loadOrCreateUser(targetMember);
return muteService.unMuteUser(userToUnMute)
return muteService.unMuteUser(userToUnMute, event.getMember())
.thenCompose(unused -> interactionService.replyEmbed(UN_MUTE_RESPONSE, event))
.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.HelpInfo;
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.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.exception.EntityGuildMismatchException;
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.HelpInfo;
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.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.exception.EntityGuildMismatchException;
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.execution.CommandContext;
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.exception.EntityGuildMismatchException;
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.HelpInfo;
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.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.exception.EntityGuildMismatchException;
import dev.sheldan.abstracto.core.interaction.InteractionService;

View File

@@ -21,4 +21,9 @@ public class ModerationListenerConfig {
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.template.display.MemberDisplay;
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.moderation.model.database.Mute;
import dev.sheldan.abstracto.moderation.model.template.command.MuteEntry;
@@ -25,9 +24,6 @@ public class MuteEntryConverter {
@Autowired
private MemberService memberService;
@Autowired
private UserInServerManagementService userInServerManagementService;
@Autowired
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.models.database.AUserInAServer;
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.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.management.MuteManagementService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Optional;
@Component
@Slf4j
public class JoinMuteListener implements AsyncJoinListener {
@@ -21,17 +25,15 @@ public class JoinMuteListener implements AsyncJoinListener {
private MuteManagementService muteManagementService;
@Autowired
private MuteService muteService;
@Autowired
private UserInServerManagementService userInServerManagementService;
private MemberService memberService;
@Override
public DefaultListenerResult execute(MemberJoinModel model) {
AUserInAServer aUserInAServer = userInServerManagementService.loadOrCreateUser(model.getServerId(), model.getJoiningUser().getUserId());
if(muteManagementService.hasActiveMute(aUserInAServer)) {
Optional<Mute> optionalMute = muteManagementService.getAMuteOfOptional(model.getMember());
if(optionalMute.isPresent()) {
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;
}

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())
.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()));
log.info("User {} in server {} reacted to report a message {} from channel {}.",
model.getUserReacting().getUserId(), model.getServerId(), cachedMessage.getMessageId(), cachedMessage.getChannelId());
reactionReportService.createReactionReport(cachedMessage, model.getUserReacting()).exceptionally(throwable -> {
if(!reactionReportService.allowedToReport(model.getUserReacting())) {
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);
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.async.jda.AsyncUserBannedListener;
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.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
@@ -27,9 +26,6 @@ import java.util.concurrent.CompletableFuture;
@Slf4j
public class UserBannedListener implements AsyncUserBannedListener {
@Autowired
private FeatureModeService featureModeService;
@Autowired
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;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.moderation.model.database.Infraction;
import org.springframework.data.jpa.repository.JpaRepository;
@@ -10,4 +11,6 @@ import java.util.List;
@Repository
public interface InfractionRepository extends JpaRepository<Infraction, Long> {
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> {
boolean existsByMutedUserAndMuteEndedFalse(AUserInAServer userInAServer);
Mute findTopByMutedUserAndMuteEndedFalse(AUserInAServer userInAServer);
Optional<Mute> findTopByMutedUserAndMuteEndedFalse(AUserInAServer userInAServer);
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 java.time.Instant;
import java.util.List;
import java.util.Optional;
@Repository
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.AUserInAServer;
import dev.sheldan.abstracto.moderation.model.database.Warning;
import org.jetbrains.annotations.NotNull;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@@ -27,6 +26,6 @@ public interface WarnRepository extends JpaRepository<Warning, ServerSpecificId>
List<Warning> findByWarnedUser(AUserInAServer aUserInAServer);
@NotNull
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;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
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.moderation.config.feature.ModerationFeatureConfig;
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureDefinition;
import dev.sheldan.abstracto.moderation.config.posttarget.ModerationPostTarget;
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
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.BanNotificationModel;
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 org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.time.Duration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
@Component
@@ -37,6 +46,18 @@ public class BanServiceBean implements BanService {
@Autowired
private MessageService messageService;
@Autowired
private FeatureFlagService featureFlagService;
@Autowired
private ConfigService configService;
@Autowired
private UserInServerManagementService userInServerManagementService;
@Autowired
private InfractionService infractionService;
@Override
public CompletableFuture<BanResult> banUserWithNotification(User user, String reason, Member banningMember, Integer deletionDays) {
BanLog banLog = BanLog
@@ -47,18 +68,31 @@ public class BanServiceBean implements BanService {
.reason(reason)
.build();
Guild guild = banningMember.getGuild();
CompletableFuture<BanResult> returningFuture = new CompletableFuture<>();
sendBanNotification(user, reason, guild)
.whenComplete((unused, throwable) -> banUser(guild, user, deletionDays, reason)
.thenCompose(unused1 -> sendBanLogMessage(banLog, guild.getIdLong()))
.thenAccept(unused1 -> {
if(throwable != null) {
returningFuture.complete(BanResult.NOTIFICATION_FAILED);
} else {
returningFuture.complete(BanResult.SUCCESSFUL);
}
}));
return returningFuture;
BanResult[] result = {BanResult.SUCCESSFUL};
return sendBanNotification(user, reason, guild)
.exceptionally(throwable -> {
result[0] = BanResult.NOTIFICATION_FAILED;
return null;
})
.thenCompose(unused -> banUser(guild, user, deletionDays, reason))
.thenCompose(unused -> sendBanLogMessage(banLog, guild.getIdLong()))
.thenAccept(banLogMessage -> self.evaluateAndStoreInfraction(user, guild, reason, banningMember, banLogMessage, deletionDays))
.thenApply(unused -> result[0]);
}
@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) {
@@ -102,10 +136,15 @@ public class BanServiceBean implements BanService {
.thenCompose(unused -> unbanUser(guild, user));
}
public CompletableFuture<Void> sendBanLogMessage(BanLog banLog, Long guildId) {
MessageToSend banLogMessage = templateService.renderEmbedTemplate(BAN_LOG_TEMPLATE, banLog, guildId);
public CompletableFuture<Message> sendBanLogMessage(BanLog banLog, Long guildId) {
MessageToSend banLogMessage = renderBanMessage(banLog, 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) {

View File

@@ -1,30 +1,36 @@
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.database.AUserInAServer;
import dev.sheldan.abstracto.core.models.template.display.MemberDisplay;
import dev.sheldan.abstracto.core.service.ConfigService;
import dev.sheldan.abstracto.core.service.FeatureFlagService;
import dev.sheldan.abstracto.core.service.PostTargetService;
import dev.sheldan.abstracto.core.service.management.ChannelManagementService;
import dev.sheldan.abstracto.core.service.management.ConfigManagementService;
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.abstracto.moderation.config.feature.InfractionFeatureConfig;
import dev.sheldan.abstracto.moderation.config.posttarget.InfractionPostTarget;
import dev.sheldan.abstracto.moderation.listener.InfractionUpdatedDescriptionListener;
import dev.sheldan.abstracto.moderation.listener.manager.InfractionLevelChangedListenerManager;
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.service.management.InfractionManagementService;
import dev.sheldan.abstracto.moderation.service.management.InfractionParameterManagementService;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Message;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.util.Pair;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
@@ -35,9 +41,6 @@ public class InfractionServiceBean implements InfractionService {
@Autowired
private InfractionManagementService infractionManagementService;
@Autowired
private FeatureFlagService featureFlagService;
@Autowired
private ConfigService configService;
@@ -56,6 +59,15 @@ public class InfractionServiceBean implements InfractionService {
@Autowired
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";
@Override
@@ -74,15 +86,31 @@ public class InfractionServiceBean implements InfractionService {
}
@Override
public CompletableFuture<Infraction> createInfractionWithNotification(AUserInAServer aUserInAServer, Long points) {
Infraction createdInfraction = infractionManagementService.createInfraction(aUserInAServer, 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(target, points, type, description, creator, message);
parameters.forEach((key, value) -> infractionParameterManagementService.createInfractionParameter(createdInfraction, key, value));
Long infractionId = createdInfraction.getId();
return createInfractionNotification(aUserInAServer, points)
.thenApply(aBoolean -> self.reloadInfraction(infractionId));
return createInfractionNotification(target, points, type, description)
.thenApply(avoid -> self.reloadInfraction(infractionId));
}
@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 currentPoints = getActiveInfractionPointsForUser(aUserInAServer);
Long newPoints = currentPoints + points;
@@ -95,6 +123,8 @@ public class InfractionServiceBean implements InfractionService {
.member(MemberDisplay.fromAUserInAServer(aUserInAServer))
.newLevel(newLevel)
.oldLevel(oldLevel)
.type(type)
.description(description)
.oldPoints(currentPoints)
.newPoints(newPoints)
.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
public Infraction reloadInfraction(Long infractionId) {
return infractionManagementService.loadInfraction(infractionId);

View File

@@ -1,18 +1,27 @@
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.management.UserInServerManagementService;
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.model.database.Infraction;
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 lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@Component
@@ -27,18 +36,47 @@ public class KickServiceBean implements KickService {
@Autowired
private PostTargetService postTargetService;
@Autowired
private FeatureFlagService featureFlagService;
@Autowired
private ConfigService configService;
@Autowired
private UserInServerManagementService userInServerManagementService;
@Autowired
private InfractionService infractionService;
@Autowired
private KickServiceBean self;
@Override
public CompletableFuture<Void> kickMember(Member member, String reason, KickLogModel kickLogModel) {
Guild guild = member.getGuild();
log.info("Kicking user {} from guild {}", member.getUser().getIdLong(), guild.getIdLong());
CompletableFuture<Void> kickFuture = guild.kick(member, reason).submit();
CompletableFuture<Void> logFuture = this.sendKickLog(kickLogModel);
return CompletableFuture.allOf(kickFuture, logFuture);
CompletableFuture<Message> logFuture = this.sendKickLog(kickLogModel);
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());
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.FullUserInServer;
import dev.sheldan.abstracto.core.models.ServerChannelMessage;
import dev.sheldan.abstracto.core.models.ServerUser;
import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
@@ -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.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.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.exception.MuteRoleNotSetupException;
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.MuteRole;
import dev.sheldan.abstracto.moderation.model.template.command.MuteContext;
import dev.sheldan.abstracto.moderation.model.template.command.MuteListenerModel;
import dev.sheldan.abstracto.moderation.model.template.command.MuteNotification;
import dev.sheldan.abstracto.moderation.model.template.command.UnMuteLog;
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.service.SchedulerService;
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
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.Qualifier;
import org.springframework.stereotype.Component;
@@ -45,12 +47,6 @@ import java.util.concurrent.TimeUnit;
@Slf4j
public class MuteServiceBean implements MuteService {
@Autowired
private MuteRoleManagementService muteRoleManagementService;
@Autowired
private RoleService roleService;
@Autowired
private UserInServerManagementService userInServerManagementService;
@@ -91,51 +87,38 @@ public class MuteServiceBean implements MuteService {
@Autowired
private ServerManagementService serverManagementService;
@Autowired
private FeatureModeService featureModeService;
@Autowired
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 UN_MUTE_LOG_TEMPLATE = "unmute_log";
public static final String MUTE_NOTIFICATION_TEMPLATE = "mute_notification";
public static final String MUTE_COUNTER_KEY = "MUTES";
@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
.builder()
.aUserInAServer(userInServerManagementService.loadOrCreateUser(memberToMute))
.member(memberToMute)
.build();
FullUserInServer mutingUser = FullUserInServer
.builder()
.aUserInAServer(userInServerManagementService.loadOrCreateUser(mutingMember))
.member(mutingMember)
.build();
return muteUserInServer(mutedUser, mutingUser, reason, unMuteDate, message);
return muteUserInServer(mutedUser, reason, unMuteDate, channelId);
}
@Override
public CompletableFuture<Void> muteUserInServer(FullUserInServer userBeingMuted, FullUserInServer userMuting, String reason, Instant unMuteDate, ServerChannelMessage message) {
AServer serverBeingMutedIn = userBeingMuted.getAUserInAServer().getServerReference();
if(!muteRoleManagementService.muteRoleForServerExists(serverBeingMutedIn)) {
log.error("Mute role for server {} has not been setup.", serverBeingMutedIn.getId());
throw new MuteRoleNotSetupException();
}
public CompletableFuture<Void> muteUserInServer(FullUserInServer userBeingMuted, String reason, Instant unMuteDate, Long channelId) {
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<>();
AUserInAServer userInServerBeingMuted = userBeingMuted.getAUserInAServer();
futures.add(applyMuteRole(userInServerBeingMuted));
futures.add(memberService.timeoutUser(userBeingMuted.getMember(), unMuteDate));
Guild guild = memberBeingMuted.getGuild();
if(memberBeingMuted.getVoiceState() != null && memberBeingMuted.getVoiceState().getChannel() != null) {
futures.add(guild.kickVoiceMember(memberBeingMuted).submit());
@@ -146,17 +129,18 @@ public class MuteServiceBean implements MuteService {
.reason(reason)
.serverName(guild.getName())
.build();
futures.add(sendMuteNotification(message, memberBeingMuted, muteNotification));
futures.add(sendMuteNotification(memberBeingMuted, muteNotification, channelId));
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.");
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);
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) -> {
notificationFuture.complete(null);
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;
}
private void createMuteObject(MuteContext muteContext, String triggerKey) {
AChannel channel = channelManagementService.loadChannel(muteContext.getContext().getChannelId());
private void createMuteObject(MuteContext muteContext, String triggerKey, Long infractionId) {
AChannel channel = channelManagementService.loadChannel(muteContext.getChannelId());
AServerAChannelMessage origin = AServerAChannelMessage
.builder()
.channel(channel)
.server(channel.getServer())
.messageId(muteContext.getContext().getMessageId())
.build();
AUserInAServer userInServerBeingMuted = userInServerManagementService.loadOrCreateUser(muteContext.getMutedUser());
AUserInAServer userInServerMuting = userInServerManagementService.loadOrCreateUser(muteContext.getMutingUser());
muteManagementService.createMute(userInServerBeingMuted, userInServerMuting, muteContext.getReason(), muteContext.getMuteTargetDate(), origin, triggerKey, muteContext.getMuteId());
}
@Override
public CompletableFuture<Void> applyMuteRole(AUserInAServer aUserInAServer) {
MuteRole muteRole = muteRoleManagementService.retrieveMuteRoleForServer(aUserInAServer.getServerReference());
return roleService.addRoleToUserAsync(aUserInAServer, muteRole.getRole());
muteManagementService.createMute(userInServerBeingMuted, userInServerMuting, muteContext.getReason(), muteContext.getMuteTargetDate(), origin, triggerKey, muteContext.getMuteId(), infractionId);
}
@Override
@@ -217,6 +194,7 @@ public class MuteServiceBean implements MuteService {
@Override
public void cancelUnMuteJob(Mute mute) {
if(mute.getTriggerKey() != null) {
log.info("Cancelling un-mute job for mute {} in server {}.", mute.getMuteId().getId(), mute.getServer().getId());
schedulerService.stopTrigger(mute.getTriggerKey());
}
}
@@ -224,107 +202,116 @@ public class MuteServiceBean implements MuteService {
@Override
public CompletableFuture<Void> muteMemberWithLog(MuteContext context) {
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);
context.setMuteId(nextCounterValue);
CompletableFuture<Void> mutingFuture = muteMember(context.getMutedUser(), context.getMutingUser(), context.getReason(), context.getMuteTargetDate(), context.getContext());
CompletableFuture<Void> muteLogFuture = sendMuteLog(context, server);
return CompletableFuture.allOf(mutingFuture, muteLogFuture).thenAccept(aVoid ->
self.persistMute(context)
);
return muteMember(context.getMutedUser(), context.getReason(), context.getMuteTargetDate(), context.getChannelId())
.thenCompose(unused -> self.sendMuteLog(context))
.thenCompose(logMessage -> self.evaluateAndStoreInfraction(context, logMessage))
.thenAccept(infractionId -> self.persistMute(context, infractionId));
}
@Transactional
public void persistMute(MuteContext context) {
String triggerKey = startUnMuteJobFor(context.getMuteTargetDate(), context.getMuteId(), context.getContext().getServerId());
createMuteObject(context, triggerKey);
public CompletableFuture<Long> evaluateAndStoreInfraction(MuteContext context, Message logMessage) {
Guild guild = context.getMutedUser().getGuild();
if(featureFlagService.getFeatureFlagValue(ModerationFeatureDefinition.INFRACTIONS, guild.getIdLong())) {
Long infractionPoints = configService.getLongValueOrConfigDefault(MutingFeatureConfig.MUTE_INFRACTION_POINTS, guild.getIdLong());
AUserInAServer mutedUser = userInServerManagementService.loadOrCreateUser(context.getMutedUser());
AUserInAServer mutingUser = userInServerManagementService.loadOrCreateUser(context.getMutingUser());
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.");
MessageToSend message = templateService.renderEmbedTemplate(MUTE_LOG_TEMPLATE, muteLogModel, server.getId());
return FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(message, MutingPostTarget.MUTE_LOG, muteLogModel.getContext().getServerId()));
MessageToSend message = templateService.renderEmbedTemplate(MUTE_LOG_TEMPLATE, model, muteLogModel.getMutedUser().getIdLong());
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) {
log.debug("Sending unMute log for mute {} to the mute posttarget in server {}", muteLogModel.getMute().getMuteId().getId(), server.getId());
MessageToSend message = templateService.renderEmbedTemplate(UN_MUTE_LOG_TEMPLATE, muteLogModel, server.getId());
MuteListenerModel model = MuteListenerModel
.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()));
}
@Override
@Transactional
public CompletableFuture<Void> unMuteUser(AUserInAServer aUserInAServer) {
if(!muteManagementService.hasActiveMute(aUserInAServer)) {
throw new NoMuteFoundException();
}
Mute mute = muteManagementService.getAMuteOf(aUserInAServer);
Long muteId = mute.getMuteId().getId();
CompletableFuture<Member> mutingMemberFuture = memberService.getMemberInServerAsync(mute.getMutingUser());
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());
public CompletableFuture<Void> unMuteUser(AUserInAServer userToUnmute, Member unMutingMember) {
boolean muteActive = muteManagementService.hasActiveMute(userToUnmute);
if(!muteActive) {
CompletableFuture<Member> unMutedMemberFuture = memberService.retrieveMemberInServer(ServerUser.fromAUserInAServer(userToUnmute));
return unMutedMemberFuture
.thenCompose(member -> memberService.removeTimeout(member))
.thenCompose(unused -> self.sendUnmuteLog(null, unMutingMember.getGuild(), unMutedMemberFuture.join(), unMutingMember));
} else {
completableFuture = CompletableFuture.completedFuture(null);
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());
Mute mute = muteManagementService.getAMuteOf(userToUnmute);
return endMute(mute);
}
return completableFuture;
}
@Override
public CompletableFuture<Void> endMute(Mute mute, Boolean sendNotification) {
public CompletableFuture<Void> endMute(Mute mute) {
if(mute.getMuteEnded()) {
log.info("Mute {} in server {} has already ended. Not unmuting.", mute.getMuteId().getId(), mute.getMuteId().getServerId());
return CompletableFuture.completedFuture(null);
}
Long muteId = mute.getMuteId().getId();
Long serverId = mute.getMuteId().getServerId();
Guild guild = guildService.getGuildById(mute.getMuteId().getServerId());
AServer mutingServer = mute.getServer();
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<Void> finalFuture = new CompletableFuture<>();
CompletableFuture.allOf(mutingMemberFuture, mutedMemberFuture, roleRemovalFuture, mutingMemberFuture, mutedMemberFuture).handle((aVoid, throwable) -> {
if(sendNotification) {
self.sendUnmuteLog(muteId, guild, mutingMemberFuture, mutedMemberFuture).thenAccept(aVoid1 ->
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;
CompletableFuture<Member> mutingMemberFuture = memberService.getMemberInServerAsync(mute.getMutingUser());
return CompletableFuture.allOf(mutedMemberFuture, mutingMemberFuture)
.thenAccept(member -> memberService.removeTimeout(mutedMemberFuture.join()))
.thenCompose(unused -> self.sendUnmuteLog(muteId, guild, mutingMemberFuture, mutedMemberFuture));
}
@Transactional
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 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
.builder()
.mute(mute)
@@ -333,9 +320,11 @@ public class MuteServiceBean implements MuteService {
.guild(guild)
.build();
CompletableFuture<Void> notificationFuture = sendUnMuteLogMessage(unMuteLog, mutingServer);
return CompletableFuture.allOf(notificationFuture).thenAccept(aVoid ->
self.endMuteInDatabase(muteId, guild.getIdLong())
);
return CompletableFuture.allOf(notificationFuture).thenAccept(aVoid -> {
if(muteId != null) {
self.endMuteInDatabase(muteId, guild.getIdLong());
}
});
}
@Transactional
@@ -344,7 +333,6 @@ public class MuteServiceBean implements MuteService {
muteOptional.ifPresent(mute ->
completelyUnMuteUser(mute.getMutedUser())
);
}
@Override
@@ -353,7 +341,7 @@ public class MuteServiceBean implements MuteService {
log.info("UnMuting the mute {} in server {}", muteId, serverId);
Optional<Mute> muteOptional = muteManagementService.findMuteOptional(muteId, serverId);
if(muteOptional.isPresent()) {
return endMute(muteOptional.get(), true);
return endMute(muteOptional.get());
} else {
throw new NoMuteFoundException();
}
@@ -375,10 +363,4 @@ public class MuteServiceBean implements MuteService {
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.cache.CachedMessage;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.service.ConfigService;
import dev.sheldan.abstracto.core.service.PostTargetService;
import dev.sheldan.abstracto.core.service.*;
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.feature.ModerationFeatureDefinition;
import dev.sheldan.abstracto.moderation.config.feature.mode.ReportReactionMode;
import dev.sheldan.abstracto.moderation.config.posttarget.ReactionReportPostTarget;
import dev.sheldan.abstracto.moderation.listener.manager.ReportMessageCreatedListenerManager;
import dev.sheldan.abstracto.moderation.model.database.ModerationUser;
import dev.sheldan.abstracto.moderation.model.database.ReactionReport;
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 net.dv8tion.jda.api.entities.GuildMessageChannel;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.TextChannel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
@@ -59,46 +59,60 @@ public class ReactionReportServiceBean implements ReactionReportService {
@Autowired
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";
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
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 reportingUser = userInServerManagementService.loadOrCreateUser(reporter);
Optional<ModerationUser> moderationUserOptional = moderationUserManagementService.findModerationUser(reportingUser);
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);
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);
if(!recentReportOptional.isPresent()) {
ReportReactionNotificationModel model = ReportReactionNotificationModel
.builder()
.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 {
boolean singularMessage = featureModeService.featureModeActive(ModerationFeatureDefinition.REPORT_REACTIONS, serverId, ReportReactionMode.SINGULAR_MESSAGE);
boolean anonym = featureModeService.featureModeActive(ModerationFeatureDefinition.REPORT_REACTIONS, reporter.getServerId(), ReportReactionMode.ANONYMOUS);
if(recentReportOptional.isPresent() && singularMessage) {
ReactionReport report = recentReportOptional.get();
log.info("Report is already present in channel {} with message {}. Updating field.", report.getReportChannel().getId(), report.getReportMessageId());
report.setReportCount(report.getReportCount() + 1);
GuildMessageChannel reportTextChannel = channelService.getMessageChannelFromServer(serverId, report.getReportChannel().getId());
return channelService.editFieldValueInMessage(reportTextChannel, report.getReportMessageId(), 0, report.getReportCount().toString())
.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
public void createReactionReportInDb(CachedMessage cachedMessage, Message reportMessage, ServerUser reporter) {
if(reportMessage == null) {
@@ -112,15 +126,35 @@ public class ReactionReportServiceBean implements ReactionReportService {
@Transactional
public void updateModerationUserReportCooldown(ServerUser reporter) {
AUserInAServer reporterAUserInServer = userInServerManagementService.loadOrCreateUser(reporter);
Optional<ModerationUser> optionalModerationUser = moderationUserManagementService.findModerationUser(reporterAUserInServer);
Instant reportTimeStamp = Instant.now();
if(optionalModerationUser.isPresent()) {
log.info("Updating last report time of user {}.", reporter.getUserId());
optionalModerationUser.get().setLastReportTimeStamp(reportTimeStamp);
} else {
log.info("Creating new moderation user instance for user {} to track report cooldowns.", reporter.getUserId());
moderationUserManagementService.createModerationUserWithReportTimeStamp(reporterAUserInServer, reportTimeStamp);
if(!featureModeService.featureModeActive(ModerationFeatureDefinition.REPORT_REACTIONS, reporter.getServerId(), ReportReactionMode.ANONYMOUS)) {
AUserInAServer reporterAUserInServer = userInServerManagementService.loadOrCreateUser(reporter);
Optional<ModerationUser> optionalModerationUser = moderationUserManagementService.findModerationUser(reporterAUserInServer);
Instant reportTimeStamp = Instant.now();
if(optionalModerationUser.isPresent()) {
log.info("Updating last report time of user {}.", reporter.getUserId());
optionalModerationUser.get().setLastReportTimeStamp(reportTimeStamp);
} else {
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
public CompletableFuture<Void> notifyAndLogFullUserWarning(WarnContext context) {
AServer server = serverManagementService.loadOrCreate(context.getGuild().getIdLong());
Long warningId = counterService.getNextCounterValue(server, WARNINGS_COUNTER_KEY);
Long serverId = context.getGuild().getIdLong();
Long warningId = counterService.getNextCounterValue(serverId, WARNINGS_COUNTER_KEY);
context.setWarnId(warningId);
Member warnedMember = context.getWarnedMember();
Member warningMember = context.getMember();
@@ -124,29 +124,39 @@ public class WarnServiceBean implements WarnService {
.warnId(warningId)
.serverName(guild.getName())
.build();
Long serverId = server.getId();
String warnNotificationMessage = templateService.renderTemplate(WARN_NOTIFICATION_TEMPLATE, warnNotification, server.getId());
List<CompletableFuture> futures = new ArrayList<>();
CompletableFuture<Void> notificationFuture = new CompletableFuture<>();
messageService.sendMessageToUser(warnedMember.getUser(), warnNotificationMessage).whenComplete((message, throwable) -> {
if(throwable != null) {
log.warn("Failed to notify user {} of warning {} in guild {}.", warnedMember.getId(), warningId, serverId);
}
notificationFuture.complete(null);
});
futures.add(notificationFuture);
log.debug("Logging warning for server {}.", server.getId());
String warnNotificationMessage = templateService.renderTemplate(WARN_NOTIFICATION_TEMPLATE, warnNotification, serverId);
return messageService.sendMessageToUser(warnedMember.getUser(), warnNotificationMessage)
.exceptionally(throwable -> {
log.warn("Failed to notify user {} of warning {} in guild {}.", warnedMember.getId(), warningId, serverId);
return null;
})
.thenCompose(message -> self.sendWarningLog(context))
.thenCompose(logMessage -> self.evaluateInfraction(context, logMessage))
.thenAccept(context::setInfractionId);
}
@Transactional
public CompletableFuture<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)) {
Long infractionPoints = configService.getLongValueOrConfigDefault(WarningFeatureConfig.WARN_INFRACTION_POINTS, serverId);
AUserInAServer warnedUser = userInServerManagementService.loadOrCreateUser(warnedMember);
warnedUser.setServerReference(server);
futures.add(infractionService.createInfractionWithNotification(warnedUser, infractionPoints)
.thenAccept(infraction -> context.setInfractionId(infraction.getId())));
AServer server = serverManagementService.loadServer(context.getGuild());
AUserInAServer warnedUser = userInServerManagementService.loadOrCreateUser(server, context.getWarnedMember().getIdLong());
AUserInAServer warningUser = userInServerManagementService.loadOrCreateUser(server, context.getMember().getIdLong());
// 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
@@ -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) {
log.debug("Loading members decaying {} warnings in server {}.", warningsToDecay.size(), server.getId());
HashMap<ServerSpecificId, FutureMemberPair> warningMembers = new HashMap<>();

View File

@@ -1,25 +1,44 @@
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.service.management.ChannelManagementService;
import dev.sheldan.abstracto.moderation.model.database.Infraction;
import dev.sheldan.abstracto.moderation.repository.InfractionRepository;
import net.dv8tion.jda.api.entities.Message;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class InfractionManagementServiceBean implements InfractionManagementService{
public class InfractionManagementServiceBean implements InfractionManagementService {
@Autowired
private InfractionRepository infractionRepository;
@Autowired
private ChannelManagementService channelManagementService;
@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
.builder()
.user(aUserInAServer)
.server(aUserInAServer.getServerReference())
.user(target)
.infractionCreator(creator)
.server(target.getServerReference())
.decayed(false)
.logChannel(channel)
.logMessageId(message != null ? message.getIdLong() : null)
.type(type)
.description(description)
.points(points)
.build();
return infractionRepository.save(infraction);
@@ -30,6 +49,16 @@ public class InfractionManagementServiceBean implements InfractionManagementServ
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
public Infraction loadInfraction(Long 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.AUserInAServer;
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.repository.MuteRepository;
import lombok.extern.slf4j.Slf4j;
@@ -27,11 +29,20 @@ public class MuteManagementServiceBean implements MuteManagementService {
@Autowired
private UserInServerManagementService userInServerManagementService;
@Autowired
private InfractionManagementService infractionManagementService;
@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 {}",
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
.builder()
.mutedUser(mutedUser)
@@ -41,6 +52,7 @@ public class MuteManagementServiceBean implements MuteManagementService {
.mutingChannel(muteMessage.getChannel())
.messageId(muteMessage.getMessageId())
.reason(reason)
.infraction(infraction)
.triggerKey(triggerKey)
.muteId(id)
.muteEnded(false)
@@ -76,6 +88,11 @@ public class MuteManagementServiceBean implements MuteManagementService {
@Override
public Mute getAMuteOf(AUserInAServer userInAServer) {
return getAMuteOfOptional(userInAServer).orElseThrow(NoMuteFoundException::new);
}
@Override
public Optional<Mute> getAMuteOfOptional(AUserInAServer userInAServer) {
return muteRepository.findTopByMutedUserAndMuteEndedFalse(userInAServer);
}
@@ -84,6 +101,11 @@ public class MuteManagementServiceBean implements MuteManagementService {
return getAMuteOf(userInServerManagementService.loadOrCreateUser(member));
}
@Override
public Optional<Mute> getAMuteOfOptional(Member member) {
return getAMuteOfOptional(userInServerManagementService.loadOrCreateUser(member));
}
@Override
public List<Mute> getAllActiveMutesOf(AUserInAServer 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.Instant;
import java.util.List;
import java.util.Optional;
@Component
@@ -30,7 +31,8 @@ public class ReactionReportManagementServiceBean implements ReactionReportManage
@Override
public Optional<ReactionReport> findRecentReactionReportAboutUser(AUserInAServer aUserInAServer, Duration 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

View File

@@ -104,5 +104,10 @@ public class WarnManagementServiceBean implements WarnManagementService {
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.9/collection.xml" relativeToChangelogFile="true"/>
<include file="1.3.10/collection.xml" relativeToChangelogFile="true"/>
<include file="1.4.0/collection.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@@ -49,6 +49,14 @@ abstracto.featureModes.infractionReporting.featureName=infractions
abstracto.featureModes.infractionReporting.mode=infractionReporting
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.longValue=10
@@ -68,7 +76,16 @@ abstracto.systemConfigs.infractionLevels.name=infractionLevels
abstracto.systemConfigs.infractionLevels.longValue=5
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.mode=automaticWarnDecayLogging
@@ -78,6 +95,3 @@ abstracto.featureModes.notifyMemberWarningDecays.featureName=warnDecay
abstracto.featureModes.notifyMemberWarningDecays.mode=notifyMemberWarningDecays
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);
}
}