mirror of
https://github.com/Sheldan/abstracto.git
synced 2026-01-01 23:35:29 +00:00
[AB-xxx] adding feature to suggest slash commands for message commands, if the command is slash command only
This commit is contained in:
@@ -182,10 +182,8 @@ public class CommandReceivedHandler extends ListenerAdapter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public UnParsedCommandResult getUnparsedCommandResult(Message message) {
|
public UnParsedCommandResult getUnparsedCommandResult(Message message) {
|
||||||
String contentStripped = message.getContentRaw();
|
String commandName = getCommandName(message);
|
||||||
List<String> parameters = Arrays.asList(contentStripped.split(" "));
|
UnParsedCommandParameter unParsedParameter = new UnParsedCommandParameter(message.getContentRaw(), message);
|
||||||
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()).orElse(null);
|
Command foundCommand = commandManager.findCommandByParameters(commandName, unParsedParameter, message.getGuild().getIdLong()).orElse(null);
|
||||||
return UnParsedCommandResult
|
return UnParsedCommandResult
|
||||||
.builder()
|
.builder()
|
||||||
@@ -194,13 +192,9 @@ public class CommandReceivedHandler extends ListenerAdapter {
|
|||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public CompletableFuture<CommandParseResult> getParametersFromMessage(Message message) {
|
public String getCommandName(Message message) {
|
||||||
UnParsedCommandResult result = getUnparsedCommandResult(message);
|
List<String> parameters = Arrays.asList( message.getContentRaw().split(" "));
|
||||||
return getParsedParameters(result.getParameter(), result.getCommand(), message).thenApply(foundParameters -> CommandParseResult
|
return commandManager.getCommandName(parameters.get(0), message.getGuild().getIdLong());
|
||||||
.builder()
|
|
||||||
.command(result.getCommand())
|
|
||||||
.parameters(foundParameters)
|
|
||||||
.build());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public CompletableFuture<CommandParseResult> getParametersFromMessage(Message message, UnParsedCommandResult result) {
|
public CompletableFuture<CommandParseResult> getParametersFromMessage(Message message, UnParsedCommandResult result) {
|
||||||
|
|||||||
@@ -0,0 +1,110 @@
|
|||||||
|
package dev.sheldan.abstracto.core.commands.utility;
|
||||||
|
|
||||||
|
import dev.sheldan.abstracto.core.command.Command;
|
||||||
|
import dev.sheldan.abstracto.core.command.CommandAlternative;
|
||||||
|
import dev.sheldan.abstracto.core.command.CommandReceivedHandler;
|
||||||
|
import dev.sheldan.abstracto.core.command.config.features.CoreFeatureDefinition;
|
||||||
|
import dev.sheldan.abstracto.core.command.config.features.CoreFeatureMode;
|
||||||
|
import dev.sheldan.abstracto.core.command.execution.UnParsedCommandParameter;
|
||||||
|
import dev.sheldan.abstracto.core.command.service.CommandManager;
|
||||||
|
import dev.sheldan.abstracto.core.config.FeatureMode;
|
||||||
|
import dev.sheldan.abstracto.core.config.ListenerPriority;
|
||||||
|
import dev.sheldan.abstracto.core.models.template.commands.SlashCommandSuggestionModel;
|
||||||
|
import dev.sheldan.abstracto.core.service.ChannelService;
|
||||||
|
import dev.sheldan.abstracto.core.service.FeatureFlagService;
|
||||||
|
import dev.sheldan.abstracto.core.service.FeatureModeService;
|
||||||
|
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
|
||||||
|
import dev.sheldan.abstracto.core.templating.service.TemplateService;
|
||||||
|
import dev.sheldan.abstracto.core.utils.FutureUtils;
|
||||||
|
import java.util.Optional;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import net.dv8tion.jda.api.entities.Guild;
|
||||||
|
import net.dv8tion.jda.api.entities.Message;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
public class SlashCommandSuggestor implements CommandAlternative {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private FeatureModeService featureModeService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private CommandManager commandManager;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private CommandReceivedHandler commandReceivedHandler;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private FeatureFlagService featureFlagService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private TemplateService templateService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ChannelService channelService;
|
||||||
|
|
||||||
|
public static final String SUGGESTION_TEMPLATE_KEY = "slash_command_suggestion";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Integer getPriority() {
|
||||||
|
return ListenerPriority.MEDIUM;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldExecute(UnParsedCommandParameter parameter, Guild guild, Message message) {
|
||||||
|
boolean featureModeActive = featureModeService.featureModeActive(CoreFeatureDefinition.CORE_FEATURE, guild.getIdLong(), CoreFeatureMode.SUGGEST_SLASH_COMMANDS);
|
||||||
|
if(!featureModeActive) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String commandName = commandReceivedHandler.getCommandName(message);
|
||||||
|
Long guildId = message.getGuildIdLong();
|
||||||
|
Optional<Command> potentialCommand = commandManager.getCommandByNameOptional(commandName, true, guildId);
|
||||||
|
return potentialCommand.isPresent() && potentialCommand.get().getConfiguration().isSlashCommandOnly();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(UnParsedCommandParameter parameter, Message message) {
|
||||||
|
String commandName = commandReceivedHandler.getCommandName(message);
|
||||||
|
Long guildId = message.getGuildIdLong();
|
||||||
|
Optional<Command> potentialCommand = commandManager.getCommandByNameOptional(commandName, true, guildId);
|
||||||
|
// limitation to not check conditions if command is executable, I dont want to completely built the entire command context, as that would require
|
||||||
|
// to parse the parameters, therefore the major checks should suffice
|
||||||
|
if(potentialCommand.isPresent()) {
|
||||||
|
Command command = potentialCommand.get();
|
||||||
|
if(command.getConfiguration().isSlashCommandOnly()) {
|
||||||
|
boolean featureAvailable = featureFlagService.getFeatureFlagValue(command.getFeature(), guildId);
|
||||||
|
if(featureAvailable) {
|
||||||
|
boolean shouldNotifyUser = command.getFeatureModeLimitations().isEmpty();
|
||||||
|
for (FeatureMode featureModeLimitation : command.getFeatureModeLimitations()) {
|
||||||
|
if(featureModeService.featureModeActive(command.getFeature(), guildId, featureModeLimitation)) {
|
||||||
|
shouldNotifyUser = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(shouldNotifyUser) {
|
||||||
|
notifyUser(message, command, commandName, guildId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notifyUser(Message message, Command command, String commandName, Long guildId) {
|
||||||
|
String path = command.getConfiguration().getSlashCommandConfig().getSlashCommandPath();
|
||||||
|
SlashCommandSuggestionModel model = SlashCommandSuggestionModel
|
||||||
|
.builder()
|
||||||
|
.slashCommandPath(path)
|
||||||
|
.build();
|
||||||
|
Long userId = message.getAuthor().getIdLong();
|
||||||
|
log.info("Suggesting slash command for command {} to user {}.", commandName, userId);
|
||||||
|
MessageToSend messageToSend = templateService.renderEmbedTemplate(SUGGESTION_TEMPLATE_KEY, model, guildId);
|
||||||
|
FutureUtils.toSingleFutureGeneric(channelService.sendMessageToSendToChannel(messageToSend, message.getChannel()))
|
||||||
|
.thenAccept(unused -> {
|
||||||
|
log.debug("Successfully suggested command.");
|
||||||
|
}).exceptionally(throwable -> {
|
||||||
|
log.warn("Failed to suggest slash command for command {} to user {}", commandName, userId);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,6 +21,10 @@ abstracto.systemConfigs.confirmationTimeout.longValue=120
|
|||||||
abstracto.systemConfigs.maxMessages.name=maxMessages
|
abstracto.systemConfigs.maxMessages.name=maxMessages
|
||||||
abstracto.systemConfigs.maxMessages.longValue=3
|
abstracto.systemConfigs.maxMessages.longValue=3
|
||||||
|
|
||||||
|
abstracto.featureModes.suggestSlashCommands.featureName=core
|
||||||
|
abstracto.featureModes.suggestSlashCommands.mode=suggestSlashCommands
|
||||||
|
abstracto.featureModes.suggestSlashCommands.enabled=true
|
||||||
|
|
||||||
abstracto.featureFlags.core.featureName=core
|
abstracto.featureFlags.core.featureName=core
|
||||||
abstracto.featureFlags.core.enabled=true
|
abstracto.featureFlags.core.enabled=true
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,169 @@
|
|||||||
|
package dev.sheldan.abstracto.core.commands.utility;
|
||||||
|
|
||||||
|
import static dev.sheldan.abstracto.core.commands.utility.SlashCommandSuggestor.SUGGESTION_TEMPLATE_KEY;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.times;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import dev.sheldan.abstracto.core.command.Command;
|
||||||
|
import dev.sheldan.abstracto.core.command.CommandReceivedHandler;
|
||||||
|
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
|
||||||
|
import dev.sheldan.abstracto.core.command.config.features.CoreFeatureDefinition;
|
||||||
|
import dev.sheldan.abstracto.core.command.config.features.CoreFeatureMode;
|
||||||
|
import dev.sheldan.abstracto.core.command.service.CommandManager;
|
||||||
|
import dev.sheldan.abstracto.core.config.FeatureMode;
|
||||||
|
import dev.sheldan.abstracto.core.service.ChannelService;
|
||||||
|
import dev.sheldan.abstracto.core.service.FeatureFlagService;
|
||||||
|
import dev.sheldan.abstracto.core.service.FeatureModeService;
|
||||||
|
import dev.sheldan.abstracto.core.templating.service.TemplateService;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Optional;
|
||||||
|
import net.dv8tion.jda.api.entities.Guild;
|
||||||
|
import net.dv8tion.jda.api.entities.Message;
|
||||||
|
import net.dv8tion.jda.api.entities.User;
|
||||||
|
import net.dv8tion.jda.api.entities.channel.unions.MessageChannelUnion;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.Mockito;
|
||||||
|
import org.mockito.junit.MockitoJUnitRunner;
|
||||||
|
|
||||||
|
@RunWith(MockitoJUnitRunner.class)
|
||||||
|
public class SlashCommandSuggestorTest {
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private SlashCommandSuggestor unitUnderTest;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private FeatureModeService featureModeService;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private CommandReceivedHandler commandReceivedHandler;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private CommandManager commandManager;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private FeatureFlagService featureFlagService;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private TemplateService templateService;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private ChannelService channelService;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private Guild guild;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private Message message;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private Command command;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private FeatureMode featureMode;
|
||||||
|
|
||||||
|
private static final Long SERVER_ID = 1L;
|
||||||
|
private static final String COMMAND_NAME = "commandName";
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setup() {
|
||||||
|
when(guild.getIdLong()).thenReturn(SERVER_ID);
|
||||||
|
when(message.getGuildIdLong()).thenReturn(SERVER_ID);
|
||||||
|
when(message.getAuthor()).thenReturn(Mockito.mock(User.class));
|
||||||
|
when(message.getChannel()).thenReturn(Mockito.mock(MessageChannelUnion.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldNotExecute_DueToFeatureMode() {
|
||||||
|
when(featureModeService.featureModeActive(CoreFeatureDefinition.CORE_FEATURE, SERVER_ID, CoreFeatureMode.SUGGEST_SLASH_COMMANDS)).thenReturn(false);
|
||||||
|
boolean shouldExecute = unitUnderTest.shouldExecute(null, guild, message);
|
||||||
|
assertThat(shouldExecute).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldNotExecute_DueToNotFoundCommand() {
|
||||||
|
when(featureModeService.featureModeActive(CoreFeatureDefinition.CORE_FEATURE, SERVER_ID, CoreFeatureMode.SUGGEST_SLASH_COMMANDS)).thenReturn(true);
|
||||||
|
commandFound(null);
|
||||||
|
boolean shouldExecute = unitUnderTest.shouldExecute(null, guild, message);
|
||||||
|
assertThat(shouldExecute).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldNotExecute_DueToFoundCommandWhichIsNotSlashCommandOnly() {
|
||||||
|
when(featureModeService.featureModeActive(CoreFeatureDefinition.CORE_FEATURE, SERVER_ID, CoreFeatureMode.SUGGEST_SLASH_COMMANDS)).thenReturn(true);
|
||||||
|
commandSetup(false);
|
||||||
|
boolean shouldExecute = unitUnderTest.shouldExecute(null, guild, message);
|
||||||
|
assertThat(shouldExecute).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldExecute_DueToFoundCommandWhichIsSlashCommandOnly() {
|
||||||
|
when(featureModeService.featureModeActive(CoreFeatureDefinition.CORE_FEATURE, SERVER_ID, CoreFeatureMode.SUGGEST_SLASH_COMMANDS)).thenReturn(true);
|
||||||
|
commandSetup(true);
|
||||||
|
boolean shouldExecute = unitUnderTest.shouldExecute(null, guild, message);
|
||||||
|
assertThat(shouldExecute).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldNotFindCommand() {
|
||||||
|
commandFound(null);
|
||||||
|
unitUnderTest.execute(null, message);
|
||||||
|
verify(templateService, times(0)).renderEmbedTemplate(eq(SUGGESTION_TEMPLATE_KEY), any(), any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void foundCommandIsNotSlashCommandOnly() {
|
||||||
|
commandSetup(false);
|
||||||
|
unitUnderTest.execute(null, message);
|
||||||
|
verify(templateService, times(0)).renderEmbedTemplate(eq(SUGGESTION_TEMPLATE_KEY), any(), any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void featureNotEnabled() {
|
||||||
|
commandSetup(true);
|
||||||
|
when(featureFlagService.getFeatureFlagValue(any(), eq(SERVER_ID))).thenReturn(false);
|
||||||
|
unitUnderTest.execute(null, message);
|
||||||
|
verify(templateService, times(0)).renderEmbedTemplate(eq(SUGGESTION_TEMPLATE_KEY), any(), any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void noFeatureModesAvailable() {
|
||||||
|
commandSetup(true);
|
||||||
|
when(command.getFeatureModeLimitations()).thenReturn(new ArrayList<>());
|
||||||
|
when(featureFlagService.getFeatureFlagValue(any(), eq(SERVER_ID))).thenReturn(true);
|
||||||
|
unitUnderTest.execute(null, message);
|
||||||
|
verify(templateService).renderEmbedTemplate(eq(SUGGESTION_TEMPLATE_KEY), any(), any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void featureModesAvailable() {
|
||||||
|
commandSetup(true);
|
||||||
|
when(command.getFeatureModeLimitations()).thenReturn(Collections.singletonList(featureMode));
|
||||||
|
when(featureFlagService.getFeatureFlagValue(any(), eq(SERVER_ID))).thenReturn(true);
|
||||||
|
when(featureModeService.featureModeActive(any(), eq(SERVER_ID), any())).thenReturn(true);
|
||||||
|
unitUnderTest.execute(null, message);
|
||||||
|
verify(templateService).renderEmbedTemplate(eq(SUGGESTION_TEMPLATE_KEY), any(), any());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void commandSetup(boolean slashCommandOnly) {
|
||||||
|
commandFound(command);
|
||||||
|
CommandConfiguration commandConfiguration = CommandConfiguration
|
||||||
|
.builder()
|
||||||
|
.slashCommandOnly(slashCommandOnly)
|
||||||
|
.build();
|
||||||
|
when(command.getConfiguration()).thenReturn(commandConfiguration);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void commandFound(Command command) {
|
||||||
|
when(commandReceivedHandler.getCommandName(message)).thenReturn(COMMAND_NAME);
|
||||||
|
when(commandManager.getCommandByNameOptional(COMMAND_NAME, true, SERVER_ID)).thenReturn(Optional.ofNullable(command));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package dev.sheldan.abstracto.core.command.config.features;
|
||||||
|
|
||||||
|
import dev.sheldan.abstracto.core.config.FeatureMode;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public enum CoreFeatureMode implements FeatureMode {
|
||||||
|
SUGGEST_SLASH_COMMANDS("suggestSlashCommands");
|
||||||
|
|
||||||
|
private final String key;
|
||||||
|
|
||||||
|
CoreFeatureMode(String key) {
|
||||||
|
this.key = key;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -32,12 +32,12 @@ public class UnParsedCommandParameter {
|
|||||||
}
|
}
|
||||||
if (m.group(1) != null) {
|
if (m.group(1) != null) {
|
||||||
String group = m.group(1);
|
String group = m.group(1);
|
||||||
if(!group.equals("")) {
|
if(!group.isEmpty()) {
|
||||||
this.parameters.add(UnparsedCommandParameterPiece.builder().value(group).build());
|
this.parameters.add(UnparsedCommandParameterPiece.builder().value(group).build());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
String group = m.group(2);
|
String group = m.group(2);
|
||||||
if(!group.equals("")) {
|
if(!group.isEmpty()) {
|
||||||
this.parameters.add(UnparsedCommandParameterPiece.builder().value(group).build());
|
this.parameters.add(UnparsedCommandParameterPiece.builder().value(group).build());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,10 @@ package dev.sheldan.abstracto.core.interaction.slash;
|
|||||||
|
|
||||||
import dev.sheldan.abstracto.core.command.config.UserCommandConfig;
|
import dev.sheldan.abstracto.core.command.config.UserCommandConfig;
|
||||||
import dev.sheldan.abstracto.core.utils.ContextUtils;
|
import dev.sheldan.abstracto.core.utils.ContextUtils;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
@@ -67,4 +71,13 @@ public class SlashCommandConfig {
|
|||||||
public String getUserSlashCompatibleCommandName() {
|
public String getUserSlashCompatibleCommandName() {
|
||||||
return userCommandName != null ? userCommandName.toLowerCase(Locale.ROOT) : null;
|
return userCommandName != null ? userCommandName.toLowerCase(Locale.ROOT) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getSlashCommandPath() {
|
||||||
|
String root = getSlashCompatibleRootName();
|
||||||
|
String group = getSlashCompatibleGroupName();
|
||||||
|
String command = getSlashCompatibleCommandName();
|
||||||
|
return Stream.of(root, group, command)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.collect(Collectors.joining(" "));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package dev.sheldan.abstracto.core.models.template.commands;
|
||||||
|
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Builder
|
||||||
|
public class SlashCommandSuggestionModel {
|
||||||
|
private String slashCommandPath;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user