[AB-53] adding economy related commands

adding runtime exception to root command handling
adding simple custom command module
disabling slash command for show emote the utility is not given (the bot cannot show any emotes from servers its not in)
fixing post target setup not allowing threads
fixing post targets not supporting threads
fixing interactions not considering the actual amount of embeds
fixing some cases for events which are not in a guild
This commit is contained in:
Sheldan
2022-07-21 00:43:04 +02:00
parent 68cae74819
commit 9954515db0
81 changed files with 3717 additions and 108 deletions

View File

@@ -1,5 +1,6 @@
package dev.sheldan.abstracto.core.command;
import dev.sheldan.abstracto.core.Prioritized;
import dev.sheldan.abstracto.core.command.condition.ConditionResult;
import dev.sheldan.abstracto.core.command.config.*;
import dev.sheldan.abstracto.core.command.config.features.CoreFeatureConfig;
@@ -74,6 +75,9 @@ public class CommandReceivedHandler extends ListenerAdapter {
@Autowired
private List<CommandParameterHandler> parameterHandlers;
@Autowired
private List<CommandAlternative> commandAlternatives;
@Autowired
private MetricService metricService;
@@ -129,19 +133,31 @@ public class CommandReceivedHandler extends ListenerAdapter {
metricService.incrementCounter(COMMANDS_PROCESSED_COUNTER);
try {
UnParsedCommandResult result = getUnparsedCommandResult(message);
CompletableFuture<CommandParseResult> parsingFuture = getParametersFromMessage(message, result);
parsingFuture.thenAccept(parsedParameters -> {
try {
self.executeCommand(event, parsedParameters.getCommand(), parsedParameters.getParameters());
} catch (Exception e) {
reportException(event, null, e, String.format("Exception when executing command from message %d in message %d in guild %d."
, message.getIdLong(), event.getChannel().getIdLong(), event.getGuild().getIdLong()));
if(result.getCommand() != null) {
CompletableFuture<CommandParseResult> parsingFuture = getParametersFromMessage(message, result);
parsingFuture.thenAccept(parsedParameters -> {
try {
self.executeCommand(event, parsedParameters.getCommand(), parsedParameters.getParameters());
} catch (Exception e) {
reportException(event, null, e, String.format("Exception when executing command from message %d in message %d in guild %d."
, message.getIdLong(), event.getChannel().getIdLong(), event.getGuild().getIdLong()));
}
});
parsingFuture.exceptionally(throwable -> {
self.reportException(event, result.getCommand(), throwable, "Exception when parsing command.");
return null;
});
} else {
Optional<CommandAlternative> foundAlternativeOptional = commandAlternatives
.stream()
.filter(commandAlternative -> commandAlternative.matches(result.getParameter()))
.findFirst();
if(foundAlternativeOptional.isPresent()) {
CommandAlternative foundAlternative = foundAlternativeOptional.get();
log.info("Found alternative {} to execute for command.", foundAlternative.getClass());
foundAlternative.execute(result.getParameter(), message);
}
});
parsingFuture.exceptionally(throwable -> {
self.reportException(event, result.getCommand(), throwable, "Exception when 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."
, message.getIdLong(), event.getChannel().getIdLong(), event.getGuild().getIdLong()));
@@ -153,7 +169,7 @@ public class CommandReceivedHandler extends ListenerAdapter {
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());
Command foundCommand = commandManager.findCommandByParameters(commandName, unParsedParameter, message.getGuild().getIdLong()).orElse(null);
return UnParsedCommandResult
.builder()
.command(foundCommand)
@@ -224,7 +240,7 @@ public class CommandReceivedHandler extends ListenerAdapter {
schedulerService.executeJobWithParametersOnce("confirmationCleanupJob", "core", jobParameters, Date.from(targetDate));
}
@Transactional
@Transactional(rollbackFor = AbstractoRunTimeException.class)
public void executeCommand(MessageReceivedEvent event, Command foundCommand, Parameters parsedParameters) {
UserInitiatedServerContext userInitiatedContext = buildUserInitiatedServerContext(event);
CommandContext.CommandContextBuilder commandContextBuilder = CommandContext.builder()
@@ -285,7 +301,7 @@ public class CommandReceivedHandler extends ListenerAdapter {
return null;
}
@Transactional(isolation = Isolation.SERIALIZABLE)
@Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = AbstractoRunTimeException.class)
public CompletableFuture<Void> executeAsyncCommand(Command foundCommand, CommandContext commandContext) {
return foundCommand.executeAsync(commandContext).thenAccept(result ->
executePostCommandListener(foundCommand, commandContext, result)
@@ -536,6 +552,7 @@ public class CommandReceivedHandler extends ListenerAdapter {
metricService.registerCounter(COMMANDS_PROCESSED_COUNTER, "Commands processed");
metricService.registerCounter(COMMANDS_WRONG_PARAMETER_COUNTER, "Commands with incorrect parameter");
this.parameterHandlers = parameterHandlers.stream().sorted(comparing(CommandParameterHandler::getPriority)).collect(Collectors.toList());
this.commandAlternatives = commandAlternatives.stream().sorted(comparing(Prioritized::getPriority)).collect(Collectors.toList());
}
@Getter

View File

@@ -41,15 +41,12 @@ public class CommandManager implements CommandRegistry {
private CommandInServerAliasService commandInServerAliasService;
@Override
public Command findCommandByParameters(String name, UnParsedCommandParameter unParsedCommandParameter, Long serverId) {
public Optional<Command> findCommandByParameters(String name, UnParsedCommandParameter unParsedCommandParameter, Long serverId) {
Optional<Command> commandOptional = commands.stream().filter(getCommandByNameAndParameterPredicate(name, unParsedCommandParameter)).findFirst();
if(!commandOptional.isPresent()) {
commandOptional = getCommandViaAliasAndParameter(name, unParsedCommandParameter, serverId);
}
if(commandOptional.isPresent()){
return commandOptional.get();
}
throw new CommandNotFoundException();
return commandOptional;
}
private Optional<Command> getCommandViaAliasAndParameter(String name, UnParsedCommandParameter unParsedCommandParameter, Long serverId) {

View File

@@ -9,6 +9,7 @@ import dev.sheldan.abstracto.core.command.condition.ConditionalCommand;
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.exception.CommandNotFoundException;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.DriedCommandContext;
import dev.sheldan.abstracto.core.command.execution.UnParsedCommandParameter;
@@ -188,7 +189,8 @@ public class CommandServiceBean implements CommandService {
public CompletableFuture<Parameters> getParametersForCommand(String commandName, Message messageContainingContent) {
String contentStripped = messageContainingContent.getContentRaw();
UnParsedCommandParameter unParsedParameter = getUnParsedCommandParameter(contentStripped, messageContainingContent);
Command command = commandRegistry.findCommandByParameters(commandName, unParsedParameter, messageContainingContent.getGuild().getIdLong());
Command command = commandRegistry.findCommandByParameters(commandName, unParsedParameter, messageContainingContent.getGuild().getIdLong())
.orElseThrow(CommandNotFoundException::new);
return commandReceivedHandler.getParsedParameters(unParsedParameter, command, messageContainingContent);
}

View File

@@ -179,7 +179,7 @@ public class InteractionServiceBean implements InteractionService {
if(messageToSend.getEmbeds() != null && !messageToSend.getEmbeds().isEmpty()) {
if(action != null) {
action = action.setEmbeds(messageToSend.getEmbeds().subList(0, 10));
action = action.setEmbeds(messageToSend.getEmbeds().subList(0, Math.min(10, messageToSend.getEmbeds().size())));
} else {
action = interactionHook.editOriginalEmbeds(messageToSend.getEmbeds());
}
@@ -250,7 +250,7 @@ public class InteractionServiceBean implements InteractionService {
if(messageToSend.getEmbeds() != null && !messageToSend.getEmbeds().isEmpty()) {
if(action != null) {
action = action.addEmbeds(messageToSend.getEmbeds().subList(0, 10));
action = action.addEmbeds(messageToSend.getEmbeds().subList(0, Math.min(10, messageToSend.getEmbeds().size())));
} else {
action = callback.replyEmbeds(messageToSend.getEmbeds());
}

View File

@@ -1,9 +1,11 @@
package dev.sheldan.abstracto.core.interaction.slash;
import dev.sheldan.abstracto.core.command.Command;
import dev.sheldan.abstracto.core.command.condition.ConditionResult;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.command.service.CommandService;
import dev.sheldan.abstracto.core.command.service.PostCommandExecution;
import dev.sheldan.abstracto.core.exception.AbstractoRunTimeException;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
@@ -68,23 +70,7 @@ public class SlashCommandListenerBean extends ListenerAdapter {
potentialCommand.ifPresent(command -> {
try {
commandService.isCommandExecutable(command, event).thenAccept(conditionResult -> {
CompletableFuture<CommandResult> commandOutput;
if(conditionResult.isResult()) {
commandOutput = command.executeSlash(event).thenApply(commandResult -> {
log.info("Command {} in server {} was executed.", command.getConfiguration().getName(), event.getGuild().getIdLong());
return commandResult;
});
} else {
commandOutput = CompletableFuture.completedFuture(CommandResult.fromCondition(conditionResult));
}
commandOutput.thenAccept(commandResult -> {
self.executePostCommandListener(command, event, commandResult);
}).exceptionally(throwable -> {
log.error("Error while handling post execution of command {}", command.getConfiguration().getName(), throwable);
CommandResult commandResult = CommandResult.fromError(throwable.getMessage(), throwable);
self.executePostCommandListener(command, event, commandResult);
return null;
});
self.executeCommand(event, command, conditionResult);
}).exceptionally(throwable -> {
log.error("Error while executing command {}", command.getConfiguration().getName(), throwable);
CommandResult commandResult = CommandResult.fromError(throwable.getMessage(), throwable);
@@ -99,6 +85,27 @@ public class SlashCommandListenerBean extends ListenerAdapter {
});
}
@Transactional(rollbackFor = AbstractoRunTimeException.class)
public void executeCommand(SlashCommandInteractionEvent event, Command command, ConditionResult conditionResult) {
CompletableFuture<CommandResult> commandOutput;
if(conditionResult.isResult()) {
commandOutput = command.executeSlash(event).thenApply(commandResult -> {
log.info("Command {} in server {} was executed.", command.getConfiguration().getName(), event.getGuild().getIdLong());
return commandResult;
});
} else {
commandOutput = CompletableFuture.completedFuture(CommandResult.fromCondition(conditionResult));
}
commandOutput.thenAccept(commandResult -> {
self.executePostCommandListener(command, event, commandResult);
}).exceptionally(throwable -> {
log.error("Error while handling post execution of command {}", command.getConfiguration().getName(), throwable);
CommandResult commandResult = CommandResult.fromError(throwable.getMessage(), throwable);
self.executePostCommandListener(command, event, commandResult);
return null;
});
}
@Transactional
public void executePostCommandListener(Command foundCommand, SlashCommandInteractionEvent event, CommandResult result) {
for (PostCommandExecution postCommandExecution : executions) {

View File

@@ -24,6 +24,9 @@ public class InteractiveMessageReceivedListener implements AsyncMessageReceivedL
@Override
public DefaultListenerResult execute(MessageReceivedModel model) {
if(model.getServerId() == null) {
return DefaultListenerResult.IGNORED;
}
if(executeCallback(model)) {
return DefaultListenerResult.PROCESSED;
}

View File

@@ -16,9 +16,7 @@ import dev.sheldan.abstracto.core.service.management.PostTargetManagement;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
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 net.dv8tion.jda.api.entities.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@@ -90,7 +88,11 @@ public class PostTargetSetupStep extends AbstractConfigSetupStep {
log.debug("No mentioned channel was seen in channel, nothing provided.");
throw new NoChannelProvidedException();
}
TextChannel textChannel = (TextChannel) message.getMentions().getMentions(Message.MentionType.CHANNEL).get(0);
IMentionable mentionableChannel = message.getMentions().getMentions(Message.MentionType.CHANNEL).get(0);
if(!(mentionableChannel instanceof GuildMessageChannel)) {
throw new NoChannelProvidedException();
}
GuildChannel textChannel = (GuildChannel) mentionableChannel;
PostTargetDelayedActionConfig build = PostTargetDelayedActionConfig
.builder()
.postTargetKey(postTargetStepParameter.getPostTargetKey())

View File

@@ -30,6 +30,9 @@ public class ListenerServiceBean implements ListenerService {
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public <T extends FeatureAwareListenerModel, R extends ListenerExecutionResult> void executeFeatureAwareListener(FeatureAwareListener<T, R> listener, T model) {
if(model.getServerId() == null) {
return;
}
FeatureConfig feature = featureConfigService.getFeatureDisplayForFeature(listener.getFeature());
if (!featureFlagService.isFeatureEnabled(feature, model.getServerId())) {
return;
@@ -47,6 +50,9 @@ public class ListenerServiceBean implements ListenerService {
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public <T extends FeatureAwareListenerModel, R extends ListenerExecutionResult> CompletableFuture<R> executeAsyncFeatureAwareListener(AsyncFeatureAwareListener<T, R> listener, T model) {
if(model.getServerId() == null) {
return CompletableFuture.completedFuture(null);
}
FeatureConfig feature = featureConfigService.getFeatureDisplayForFeature(listener.getFeature());
if (!featureFlagService.isFeatureEnabled(feature, model.getServerId())) {
return CompletableFuture.completedFuture(null);
@@ -67,6 +73,9 @@ public class ListenerServiceBean implements ListenerService {
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public <T extends FeatureAwareListenerModel, R extends ListenerExecutionResult> void executeFeatureAwareListener(FeatureAwareListener<T, R> listener, T model, TaskExecutor executor) {
if(model.getServerId() == null) {
return;
}
FeatureConfig feature = featureConfigService.getFeatureDisplayForFeature(listener.getFeature());
if (!featureFlagService.isFeatureEnabled(feature, model.getServerId())) {
return;

View File

@@ -12,10 +12,7 @@ import dev.sheldan.abstracto.core.service.management.DefaultPostTargetManagement
import dev.sheldan.abstracto.core.service.management.PostTargetManagement;
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.MessageEmbed;
import net.dv8tion.jda.api.entities.TextChannel;
import net.dv8tion.jda.api.entities.*;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@@ -69,20 +66,20 @@ public class PostTargetServiceBean implements PostTargetService {
log.info("Post target {} has been disabled in server {} - not sending message.", target.getName(), target.getServerReference().getId());
return CompletableFuture.completedFuture(null);
} else {
TextChannel textChannelForPostTarget = getTextChannelForPostTarget(target);
GuildMessageChannel messageChannelForPostTarget = getMessageChannelForPostTarget(target);
log.debug("Sending message embed to post target {}.", target.getName());
return channelService.sendEmbedToChannel(embed, textChannelForPostTarget);
return channelService.sendEmbedToChannel(embed, messageChannelForPostTarget);
}
}
private TextChannel getTextChannelForPostTarget(PostTarget target) {
private GuildMessageChannel getMessageChannelForPostTarget(PostTarget target) {
Guild guild = botService.getInstance().getGuildById(target.getServerReference().getId());
if(guild != null) {
TextChannel textChannelById = guild.getTextChannelById(target.getChannelReference().getId());
if(textChannelById != null) {
return textChannelById;
GuildChannel guildChannelById = guild.getGuildChannelById(target.getChannelReference().getId());
if(guildChannelById instanceof GuildMessageChannel) {
return (GuildMessageChannel) guildChannelById;
} else {
log.error("Incorrect post target configuration: {} points to {} on server {}", target.getName(),
log.error("Incorrect post target configuration (it is not a message channel): {} points to {} on server {}", target.getName(),
target.getChannelReference().getId(), target.getServerReference().getId());
throw new ChannelNotInGuildException(target.getChannelReference().getId());
}
@@ -144,9 +141,9 @@ public class PostTargetServiceBean implements PostTargetService {
log.info("Post target {} has been disabled in server {} - not sending message.", target.getName(), target.getServerReference().getId());
return Arrays.asList(CompletableFuture.completedFuture(null));
} else {
TextChannel textChannelForPostTarget = getTextChannelForPostTarget(target);
GuildMessageChannel guildMessageChannel = getMessageChannelForPostTarget(target);
log.debug("Send messageToSend towards post target {}.", target.getName());
return channelService.sendMessageToSendToChannel(message, textChannelForPostTarget);
return channelService.sendMessageToSendToChannel(message, guildMessageChannel);
}
}
@@ -156,15 +153,15 @@ public class PostTargetServiceBean implements PostTargetService {
log.info("Post target {} has been disabled in server {} - not sending message.", target.getName(), target.getServerReference().getId());
return Arrays.asList(CompletableFuture.completedFuture(null));
} else {
TextChannel textChannelForPostTarget = getTextChannelForPostTarget(target);
GuildMessageChannel guildMessageChannel = getMessageChannelForPostTarget(target);
// always takes the first one, only applicable for this scenario
String messageText = message.getMessages().get(0);
if(StringUtils.isBlank(messageText)) {
log.debug("Editing embeds of message {} in post target {}.", messageId, target.getName());
return Arrays.asList(channelService.editEmbedMessageInAChannel(message.getEmbeds().get(0), textChannelForPostTarget, messageId));
return Arrays.asList(channelService.editEmbedMessageInAChannel(message.getEmbeds().get(0), guildMessageChannel, messageId));
} else {
log.debug("Editing message text and potentially text for message {} in post target {}.", messageId, target.getName());
return Arrays.asList(channelService.editTextMessageInAChannel(messageText, message.getEmbeds().get(0), textChannelForPostTarget, messageId));
return Arrays.asList(channelService.editTextMessageInAChannel(messageText, message.getEmbeds().get(0), guildMessageChannel, messageId));
}
}
}
@@ -176,18 +173,18 @@ public class PostTargetServiceBean implements PostTargetService {
return Arrays.asList(CompletableFuture.completedFuture(null));
} else {
List<CompletableFuture<Message>> futures = new ArrayList<>();
TextChannel textChannelForPostTarget = getTextChannelForPostTarget(target);
GuildMessageChannel guildMessageChannel = getMessageChannelForPostTarget(target);
CompletableFuture<Message> messageEditFuture = new CompletableFuture<>();
futures.add(messageEditFuture);
if (StringUtils.isBlank(messageToSend.getMessages().get(0).trim())) {
channelService.retrieveMessageInChannel(textChannelForPostTarget, messageId).thenAccept(message -> {
channelService.retrieveMessageInChannel(guildMessageChannel, messageId).thenAccept(message -> {
log.debug("Editing existing message {} when upserting message embeds in channel {} in server {}.",
messageId, textChannelForPostTarget.getIdLong(), textChannelForPostTarget.getGuild().getId());
messageId, guildMessageChannel.getIdLong(), guildMessageChannel.getGuild().getId());
messageService.editMessage(message, messageToSend.getEmbeds().get(0))
.queue(messageEditFuture::complete, messageEditFuture::completeExceptionally);
}).exceptionally(throwable -> {
log.debug("Creating new message when upserting message embeds for message {} in channel {} in server {}.",
messageId, textChannelForPostTarget.getIdLong(), textChannelForPostTarget.getGuild().getId());
messageId, guildMessageChannel.getIdLong(), guildMessageChannel.getGuild().getId());
sendEmbedInPostTarget(messageToSend, target).get(0)
.thenAccept(messageEditFuture::complete).exceptionally(innerThrowable -> {
log.error("Failed to send message to create a message.", innerThrowable);
@@ -197,14 +194,14 @@ public class PostTargetServiceBean implements PostTargetService {
return null;
});
} else {
channelService.retrieveMessageInChannel(textChannelForPostTarget, messageId).thenAccept(message -> {
channelService.retrieveMessageInChannel(guildMessageChannel, messageId).thenAccept(message -> {
log.debug("Editing existing message {} when upserting message in channel {} in server {}.",
messageId, textChannelForPostTarget.getIdLong(), textChannelForPostTarget.getGuild().getId());
messageId, guildMessageChannel.getIdLong(), guildMessageChannel.getGuild().getId());
messageService.editMessage(message, messageToSend.getMessages().get(0), messageToSend.getEmbeds().get(0))
.queue(messageEditFuture::complete, messageEditFuture::completeExceptionally);
}).exceptionally(throwable -> {
log.debug("Creating new message when trying to upsert a message {} in channel {} in server {}.",
messageId, textChannelForPostTarget.getIdLong(), textChannelForPostTarget.getGuild().getId());
messageId, guildMessageChannel.getIdLong(), guildMessageChannel.getGuild().getId());
sendEmbedInPostTarget(messageToSend, target).get(0)
.thenAccept(messageEditFuture::complete).exceptionally(innerThrowable -> {
log.error("Failed to send message to create a message.", innerThrowable);

View File

@@ -90,11 +90,11 @@ public class StartupServiceBean implements Startup {
log.info("Executing startup listener {}.", asyncStartupListener);
self.executeStartupListener(asyncStartupListener);
} catch (Exception e) {
log.error("Startup listener {} failed.", asyncStartupListener);
log.error("Startup listener {} failed.", asyncStartupListener, e);
}
}).thenAccept(unused -> log.info("Startup listener {} finished.", asyncStartupListener))
.exceptionally(throwable -> {
log.error("Startup listener {} failed.", asyncStartupListener);
log.error("Startup listener {} failed.", asyncStartupListener, throwable);
return null;
})
);