mirror of
https://github.com/Sheldan/abstracto.git
synced 2026-04-03 08:26:25 +00:00
[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:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -39,6 +39,7 @@ public class DeleteAlias extends AbstractConditionableCommand {
|
||||
.module(ConfigModuleDefinition.CONFIG)
|
||||
.parameters(parameters)
|
||||
.templated(true)
|
||||
.requiresConfirmation(true)
|
||||
.supportsEmbedException(true)
|
||||
.help(helpInfo)
|
||||
.causesReaction(true)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user