[AB-196] adding confirmation requirement to various commands

refactoring command received handler in order to re-use when confirmation has been given
removing reaction from showSuggestion and remind command
adding log message for message deleted message, in case the member left
all commands from now on must work without the member field from the message, as this is not available when retrieving the message
This commit is contained in:
Sheldan
2021-09-10 21:40:54 +02:00
parent da1a71ecdc
commit 16e6caa1f0
51 changed files with 615 additions and 109 deletions

View File

@@ -2,15 +2,15 @@ package dev.sheldan.abstracto.core.command;
import dev.sheldan.abstracto.core.command.condition.ConditionResult;
import dev.sheldan.abstracto.core.command.config.*;
import dev.sheldan.abstracto.core.command.config.features.CoreFeatureConfig;
import dev.sheldan.abstracto.core.command.exception.CommandParameterValidationException;
import dev.sheldan.abstracto.core.command.exception.IncorrectParameterException;
import dev.sheldan.abstracto.core.command.exception.InsufficientParametersException;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.command.execution.UnParsedCommandParameter;
import dev.sheldan.abstracto.core.command.execution.UnparsedCommandParameterPiece;
import dev.sheldan.abstracto.core.command.execution.*;
import dev.sheldan.abstracto.core.command.handler.CommandParameterHandler;
import dev.sheldan.abstracto.core.command.handler.CommandParameterIterators;
import dev.sheldan.abstracto.core.command.model.CommandConfirmationModel;
import dev.sheldan.abstracto.core.command.model.CommandConfirmationPayload;
import dev.sheldan.abstracto.core.command.service.CommandManager;
import dev.sheldan.abstracto.core.command.service.CommandService;
import dev.sheldan.abstracto.core.command.service.ExceptionService;
@@ -20,10 +20,16 @@ import dev.sheldan.abstracto.core.metric.service.CounterMetric;
import dev.sheldan.abstracto.core.metric.service.MetricService;
import dev.sheldan.abstracto.core.metric.service.MetricTag;
import dev.sheldan.abstracto.core.models.context.UserInitiatedServerContext;
import dev.sheldan.abstracto.core.service.EmoteService;
import dev.sheldan.abstracto.core.service.RoleService;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.service.*;
import dev.sheldan.abstracto.core.service.management.*;
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.scheduling.model.JobParameters;
import dev.sheldan.abstracto.scheduling.service.SchedulerService;
import lombok.Builder;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.*;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
@@ -35,6 +41,8 @@ import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.PostConstruct;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
@@ -88,6 +96,32 @@ public class CommandReceivedHandler extends ListenerAdapter {
@Autowired
private MetricService metricService;
@Autowired
private ComponentService componentService;
@Autowired
private ComponentPayloadManagementService componentPayloadManagementService;
@Autowired
private ComponentPayloadService componentPayloadService;
@Autowired
private TemplateService templateService;
@Autowired
private ChannelService channelService;
@Autowired
private SchedulerService schedulerService;
@Autowired
private ConfigService configService;
@Autowired
private MessageService messageService;
public static final String COMMAND_CONFIRMATION_ORIGIN = "commandConfirmation";
public static final String COMMAND_CONFIRMATION_MESSAGE_TEMPLATE_KEY = "command_confirmation_message";
public static final String COMMAND_PROCESSED = "command.processed";
public static final String STATUS_TAG = "status";
public static final CounterMetric COMMANDS_PROCESSED_COUNTER = CounterMetric
@@ -107,38 +141,105 @@ public class CommandReceivedHandler extends ListenerAdapter {
if(!event.isFromGuild()) {
return;
}
if(!commandManager.isCommand(event.getMessage())) {
Message message = event.getMessage();
if(!commandManager.isCommand(message)) {
return;
}
metricService.incrementCounter(COMMANDS_PROCESSED_COUNTER);
final Command foundCommand;
try {
String contentStripped = event.getMessage().getContentRaw();
List<String> parameters = Arrays.asList(contentStripped.split(" "));
UnParsedCommandParameter unParsedParameter = new UnParsedCommandParameter(contentStripped, event.getMessage());
String commandName = commandManager.getCommandName(parameters.get(0), event.getGuild().getIdLong());
foundCommand = commandManager.findCommandByParameters(commandName, unParsedParameter, event.getGuild().getIdLong());
tryToExecuteFoundCommand(event, foundCommand, unParsedParameter);
UnParsedCommandResult result = getUnparsedCommandResult(message);
CompletableFuture<CommandParseResult> parsingFuture = getParametersFromMessage(message, result);
parsingFuture.thenAccept(parsedParameters ->
self.executeCommand(event, parsedParameters.getCommand(), parsedParameters.getParameters())
).exceptionally(throwable -> {
self.reportException(event, result.getCommand(), throwable, "Exception when executing or parsing command.");
return null;
});
} catch (Exception e) {
reportException(event, null, e, String.format("Exception when executing command from message %d in message %d in guild %d."
,event.getMessage().getIdLong(), event.getChannel().getIdLong(), event.getGuild().getIdLong()));
, message.getIdLong(), event.getChannel().getIdLong(), event.getGuild().getIdLong()));
}
}
private void tryToExecuteFoundCommand(MessageReceivedEvent event, Command foundCommand, UnParsedCommandParameter unParsedParameter) {
CompletableFuture<Parameters> parsingFuture = getParsedParameters(unParsedParameter, foundCommand, event.getMessage());
parsingFuture.thenAccept(parsedParameters ->
self.executeCommand(event, foundCommand, parsedParameters)
).exceptionally(throwable -> {
self.reportException(event, foundCommand, throwable, "Exception when executing or parsing command.");
return null;
});
public UnParsedCommandResult getUnparsedCommandResult(Message message) {
String contentStripped = message.getContentRaw();
List<String> parameters = Arrays.asList(contentStripped.split(" "));
UnParsedCommandParameter unParsedParameter = new UnParsedCommandParameter(contentStripped, message);
String commandName = commandManager.getCommandName(parameters.get(0), message.getGuild().getIdLong());
Command foundCommand = commandManager.findCommandByParameters(commandName, unParsedParameter, message.getGuild().getIdLong());
return UnParsedCommandResult
.builder()
.command(foundCommand)
.parameter(unParsedParameter)
.build();
}
public CompletableFuture<CommandParseResult> getParametersFromMessage(Message message) {
UnParsedCommandResult result = getUnparsedCommandResult(message);
return getParsedParameters(result.getParameter(), result.getCommand(), message).thenApply(foundParameters -> CommandParseResult
.builder()
.command(result.getCommand())
.parameters(foundParameters)
.build());
}
public CompletableFuture<CommandParseResult> getParametersFromMessage(Message message, UnParsedCommandResult result) {
return getParsedParameters(result.getParameter(), result.getCommand(), message).thenApply(foundParameters -> CommandParseResult
.builder()
.command(result.getCommand())
.parameters(foundParameters)
.build());
}
@Transactional
public CompletableFuture<Void> cleanupConfirmationMessage(Long server, Long channelId, Long messageId, String confirmationPayloadId, String abortPayloadId) {
componentPayloadManagementService.deletePayloads(Arrays.asList(confirmationPayloadId, abortPayloadId));
return messageService.deleteMessageInChannelInServer(server, channelId, messageId);
}
@Transactional
public void persistConfirmationCallbacks(CommandConfirmationModel model, Message createdMessage) {
AServer server = serverManagementService.loadServer(model.getDriedCommandContext().getServerId());
CommandConfirmationPayload confirmPayload = CommandConfirmationPayload
.builder()
.commandContext(model.getDriedCommandContext())
.otherButtonComponentId(model.getAbortButtonId())
.action(CommandConfirmationPayload.CommandConfirmationAction.CONFIRM)
.build();
componentPayloadService.createButtonPayload(model.getConfirmButtonId(), confirmPayload, COMMAND_CONFIRMATION_ORIGIN, server);
CommandConfirmationPayload abortPayload = CommandConfirmationPayload
.builder()
.commandContext(model.getDriedCommandContext())
.otherButtonComponentId(model.getConfirmButtonId())
.action(CommandConfirmationPayload.CommandConfirmationAction.ABORT)
.build();
componentPayloadService.createButtonPayload(model.getAbortButtonId(), abortPayload, COMMAND_CONFIRMATION_ORIGIN, server);
scheduleConfirmationDeletion(createdMessage, model.getConfirmButtonId(), model.getAbortButtonId());
}
private void scheduleConfirmationDeletion(Message createdMessage, String confirmationPayloadId, String abortPayloadId) {
HashMap<Object, Object> parameters = new HashMap<>();
Long serverId = createdMessage.getGuild().getIdLong();
parameters.put("serverId", serverId.toString());
parameters.put("channelId", createdMessage.getChannel().getId());
parameters.put("messageId", createdMessage.getId());
parameters.put("confirmationPayloadId", confirmationPayloadId);
parameters.put("abortPayloadId", abortPayloadId);
JobParameters jobParameters = JobParameters
.builder()
.parameters(parameters)
.build();
Long confirmationTimeout = configService.getLongValueOrConfigDefault(CoreFeatureConfig.CONFIRMATION_TIMEOUT, serverId);
Instant targetDate = Instant.now().plus(confirmationTimeout, ChronoUnit.SECONDS);
long channelId = createdMessage.getChannel().getIdLong();
log.info("Scheduling job to delete confirmation message {} in channel {} in server {} at {}.", createdMessage.getIdLong(), channelId, serverId, targetDate);
schedulerService.executeJobWithParametersOnce("confirmationCleanupJob", "core", jobParameters, Date.from(targetDate));
}
@Transactional
public void executeCommand(MessageReceivedEvent event, Command foundCommand, Parameters parsedParameters) {
UserInitiatedServerContext userInitiatedContext = buildTemplateParameter(event);
UserInitiatedServerContext userInitiatedContext = buildUserInitiatedServerContext(event);
CommandContext.CommandContextBuilder commandContextBuilder = CommandContext.builder()
.author(event.getMember())
.guild(event.getGuild())
@@ -153,9 +254,27 @@ public class CommandReceivedHandler extends ListenerAdapter {
conditionResultFuture.thenAccept(conditionResult -> {
CommandResult commandResult = null;
if(conditionResult.isResult()) {
if(foundCommand.getConfiguration().isAsync()) {
CommandConfiguration commandConfiguration = foundCommand.getConfiguration();
if(commandConfiguration.isRequiresConfirmation()) {
DriedCommandContext driedCommandContext = DriedCommandContext.buildFromCommandContext(commandContext);
driedCommandContext.setCommandName(commandConfiguration.getName());
String confirmId = componentService.generateComponentId();
String abortId = componentService.generateComponentId();
CommandConfirmationModel model = CommandConfirmationModel
.builder()
.abortButtonId(abortId)
.confirmButtonId(confirmId)
.driedCommandContext(driedCommandContext)
.commandName(commandConfiguration.getName())
.build();
MessageToSend message = templateService.renderEmbedTemplate(COMMAND_CONFIRMATION_MESSAGE_TEMPLATE_KEY, model, event.getGuild().getIdLong());
List<CompletableFuture<Message>> confirmationMessageFutures = channelService.sendMessageToSendToChannel(message, event.getChannel());
FutureUtils.toSingleFutureGeneric(confirmationMessageFutures)
.thenAccept(unused -> self.persistConfirmationCallbacks(model, confirmationMessageFutures.get(0).join()))
.exceptionally(throwable -> self.failedCommandHandling(event, foundCommand, parsedParameters, commandContext, throwable));
} else if(commandConfiguration.isAsync()) {
log.info("Executing async command {} for server {} in channel {} based on message {} by user {}.",
foundCommand.getConfiguration().getName(), commandContext.getGuild().getId(), commandContext.getChannel().getId(), commandContext.getMessage().getId(), commandContext.getAuthor().getId());
commandConfiguration.getName(), commandContext.getGuild().getId(), commandContext.getChannel().getId(), commandContext.getMessage().getId(), commandContext.getAuthor().getId());
self.executeAsyncCommand(foundCommand, commandContext)
.exceptionally(throwable -> failedCommandHandling(event, foundCommand, parsedParameters, commandContext, throwable));
@@ -174,7 +293,7 @@ public class CommandReceivedHandler extends ListenerAdapter {
private Void failedCommandHandling(MessageReceivedEvent event, Command foundCommand, Parameters parsedParameters, CommandContext commandContext, Throwable throwable) {
log.error("Asynchronous command {} failed.", foundCommand.getConfiguration().getName(), throwable);
UserInitiatedServerContext rebuildUserContext = buildTemplateParameter(event);
UserInitiatedServerContext rebuildUserContext = buildUserInitiatedServerContext(commandContext);
CommandContext rebuildContext = CommandContext.builder()
.author(event.getMember())
.guild(event.getGuild())
@@ -197,15 +316,20 @@ public class CommandReceivedHandler extends ListenerAdapter {
}
@Transactional
public void reportException(MessageReceivedEvent event, Command foundCommand, Throwable throwable, String s) {
UserInitiatedServerContext userInitiatedContext = buildTemplateParameter(event);
public void reportException(CommandContext context, Command foundCommand, Throwable throwable, String s) {
reportException(context.getMessage(), context.getChannel(), context.getAuthor(), foundCommand, throwable, s);
}
@Transactional
public void reportException(Message message, TextChannel textChannel, Member member, Command foundCommand, Throwable throwable, String s) {
UserInitiatedServerContext userInitiatedContext = buildUserInitiatedServerContext(member, textChannel, member.getGuild());
CommandContext.CommandContextBuilder commandContextBuilder = CommandContext.builder()
.author(event.getMember())
.guild(event.getGuild())
.author(member)
.guild(message.getGuild())
.undoActions(new ArrayList<>())
.channel(event.getTextChannel())
.message(event.getMessage())
.jda(event.getJDA())
.channel(message.getTextChannel())
.message(message)
.jda(message.getJDA())
.userInitiatedContext(userInitiatedContext);
log.error(s, throwable);
CommandResult commandResult = CommandResult.fromError(throwable.getMessage(), throwable);
@@ -213,6 +337,11 @@ public class CommandReceivedHandler extends ListenerAdapter {
self.executePostCommandListener(foundCommand, commandContext, commandResult);
}
@Transactional
public void reportException(MessageReceivedEvent event, Command foundCommand, Throwable throwable, String s) {
reportException(event.getMessage(), event.getTextChannel(), event.getMember(), foundCommand, throwable, s);
}
private void validateCommandParameters(Parameters parameters, Command foundCommand) {
CommandConfiguration commandConfiguration = foundCommand.getConfiguration();
List<Parameter> parameterList = commandConfiguration.getParameters();
@@ -249,15 +378,23 @@ public class CommandReceivedHandler extends ListenerAdapter {
return foundCommand.execute(commandContext);
}
private UserInitiatedServerContext buildTemplateParameter(MessageReceivedEvent event) {
private UserInitiatedServerContext buildUserInitiatedServerContext(Member member, TextChannel textChannel, Guild guild) {
return UserInitiatedServerContext
.builder()
.member(event.getMember())
.messageChannel(event.getTextChannel())
.guild(event.getGuild())
.member(member)
.messageChannel(textChannel)
.guild(guild)
.build();
}
private UserInitiatedServerContext buildUserInitiatedServerContext(MessageReceivedEvent event) {
return buildUserInitiatedServerContext(event.getMember(), event.getTextChannel(), event.getGuild());
}
private UserInitiatedServerContext buildUserInitiatedServerContext(CommandContext context) {
return buildUserInitiatedServerContext(context.getAuthor(), context.getChannel(), context.getGuild());
}
public CompletableFuture<Parameters> getParsedParameters(UnParsedCommandParameter unParsedCommandParameter, Command command, Message message){
List<ParseResult> parsedParameters = new ArrayList<>();
List<Parameter> parameters = command.getConfiguration().getParameters();
@@ -405,4 +542,18 @@ public class CommandReceivedHandler extends ListenerAdapter {
metricService.registerCounter(COMMANDS_WRONG_PARAMETER_COUNTER, "Commands with incorrect parameter");
this.parameterHandlers = parameterHandlers.stream().sorted(comparing(CommandParameterHandler::getPriority)).collect(Collectors.toList());
}
@Getter
@Builder
public static class CommandParseResult {
private Parameters parameters;
private Command command;
}
@Getter
@Builder
public static class UnParsedCommandResult {
private UnParsedCommandParameter parameter;
private Command command;
}
}

View File

@@ -0,0 +1,118 @@
package dev.sheldan.abstracto.core.command.listener;
import dev.sheldan.abstracto.core.command.CommandReceivedHandler;
import dev.sheldan.abstracto.core.command.config.features.CoreFeatureDefinition;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.command.execution.DriedCommandContext;
import dev.sheldan.abstracto.core.command.model.CommandConfirmationPayload;
import dev.sheldan.abstracto.core.command.model.CommandServiceBean;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.config.ListenerPriority;
import dev.sheldan.abstracto.core.interaction.InteractionService;
import dev.sheldan.abstracto.core.listener.ButtonClickedListenerResult;
import dev.sheldan.abstracto.core.listener.async.jda.ButtonClickedListener;
import dev.sheldan.abstracto.core.models.listener.ButtonClickedListenerModel;
import dev.sheldan.abstracto.core.service.MessageService;
import dev.sheldan.abstracto.core.service.management.ComponentPayloadManagementService;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.Arrays;
import java.util.concurrent.CompletableFuture;
@Component
@Slf4j
public class ConfirmationButtonClickedListener implements ButtonClickedListener {
@Autowired
private CommandServiceBean commandServiceBean;
@Autowired
private CommandReceivedHandler commandReceivedHandler;
@Autowired
private MessageService messageService;
@Autowired
private ComponentPayloadManagementService componentPayloadManagementService;
@Autowired
private ConfirmationButtonClickedListener self;
@Autowired
private InteractionService interactionService;
@Override
public ButtonClickedListenerResult execute(ButtonClickedListenerModel model) {
CommandConfirmationPayload payload = (CommandConfirmationPayload) model.getDeserializedPayload();
DriedCommandContext commandCtx = payload.getCommandContext();
if(payload.getAction().equals(CommandConfirmationPayload.CommandConfirmationAction.CONFIRM)) {
log.info("Confirming command {} in server {} from message {} in channel {} with event {}.",
commandCtx.getCommandName(), commandCtx.getServerId(), commandCtx.getMessageId(),
commandCtx.getChannelId(), model.getEvent().getInteraction().getId());
commandServiceBean.fillCommandContext(commandCtx)
.thenAccept(context -> self.executeButtonClickedListener(model, payload, context))
.exceptionally(throwable -> {
log.error("Command confirmation failed to execute.", throwable);
return null;
});
} else {
log.info("Denying command {} in server {} from message {} in channel {} with event {}.",
commandCtx.getCommandName(), commandCtx.getServerId(), commandCtx.getMessageId(),
commandCtx.getChannelId(), model.getEvent().getInteraction().getId());
cleanup(model, payload);
}
return ButtonClickedListenerResult.ACKNOWLEDGED;
}
@Transactional
public void executeButtonClickedListener(ButtonClickedListenerModel model, CommandConfirmationPayload payload, CommandServiceBean.RebuiltCommandContext context) {
try {
if(context.getCommand().getConfiguration().isAsync()) {
commandReceivedHandler.executeAsyncCommand(context.getCommand(), context.getContext());
} else {
CommandResult result = commandReceivedHandler.executeCommand(context.getCommand(), context.getContext());
commandReceivedHandler.executePostCommandListener(context.getCommand(), context.getContext(), result);
}
} catch (Exception e) {
commandReceivedHandler.reportException(context.getContext(), context.getCommand(), e, "Confirmation execution of command failed.");
} finally {
cleanup(model, payload);
}
}
private void cleanup(ButtonClickedListenerModel model, CommandConfirmationPayload payload) {
log.debug("Cleaning up component {} and {}.", payload.getOtherButtonComponentId(), model.getEvent().getComponentId());
componentPayloadManagementService.deletePayloads(Arrays.asList(payload.getOtherButtonComponentId(), model.getEvent().getComponentId()));
log.debug("Deleting confirmation message {}.", model.getEvent().getMessageId());
messageService.deleteMessage(model.getEvent().getMessage())
.thenAccept(unused -> self.sendAbortNotification(model))
.exceptionally(throwable -> {
log.warn("Failed to clean up confirmation message {}.", model.getEvent().getMessageId());
return null;
});
}
public CompletableFuture<Void> sendAbortNotification(ButtonClickedListenerModel model) {
log.info("Sending abort notification for message {}", model.getEvent().getMessageId());
return FutureUtils.toSingleFutureGeneric(interactionService.sendMessageToInteraction("command_aborted_notification", new Object(), model.getEvent().getHook()));
}
@Override
public Boolean handlesEvent(ButtonClickedListenerModel model) {
return model.getOrigin().equals(CommandReceivedHandler.COMMAND_CONFIRMATION_ORIGIN);
}
@Override
public FeatureDefinition getFeature() {
return CoreFeatureDefinition.CORE_FEATURE;
}
@Override
public Integer getPriority() {
return ListenerPriority.MEDIUM;
}
}

View File

@@ -0,0 +1,14 @@
package dev.sheldan.abstracto.core.command.model;
import dev.sheldan.abstracto.core.command.execution.DriedCommandContext;
import lombok.Builder;
import lombok.Getter;
@Getter
@Builder
public class CommandConfirmationModel {
private String confirmButtonId;
private String abortButtonId;
private DriedCommandContext driedCommandContext;
private String commandName;
}

View File

@@ -0,0 +1,21 @@
package dev.sheldan.abstracto.core.command.model;
import dev.sheldan.abstracto.core.command.execution.DriedCommandContext;
import dev.sheldan.abstracto.core.models.template.button.ButtonPayload;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
@Getter
@Builder
@Setter
public class CommandConfirmationPayload implements ButtonPayload {
private DriedCommandContext commandContext;
private CommandConfirmationAction action;
private String otherButtonComponentId;
public enum CommandConfirmationAction {
CONFIRM, ABORT
}
}

View File

@@ -1,4 +1,4 @@
package dev.sheldan.abstracto.core.command.service;
package dev.sheldan.abstracto.core.command.model;
import com.google.common.collect.Iterables;
import dev.sheldan.abstracto.core.command.Command;
@@ -10,20 +10,29 @@ import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.config.Parameters;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.DriedCommandContext;
import dev.sheldan.abstracto.core.command.execution.UnParsedCommandParameter;
import dev.sheldan.abstracto.core.command.model.database.ACommand;
import dev.sheldan.abstracto.core.command.model.database.ACommandInAServer;
import dev.sheldan.abstracto.core.command.model.database.AModule;
import dev.sheldan.abstracto.core.command.service.CommandRegistry;
import dev.sheldan.abstracto.core.command.service.CommandService;
import dev.sheldan.abstracto.core.command.service.management.CommandInServerManagementService;
import dev.sheldan.abstracto.core.command.service.management.CommandManagementService;
import dev.sheldan.abstracto.core.command.service.management.FeatureManagementService;
import dev.sheldan.abstracto.core.command.service.management.ModuleManagementService;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.models.context.UserInitiatedServerContext;
import dev.sheldan.abstracto.core.models.database.AFeature;
import dev.sheldan.abstracto.core.models.database.ARole;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.service.MemberService;
import dev.sheldan.abstracto.core.service.MessageService;
import dev.sheldan.abstracto.core.utils.CompletableFutureList;
import lombok.Builder;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
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;
@@ -59,6 +68,12 @@ public class CommandServiceBean implements CommandService {
@Autowired
private CommandReceivedHandler commandReceivedHandler;
@Autowired
private MemberService memberService;
@Autowired
private MessageService messageService;
@Override
public ACommand createCommand(String name, String moduleName, FeatureDefinition featureDefinition) {
AModule module = moduleManagementService.getOrCreate(moduleName);
@@ -216,5 +231,46 @@ public class CommandServiceBean implements CommandService {
}
}
public CompletableFuture<RebuiltCommandContext> fillCommandContext(DriedCommandContext commandContext) {
CompletableFuture<Member> memberFuture = memberService.getMemberInServerAsync(commandContext.getServerId(), commandContext.getUserId());
CompletableFuture<Message> messageFuture = messageService.loadMessage(commandContext.getServerId(), commandContext.getChannelId(), commandContext.getMessageId());
return CompletableFuture.allOf(memberFuture, messageFuture).thenCompose(unused -> {
Message message = messageFuture.join();
CommandReceivedHandler.UnParsedCommandResult unparsedResult = commandReceivedHandler.getUnparsedCommandResult(message);
CompletableFuture<CommandReceivedHandler.CommandParseResult> getParseResult = commandReceivedHandler.getParametersFromMessage(message, unparsedResult);
return getParseResult.thenApply(commandParseResult -> {
Member author = memberFuture.join();
UserInitiatedServerContext userInitiatedServerContext = UserInitiatedServerContext
.builder()
.messageChannel(message.getChannel())
.message(message)
.guild(message.getGuild())
.build();
CommandContext context = CommandContext
.builder()
.channel(message.getTextChannel())
.author(author)
.guild(message.getGuild())
.jda(message.getJDA())
.parameters(commandParseResult.getParameters())
.userInitiatedContext(userInitiatedServerContext)
.message(message)
.build();
return RebuiltCommandContext
.builder()
.context(context)
.command(unparsedResult.getCommand())
.build();
});
});
}
@Builder
@Getter
public static class RebuiltCommandContext {
private CommandContext context;
private Command command;
}
}

View File

@@ -39,6 +39,7 @@ public class DeleteAlias extends AbstractConditionableCommand {
.module(ConfigModuleDefinition.CONFIG)
.parameters(parameters)
.templated(true)
.requiresConfirmation(true)
.supportsEmbedException(true)
.help(helpInfo)
.causesReaction(true)

View File

@@ -9,7 +9,7 @@ import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.command.model.database.ACommand;
import dev.sheldan.abstracto.core.command.service.CommandService;
import dev.sheldan.abstracto.core.command.service.CommandServiceBean;
import dev.sheldan.abstracto.core.command.model.CommandServiceBean;
import dev.sheldan.abstracto.core.command.service.management.CommandManagementService;
import dev.sheldan.abstracto.core.command.service.management.FeatureManagementService;
import dev.sheldan.abstracto.core.commands.config.ConfigModuleDefinition;

View File

@@ -9,7 +9,7 @@ import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.command.model.database.ACommand;
import dev.sheldan.abstracto.core.command.service.CommandService;
import dev.sheldan.abstracto.core.command.service.CommandServiceBean;
import dev.sheldan.abstracto.core.command.model.CommandServiceBean;
import dev.sheldan.abstracto.core.command.service.management.CommandManagementService;
import dev.sheldan.abstracto.core.command.service.management.FeatureManagementService;
import dev.sheldan.abstracto.core.commands.config.ConfigModuleDefinition;

View File

@@ -9,7 +9,7 @@ import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.command.model.database.ACommand;
import dev.sheldan.abstracto.core.command.service.CommandService;
import dev.sheldan.abstracto.core.command.service.CommandServiceBean;
import dev.sheldan.abstracto.core.command.model.CommandServiceBean;
import dev.sheldan.abstracto.core.command.service.management.CommandManagementService;
import dev.sheldan.abstracto.core.command.service.management.FeatureManagementService;
import dev.sheldan.abstracto.core.commands.config.ConfigModuleDefinition;

View File

@@ -9,7 +9,7 @@ import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.command.model.database.ACommand;
import dev.sheldan.abstracto.core.command.service.CommandService;
import dev.sheldan.abstracto.core.command.service.CommandServiceBean;
import dev.sheldan.abstracto.core.command.model.CommandServiceBean;
import dev.sheldan.abstracto.core.command.service.management.CommandManagementService;
import dev.sheldan.abstracto.core.command.service.management.FeatureManagementService;
import dev.sheldan.abstracto.core.commands.config.ConfigModuleDefinition;

View File

@@ -0,0 +1,40 @@
package dev.sheldan.abstracto.core.job;
import dev.sheldan.abstracto.core.command.CommandReceivedHandler;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.PersistJobDataAfterExecution;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.quartz.QuartzJobBean;
import org.springframework.stereotype.Component;
@Slf4j
@DisallowConcurrentExecution
@Component
@PersistJobDataAfterExecution
@Setter
public class ConfirmationCleanupJob extends QuartzJobBean {
private Long serverId;
private Long channelId;
private Long messageId;
private String confirmationPayloadId;
private String abortPayloadId;
@Autowired
private CommandReceivedHandler commandReceivedHandler;
@Override
protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
log.info("Cleaning up confirmation message {} in server {} in channel {}.", messageId, serverId, channelId);
commandReceivedHandler.cleanupConfirmationMessage(serverId, channelId, messageId, confirmationPayloadId, abortPayloadId)
.thenAccept(unused -> log.info("Deleted confirmation message {}", messageId))
.exceptionally(throwable -> {
log.warn("Failed to cleanup confirmation message {}.", messageId);
return null;
});
}
}

View File

@@ -60,6 +60,7 @@ public class ComponentPayloadManagementServiceBean implements ComponentPayloadMa
@Override
public void deletePayloads(List<String> ids) {
ids.forEach(payloadId -> log.info("Deleting payload {}", payloadId));
repository.deleteByIdIn(ids);
}

View File

@@ -13,6 +13,9 @@ abstracto.systemConfigs.prefix.stringValue=!
abstracto.systemConfigs.noCommandFoundReporting.name=noCommandFoundReporting
abstracto.systemConfigs.noCommandFoundReporting.stringValue=true
abstracto.systemConfigs.confirmationTimeout.name=confirmationTimeout
abstracto.systemConfigs.confirmationTimeout.longValue=120
abstracto.systemConfigs.maxMessages.name=maxMessages
abstracto.systemConfigs.maxMessages.longValue=3

View File

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

View File

@@ -0,0 +1,18 @@
<?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="confirmation-cleanup-job-insert">
<insert tableName="scheduler_job">
<column name="name" value="confirmationCleanupJob"/>
<column name="group_name" value="core"/>
<column name="clazz" value="dev.sheldan.abstracto.core.job.ConfirmationCleanupJob"/>
<column name="active" value="true"/>
<column name="recovery" value="false"/>
</insert>
</changeSet>
</databaseChangeLog>

View File

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

View File

@@ -20,4 +20,5 @@
<include file="1.3.1/collection.xml" relativeToChangelogFile="true"/>
<include file="1.3.5/collection.xml" relativeToChangelogFile="true"/>
<include file="1.3.6/collection.xml" relativeToChangelogFile="true"/>
<include file="1.3.9/collection.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@@ -122,6 +122,9 @@ public class CommandReceivedHandlerTest {
@Mock
private List<Member> members;
@Mock
private Member member;
@Mock
private Bag<Role> roles;
@@ -161,6 +164,8 @@ public class CommandReceivedHandlerTest {
when(commandManager.isCommand(message)).thenReturn(true);
when(event.getGuild()).thenReturn(guild);
when(event.getChannel()).thenReturn(channel);
when(event.getMember()).thenReturn(member);
when(message.getGuild()).thenReturn(guild);
when(guild.getIdLong()).thenReturn(SERVER_ID);
when(message.getContentRaw()).thenReturn(MESSAGE_CONTENT_COMMAND_ONLY);
when(commandManager.getCommandName(anyString(), eq(SERVER_ID))).thenReturn(COMMAND_NAME);
@@ -286,9 +291,9 @@ public class CommandReceivedHandlerTest {
parameterHandlers.add(parameterHandler);
parameterHandlers.add(secondParameterHandler);
when(event.isFromGuild()).thenReturn(true);
when(message.getGuild()).thenReturn(guild);
when(event.getMessage()).thenReturn(message);
when(commandManager.isCommand(message)).thenReturn(true);
when(event.getGuild()).thenReturn(guild);
when(guild.getIdLong()).thenReturn(SERVER_ID);
when(message.getContentRaw()).thenReturn(messageContentTwoParameter);
when(commandManager.getCommandName(anyString(), eq(SERVER_ID))).thenReturn(COMMAND_NAME);

View File

@@ -3,6 +3,7 @@ package dev.sheldan.abstracto.core.command.service;
import dev.sheldan.abstracto.core.command.Command;
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.model.CommandServiceBean;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;