diff --git a/lib/src/main/java/net/tomatentum/marinara/interaction/annotation/AutoComplete.java b/lib/src/main/java/net/tomatentum/marinara/interaction/annotation/AutoComplete.java new file mode 100644 index 0000000..46aa6aa --- /dev/null +++ b/lib/src/main/java/net/tomatentum/marinara/interaction/annotation/AutoComplete.java @@ -0,0 +1,12 @@ +package net.tomatentum.marinara.interaction.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface AutoComplete { + +} diff --git a/lib/src/main/java/net/tomatentum/marinara/interaction/commands/ChoiceValueProvider.java b/lib/src/main/java/net/tomatentum/marinara/interaction/commands/ChoiceValueProvider.java new file mode 100644 index 0000000..b7d13f6 --- /dev/null +++ b/lib/src/main/java/net/tomatentum/marinara/interaction/commands/ChoiceValueProvider.java @@ -0,0 +1,5 @@ +package net.tomatentum.marinara.interaction.commands; + +public interface ChoiceValueProvider { + T getChoiceValue(); +} diff --git a/lib/src/main/java/net/tomatentum/marinara/interaction/commands/EnumChoices.java b/lib/src/main/java/net/tomatentum/marinara/interaction/commands/EnumChoices.java new file mode 100644 index 0000000..58d82d9 --- /dev/null +++ b/lib/src/main/java/net/tomatentum/marinara/interaction/commands/EnumChoices.java @@ -0,0 +1,78 @@ +package net.tomatentum.marinara.interaction.commands; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import io.leangen.geantyref.AnnotationFormatException; +import io.leangen.geantyref.GenericTypeReflector; +import io.leangen.geantyref.TypeFactory; +import net.tomatentum.marinara.interaction.commands.annotation.SlashCommandOptionChoice; + +public record EnumChoices(Class> enumClass, ChoiceType type, SlashCommandOptionChoice[] choices) { + + private static Method method; + + static { + try { + method = ChoiceValueProvider.class.getMethod("getChoiceValue"); + } catch (NoSuchMethodException | SecurityException e) { + e.printStackTrace(); + } + } + + public static EnumChoices of(Class> enumClass) { + if (!ChoiceValueProvider.class.isAssignableFrom(enumClass)) + throw new IllegalArgumentException("Provided class needs to implement the ChoiceValueProvider interface."); + ChoiceType type = parseChoiceType(enumClass); + SlashCommandOptionChoice[] choices = parseChoices(enumClass, type); + return new EnumChoices(enumClass, type, choices); + } + + private static ChoiceType parseChoiceType(Class> enumClass) { + ParameterizedType type = (ParameterizedType) GenericTypeReflector.getExactSuperType(enumClass, ChoiceValueProvider.class); + Type typeParam = type.getActualTypeArguments()[0]; + + if (!(typeParam instanceof Class)) + throw new IllegalArgumentException("ChoiceValueProvider need either a String or Number type parameter."); + + if (Long.class.isAssignableFrom((Class) typeParam)) + return ChoiceType.INTEGER; + if (Double.class.isAssignableFrom((Class) typeParam)) + return ChoiceType.DOUBLE; + if (String.class.isAssignableFrom((Class) typeParam)) + return ChoiceType.String; + throw new IllegalArgumentException("ChoiceValueProvider need either a String, Number or Decimal type parameter."); + } + + private static SlashCommandOptionChoice[] parseChoices(Class> enumClass, ChoiceType type) { + Enum>[] constants = enumClass.getEnumConstants(); + List choices = new ArrayList<>(); + for (Enum> enumInstance : constants) { + Object value; + try { + value = method.invoke(enumInstance); + if (type.equals(ChoiceType.INTEGER)) + choices.add(TypeFactory.annotation(SlashCommandOptionChoice.class, Map.of("name", enumInstance.name(), "longValue", value))); + if (type.equals(ChoiceType.DOUBLE)) + choices.add(TypeFactory.annotation(SlashCommandOptionChoice.class, Map.of("name", enumInstance.name(), "doubleValue", value))); + if (type.equals(ChoiceType.String)) + choices.add(TypeFactory.annotation(SlashCommandOptionChoice.class, Map.of("name", enumInstance.name(), "stringValue", value))); + } catch (IllegalAccessException | InvocationTargetException | AnnotationFormatException e) { + e.printStackTrace(); + return null; + } + } + return choices.toArray(new SlashCommandOptionChoice[0]); + } + + public static enum ChoiceType { + String, + INTEGER, + DOUBLE + } +} diff --git a/lib/src/main/java/net/tomatentum/marinara/interaction/commands/ExecutableSlashCommandDefinition.java b/lib/src/main/java/net/tomatentum/marinara/interaction/commands/ExecutableSlashCommandDefinition.java index e110098..f0b551c 100644 --- a/lib/src/main/java/net/tomatentum/marinara/interaction/commands/ExecutableSlashCommandDefinition.java +++ b/lib/src/main/java/net/tomatentum/marinara/interaction/commands/ExecutableSlashCommandDefinition.java @@ -2,8 +2,10 @@ package net.tomatentum.marinara.interaction.commands; import net.tomatentum.marinara.interaction.commands.annotation.SlashCommand; import net.tomatentum.marinara.interaction.commands.annotation.SlashCommandOption; +import net.tomatentum.marinara.interaction.commands.annotation.SlashCommandOptionChoice; import net.tomatentum.marinara.interaction.commands.annotation.SubCommand; import net.tomatentum.marinara.interaction.commands.annotation.SubCommandGroup; +import net.tomatentum.marinara.interaction.commands.annotation.SlashCommandOption.PlaceHolderEnum; public record ExecutableSlashCommandDefinition( SlashCommand applicationCommand, @@ -11,6 +13,13 @@ public record ExecutableSlashCommandDefinition( SubCommandGroup subCommandGroup, SlashCommandOption[] options) { + public static SlashCommandOptionChoice[] getActualChoices(SlashCommandOption option) { + SlashCommandOptionChoice[] choices = option.choices(); + if (choices.length <= 0 && !option.choiceEnum().equals(PlaceHolderEnum.class)) + choices = EnumChoices.of(option.choiceEnum()).choices(); + return choices; + } + @Override public final boolean equals(Object o) { if (!(o instanceof ExecutableSlashCommandDefinition)) @@ -18,8 +27,8 @@ public record ExecutableSlashCommandDefinition( ExecutableSlashCommandDefinition other = (ExecutableSlashCommandDefinition) o; boolean equals = false; - if (this.applicationCommand() != null && other.subCommandGroup() != null) - equals = this.applicationCommand.name().equals(other.applicationCommand().name()); + if (this.applicationCommand() != null && other.applicationCommand() != null) + equals = this.applicationCommand().name().equals(other.applicationCommand().name()); if (this.subCommandGroup() != null && other.subCommandGroup() != null) equals = this.subCommandGroup().name().equals(other.subCommandGroup().name()); diff --git a/lib/src/main/java/net/tomatentum/marinara/interaction/commands/annotation/SlashCommandOption.java b/lib/src/main/java/net/tomatentum/marinara/interaction/commands/annotation/SlashCommandOption.java index 09574d3..d7b845f 100644 --- a/lib/src/main/java/net/tomatentum/marinara/interaction/commands/annotation/SlashCommandOption.java +++ b/lib/src/main/java/net/tomatentum/marinara/interaction/commands/annotation/SlashCommandOption.java @@ -14,4 +14,11 @@ public @interface SlashCommandOption { public String description() default ""; public SlashCommandOptionType type() default SlashCommandOptionType.STRING; public boolean required() default false; + public boolean autocomplete() default false; + public SlashCommandOptionChoice[] choices() default {}; + public Class> choiceEnum() default PlaceHolderEnum.class; + + public static enum PlaceHolderEnum { + + } } diff --git a/lib/src/main/java/net/tomatentum/marinara/interaction/commands/annotation/SlashCommandOptionChoice.java b/lib/src/main/java/net/tomatentum/marinara/interaction/commands/annotation/SlashCommandOptionChoice.java new file mode 100644 index 0000000..9ccd3e0 --- /dev/null +++ b/lib/src/main/java/net/tomatentum/marinara/interaction/commands/annotation/SlashCommandOptionChoice.java @@ -0,0 +1,8 @@ +package net.tomatentum.marinara.interaction.commands.annotation; + +public @interface SlashCommandOptionChoice { + public String name(); + public long longValue() default Long.MAX_VALUE; + public double doubleValue() default Double.MAX_VALUE; + public String stringValue() default ""; +} diff --git a/lib/src/main/java/net/tomatentum/marinara/interaction/commands/option/SlashCommandOptionType.java b/lib/src/main/java/net/tomatentum/marinara/interaction/commands/option/SlashCommandOptionType.java index db222c8..ce80c6f 100644 --- a/lib/src/main/java/net/tomatentum/marinara/interaction/commands/option/SlashCommandOptionType.java +++ b/lib/src/main/java/net/tomatentum/marinara/interaction/commands/option/SlashCommandOptionType.java @@ -1,16 +1,26 @@ package net.tomatentum.marinara.interaction.commands.option; public enum SlashCommandOptionType { - ATTACHMENT, - BOOLEAN, - CHANNEL, - DECIMAL, - LONG, - MENTIONABLE, - ROLE, - STRING, - SUB_COMMAND, - SUB_COMMAND_GROUP, - UNKNOW, - USER + SUB_COMMAND(1), + SUB_COMMAND_GROUP(2), + STRING(3), + INTEGER(4), + BOOLEAN(5), + USER(6), + CHANNEL(7), + ROLE(8), + MENTIONABLE(9), + DOUBLE(10), + ATTACHMENT(11), + UNKNOWN(-1); + + private final int value; + + private SlashCommandOptionType(int value) { + this.value = value; + } + + public int getValue() { + return value; + } } diff --git a/lib/src/main/java/net/tomatentum/marinara/interaction/methods/AutoCompleteInteractionMethod.java b/lib/src/main/java/net/tomatentum/marinara/interaction/methods/AutoCompleteInteractionMethod.java new file mode 100644 index 0000000..ffed507 --- /dev/null +++ b/lib/src/main/java/net/tomatentum/marinara/interaction/methods/AutoCompleteInteractionMethod.java @@ -0,0 +1,51 @@ +package net.tomatentum.marinara.interaction.methods; + +import java.lang.reflect.Method; + +import net.tomatentum.marinara.Marinara; +import net.tomatentum.marinara.interaction.InteractionHandler; +import net.tomatentum.marinara.interaction.InteractionType; +import net.tomatentum.marinara.interaction.commands.ExecutableSlashCommandDefinition; +import net.tomatentum.marinara.parser.AnnotationParser; +import net.tomatentum.marinara.parser.SlashCommandParser; + +public class AutoCompleteInteractionMethod extends InteractionMethod { + + private ExecutableSlashCommandDefinition commandDefinition; + + public AutoCompleteInteractionMethod(Method method, + InteractionHandler handler, + Marinara marinara + ) { + super(method, handler, marinara); + } + + @Override + public AnnotationParser[] getParsers() { + return new AnnotationParser[] { + new SlashCommandParser(method, (x) -> { this.commandDefinition = x; } ) + }; + } + + @Override + public Object getParameter(Object context, int index) { + Class type = getMethod().getParameterTypes()[index+1]; + Object autocompleteOptionValue = marinara.getWrapper().getContextObjectProvider().getAutocompleteFocusedOption(context); + if (autocompleteOptionValue != null) + return autocompleteOptionValue; + + return marinara.getWrapper().getContextObjectProvider().getComponentContextObject(context, type); + } + + @Override + public boolean canRun(Object context) { + ExecutableSlashCommandDefinition other = marinara.getWrapper().getCommandDefinition(context); + return commandDefinition.equals(other); + } + + @Override + public InteractionType getType() { + return InteractionType.AUTOCOMPLETE; + } + +} diff --git a/lib/src/main/java/net/tomatentum/marinara/interaction/methods/ButtonInteractionMethod.java b/lib/src/main/java/net/tomatentum/marinara/interaction/methods/ButtonInteractionMethod.java index f6b2a9e..fe039ac 100644 --- a/lib/src/main/java/net/tomatentum/marinara/interaction/methods/ButtonInteractionMethod.java +++ b/lib/src/main/java/net/tomatentum/marinara/interaction/methods/ButtonInteractionMethod.java @@ -24,9 +24,9 @@ public class ButtonInteractionMethod extends InteractionMethod { } @Override - public Object getParameter(Object parameter, int index) { + public Object getParameter(Object context, int index) { Class type = getMethod().getParameterTypes()[index+1]; - return marinara.getWrapper().getComponentContextObject(parameter, type); + return marinara.getWrapper().getContextObjectProvider().getComponentContextObject(context, type); } @Override @@ -38,4 +38,5 @@ public class ButtonInteractionMethod extends InteractionMethod { public InteractionType getType() { return InteractionType.BUTTON; } + } diff --git a/lib/src/main/java/net/tomatentum/marinara/interaction/methods/InteractionMethod.java b/lib/src/main/java/net/tomatentum/marinara/interaction/methods/InteractionMethod.java index f9c5a1c..20fec5f 100644 --- a/lib/src/main/java/net/tomatentum/marinara/interaction/methods/InteractionMethod.java +++ b/lib/src/main/java/net/tomatentum/marinara/interaction/methods/InteractionMethod.java @@ -11,6 +11,7 @@ import net.tomatentum.marinara.Marinara; import net.tomatentum.marinara.checks.AppliedCheck; import net.tomatentum.marinara.interaction.InteractionHandler; import net.tomatentum.marinara.interaction.InteractionType; +import net.tomatentum.marinara.interaction.annotation.AutoComplete; import net.tomatentum.marinara.interaction.annotation.Button; import net.tomatentum.marinara.interaction.commands.annotation.SlashCommand; import net.tomatentum.marinara.interaction.commands.annotation.SubCommand; @@ -20,6 +21,8 @@ import net.tomatentum.marinara.parser.InteractionCheckParser; public abstract class InteractionMethod { public static InteractionMethod create(Method method, InteractionHandler handler, Marinara marinara) { + if (method.isAnnotationPresent(AutoComplete.class)) + return new AutoCompleteInteractionMethod(method, handler, marinara); if (method.isAnnotationPresent(SlashCommand.class) || method.isAnnotationPresent(SubCommand.class)) return new SlashCommandInteractionMethod(method, handler, marinara); if (method.isAnnotationPresent(Button.class)) @@ -53,7 +56,7 @@ public abstract class InteractionMethod { public abstract AnnotationParser[] getParsers(); - public abstract Object getParameter(Object parameter, int index); + public abstract Object getParameter(Object context, int index); public abstract boolean canRun(Object context); diff --git a/lib/src/main/java/net/tomatentum/marinara/interaction/methods/SlashCommandInteractionMethod.java b/lib/src/main/java/net/tomatentum/marinara/interaction/methods/SlashCommandInteractionMethod.java index 666dd3c..2f5faf4 100644 --- a/lib/src/main/java/net/tomatentum/marinara/interaction/methods/SlashCommandInteractionMethod.java +++ b/lib/src/main/java/net/tomatentum/marinara/interaction/methods/SlashCommandInteractionMethod.java @@ -26,7 +26,7 @@ public class SlashCommandInteractionMethod extends InteractionMethod { @Override public Object getParameter(Object context, int index) { - return marinara.getWrapper().convertCommandOption(context, commandDefinition.options()[index].type(), commandDefinition.options()[index].name()); + return marinara.getWrapper().getContextObjectProvider().convertCommandOption(context, commandDefinition.options()[index].name()); } @Override diff --git a/lib/src/main/java/net/tomatentum/marinara/wrapper/ContextObjectProvider.java b/lib/src/main/java/net/tomatentum/marinara/wrapper/ContextObjectProvider.java new file mode 100644 index 0000000..27cbaa4 --- /dev/null +++ b/lib/src/main/java/net/tomatentum/marinara/wrapper/ContextObjectProvider.java @@ -0,0 +1,11 @@ +package net.tomatentum.marinara.wrapper; + +public interface ContextObjectProvider { + + public Object convertCommandOption(Object context, String optionName); + + public Object getComponentContextObject(Object context, Class type); + public Object getInteractionContextObject(Object context, Class type); + + public Object getAutocompleteFocusedOption(Object context); +} diff --git a/lib/src/main/java/net/tomatentum/marinara/wrapper/LibraryWrapper.java b/lib/src/main/java/net/tomatentum/marinara/wrapper/LibraryWrapper.java index 4fe8ef9..06e1661 100644 --- a/lib/src/main/java/net/tomatentum/marinara/wrapper/LibraryWrapper.java +++ b/lib/src/main/java/net/tomatentum/marinara/wrapper/LibraryWrapper.java @@ -6,7 +6,6 @@ import java.util.function.Consumer; import net.tomatentum.marinara.interaction.commands.SlashCommandDefinition; import net.tomatentum.marinara.interaction.commands.ExecutableSlashCommandDefinition; -import net.tomatentum.marinara.interaction.commands.option.SlashCommandOptionType; import net.tomatentum.marinara.interaction.InteractionType; public abstract class LibraryWrapper { @@ -17,7 +16,6 @@ public abstract class LibraryWrapper { interactionSubscriber = new ArrayList<>(); } - public void handleInteraction(Object context) { interactionSubscriber.forEach((o) -> o.accept(context)); } @@ -32,9 +30,10 @@ public abstract class LibraryWrapper { public abstract InteractionType getInteractionType(Class clazz); public abstract void registerSlashCommands(SlashCommandDefinition[] defs); - public abstract Object convertCommandOption(Object context, SlashCommandOptionType type, String optionName); public abstract ExecutableSlashCommandDefinition getCommandDefinition(Object context); public abstract String getButtonId(Object context); - public abstract Object getComponentContextObject(Object context, Class type); + + public abstract ContextObjectProvider getContextObjectProvider(); + } \ No newline at end of file diff --git a/wrapper/javacord/src/main/java/net/tomatentum/marinara/wrapper/javacord/JavacordContextObjectProvider.java b/wrapper/javacord/src/main/java/net/tomatentum/marinara/wrapper/javacord/JavacordContextObjectProvider.java new file mode 100644 index 0000000..0812f0c --- /dev/null +++ b/wrapper/javacord/src/main/java/net/tomatentum/marinara/wrapper/javacord/JavacordContextObjectProvider.java @@ -0,0 +1,110 @@ +package net.tomatentum.marinara.wrapper.javacord; + +import org.javacord.api.interaction.AutocompleteInteraction; +import org.javacord.api.interaction.ButtonInteraction; +import org.javacord.api.interaction.SlashCommandInteraction; +import org.javacord.api.interaction.SlashCommandInteractionOption; +import org.javacord.api.interaction.SlashCommandOptionType; + +import net.tomatentum.marinara.wrapper.ContextObjectProvider; + +public class JavacordContextObjectProvider implements ContextObjectProvider { + + @Override + public Object convertCommandOption(Object context, String optionName) { + if (!(context instanceof SlashCommandInteraction)) + return null; + SlashCommandInteraction interaction = (SlashCommandInteraction) context; + if (!interaction.getArguments().isEmpty()) + return getOptionValue(interaction.getOptionByName(optionName).get()); + + SlashCommandInteractionOption subCommandOption = interaction.getOptions().getFirst(); + + if (!subCommandOption.getOptions().isEmpty()) + subCommandOption = subCommandOption.getOptions().getFirst(); + + return getOptionValue(subCommandOption.getOptionByName(optionName).get()); + } + + private Object getOptionValue(SlashCommandInteractionOption option) { + SlashCommandOptionType type = getOptionType(option); + switch (type) { + case ATTACHMENT: + return option.getAttachmentValue().get(); + case BOOLEAN: + return option.getBooleanValue().get(); + case CHANNEL: + return option.getChannelValue().get(); + case DECIMAL: + return option.getDecimalValue().get(); + case LONG: + return option.getLongValue().get(); + case MENTIONABLE: + return option.requestMentionableValue().get(); + case ROLE: + return option.getRoleValue().get(); + case STRING: + return option.getStringValue().get(); + case USER: + return option.requestUserValue().get(); + default: + return null; + } + } + + + private SlashCommandOptionType getOptionType(SlashCommandInteractionOption option) { + if (option.getAttachmentValue().isPresent()) + return SlashCommandOptionType.ATTACHMENT; + if (option.getBooleanValue().isPresent()) + return SlashCommandOptionType.BOOLEAN; + if (option.getChannelValue().isPresent()) + return SlashCommandOptionType.CHANNEL; + if (option.getDecimalValue().isPresent()) + return SlashCommandOptionType.DECIMAL; + if (option.getLongValue().isPresent()) + return SlashCommandOptionType.LONG; + if (option.requestMentionableValue().isPresent()) + return SlashCommandOptionType.MENTIONABLE; + if (option.getRoleValue().isPresent()) + return SlashCommandOptionType.ROLE; + if (option.getStringValue().isPresent()) + return SlashCommandOptionType.STRING; + if (option.requestUserValue().isPresent()) + return SlashCommandOptionType.USER; + + return SlashCommandOptionType.UNKNOWN; + } + + @Override + public Object getComponentContextObject(Object context, Class type) { + ButtonInteraction button = (ButtonInteraction) context; + switch (type.getName()) { + case "org.javacord.api.entity.message.Message": + return button.getMessage(); + default: + return getInteractionContextObject(context, type); + } + } + + @Override + public Object getInteractionContextObject(Object context, Class type) { + ButtonInteraction button = (ButtonInteraction) context; + switch (type.getName()) { + case "org.javacord.api.entity.channel.TextChannel": + return button.getChannel().orElse(null); + case "org.javacord.api.entity.server.Server": + return button.getServer().orElse(null); + case "org.javacord.api.entity.user.User": + return button.getUser(); + } + return null; + } + + @Override + public Object getAutocompleteFocusedOption(Object context) { + AutocompleteInteraction interaction = (AutocompleteInteraction) context; + return getOptionValue(interaction.getFocusedOption()); + } + +} diff --git a/wrapper/javacord/src/main/java/net/tomatentum/marinara/wrapper/javacord/JavacordWrapper.java b/wrapper/javacord/src/main/java/net/tomatentum/marinara/wrapper/javacord/JavacordWrapper.java index 98988df..e07fb40 100644 --- a/wrapper/javacord/src/main/java/net/tomatentum/marinara/wrapper/javacord/JavacordWrapper.java +++ b/wrapper/javacord/src/main/java/net/tomatentum/marinara/wrapper/javacord/JavacordWrapper.java @@ -10,10 +10,14 @@ import java.util.Set; import org.javacord.api.DiscordApi; import org.javacord.api.interaction.ApplicationCommandInteraction; +import org.javacord.api.interaction.AutocompleteInteraction; import org.javacord.api.interaction.ButtonInteraction; import org.javacord.api.interaction.SlashCommandBuilder; import org.javacord.api.interaction.SlashCommandInteraction; import org.javacord.api.interaction.SlashCommandInteractionOption; +import org.javacord.api.interaction.SlashCommandOptionBuilder; +import org.javacord.api.interaction.SlashCommandOptionChoiceBuilder; +import org.javacord.api.interaction.SlashCommandOptionType; import io.leangen.geantyref.AnnotationFormatException; import io.leangen.geantyref.TypeFactory; @@ -22,27 +26,31 @@ import net.tomatentum.marinara.interaction.commands.ExecutableSlashCommandDefini import net.tomatentum.marinara.interaction.commands.SlashCommandDefinition; import net.tomatentum.marinara.interaction.commands.annotation.SlashCommand; import net.tomatentum.marinara.interaction.commands.annotation.SlashCommandOption; +import net.tomatentum.marinara.interaction.commands.annotation.SlashCommandOptionChoice; import net.tomatentum.marinara.interaction.commands.annotation.SubCommand; import net.tomatentum.marinara.interaction.commands.annotation.SubCommandGroup; -import net.tomatentum.marinara.interaction.commands.option.SlashCommandOptionType; +import net.tomatentum.marinara.wrapper.ContextObjectProvider; import net.tomatentum.marinara.wrapper.LibraryWrapper; public class JavacordWrapper extends LibraryWrapper { private DiscordApi api; + private JavacordContextObjectProvider contextObjectProvider; public JavacordWrapper(DiscordApi api) { this.api = api; + this.contextObjectProvider = new JavacordContextObjectProvider(); api.addInteractionCreateListener((e) -> handleInteraction(e.getInteraction())); } @Override public InteractionType getInteractionType(Class clazz) { + if (AutocompleteInteraction.class.isAssignableFrom(clazz)) + return InteractionType.AUTOCOMPLETE; if (ApplicationCommandInteraction.class.isAssignableFrom(clazz)) return InteractionType.COMMAND; if (ButtonInteraction.class.isAssignableFrom(clazz)) return InteractionType.BUTTON; - return null; } @@ -67,22 +75,6 @@ public class JavacordWrapper extends LibraryWrapper { api.bulkOverwriteGlobalApplicationCommands(globalCommands); } - @Override - public Object convertCommandOption(Object context, SlashCommandOptionType type, String optionName) { - if (!(context instanceof SlashCommandInteraction)) - return null; - SlashCommandInteraction interaction = (SlashCommandInteraction) context; - if (!interaction.getArguments().isEmpty()) - return getOptionValue(interaction.getOptionByName(optionName).get(), type); - - SlashCommandInteractionOption subCommandOption = interaction.getOptions().getFirst(); - - if (!subCommandOption.getOptions().isEmpty()) - subCommandOption = subCommandOption.getOptions().getFirst(); - - return getOptionValue(subCommandOption.getOptionByName(optionName).get(), type); - } - @Override public ExecutableSlashCommandDefinition getCommandDefinition(Object context) { if (!(context instanceof SlashCommandInteraction)) @@ -93,11 +85,13 @@ public class JavacordWrapper extends LibraryWrapper { List options = interaction.getOptions(); try { builder.setApplicationCommand(TypeFactory.annotation(SlashCommand.class, Map.of("name", interaction.getCommandName()))); - if (!options.getFirst().getArguments().isEmpty()) { - builder.setSubCommandGroup(TypeFactory.annotation(SubCommandGroup.class, Map.of("name", options.getFirst().getName()))); - builder.setSubCommand(TypeFactory.annotation(SubCommand.class, Map.of("name", options.getFirst().getOptions().getFirst().getName()))); - }else - builder.setSubCommand(TypeFactory.annotation(SubCommand.class, Map.of("name", options.getFirst().getName()))); + if (!options.isEmpty()) { + if (!options.getFirst().getArguments().isEmpty()) { + builder.setSubCommandGroup(TypeFactory.annotation(SubCommandGroup.class, Map.of("name", options.getFirst().getName()))); + builder.setSubCommand(TypeFactory.annotation(SubCommand.class, Map.of("name", options.getFirst().getOptions().getFirst().getName()))); + }else + builder.setSubCommand(TypeFactory.annotation(SubCommand.class, Map.of("name", options.getFirst().getName()))); + } } catch (AnnotationFormatException e) { e.printStackTrace(); } @@ -121,43 +115,53 @@ public class JavacordWrapper extends LibraryWrapper { private org.javacord.api.interaction.SlashCommandOption convertSubCommandGroupDef(SlashCommandDefinition def, SubCommandGroup subGroup) { SubCommand[] subCommands = def.getSubCommands(subGroup.name()); org.javacord.api.interaction.SlashCommandOption[] convertedSubCommands = (org.javacord.api.interaction.SlashCommandOption[]) Arrays.stream(subCommands).map(this::convertSubCommandDef).toArray(); - return org.javacord.api.interaction.SlashCommandOption.createWithOptions(org.javacord.api.interaction.SlashCommandOptionType.SUB_COMMAND_GROUP, subGroup.name(), subGroup.description(), Arrays.asList(convertedSubCommands)); + return org.javacord.api.interaction.SlashCommandOption.createWithOptions( + org.javacord.api.interaction.SlashCommandOptionType.SUB_COMMAND_GROUP, + subGroup.name(), + subGroup.description(), + Arrays.asList(convertedSubCommands)); } private org.javacord.api.interaction.SlashCommandOption convertSubCommandDef(SubCommand sub) { List convertedOptions = new ArrayList<>(); Arrays.stream(sub.options()).map(this::convertOptionDef).forEach(convertedOptions::add); - return org.javacord.api.interaction.SlashCommandOption.createWithOptions(org.javacord.api.interaction.SlashCommandOptionType.SUB_COMMAND, sub.name(), sub.description(), convertedOptions); + return org.javacord.api.interaction.SlashCommandOption.createWithOptions( + org.javacord.api.interaction.SlashCommandOptionType.SUB_COMMAND, + sub.name(), + sub.description(), + convertedOptions); } private org.javacord.api.interaction.SlashCommandOption convertOptionDef(SlashCommandOption option) { - org.javacord.api.interaction.SlashCommandOptionType type = Enum.valueOf(org.javacord.api.interaction.SlashCommandOptionType.class, option.type().toString()); - return org.javacord.api.interaction.SlashCommandOption.create(type, option.name(), option.description(), option.required()); + SlashCommandOptionType type = SlashCommandOptionType.fromValue(option.type().getValue()); + SlashCommandOptionBuilder builder = new SlashCommandOptionBuilder(); + builder + .setType(type) + .setName(option.name()) + .setDescription(option.description()) + .setRequired(option.required()) + .setAutocompletable(option.autocomplete()) + .setChoices(convertChoices(option)); + + return builder.build(); } - private Object getOptionValue(SlashCommandInteractionOption option, SlashCommandOptionType type) { - switch (type) { - case ATTACHMENT: - return option.getAttachmentValue().get(); - case BOOLEAN: - return option.getBooleanValue().get(); - case CHANNEL: - return option.getChannelValue().get(); - case DECIMAL: - return option.getDecimalValue().get(); - case LONG: - return option.getLongValue().get(); - case MENTIONABLE: - return option.getMentionableValue().get(); - case ROLE: - return option.getRoleValue().get(); - case STRING: - return option.getStringValue().get(); - case USER: - return option.getUserValue().get(); - default: - return null; + private List convertChoices(SlashCommandOption option) { + List convertedChoices = new ArrayList<>(); + for (SlashCommandOptionChoice choice : ExecutableSlashCommandDefinition.getActualChoices(option)) { + SlashCommandOptionChoiceBuilder builder = new SlashCommandOptionChoiceBuilder(); + builder.setName(choice.name()); + if (choice.longValue() != Long.MAX_VALUE) + builder.setValue(choice.longValue()); + /* + not yet available + if (choice.doubleValue() != Double.MAX_VALUE) + builder.setValue(choice.doubleValue()); + */ + if (!choice.stringValue().isEmpty()) + builder.setValue(choice.stringValue()); } + return convertedChoices; } @Override @@ -167,20 +171,8 @@ public class JavacordWrapper extends LibraryWrapper { } @Override - public Object getComponentContextObject(Object context, Class type) { - ButtonInteraction button = (ButtonInteraction) context; - switch (type.getName()) { - case "org.javacord.api.entity.channel.TextChannel": - return button.getChannel().orElse(null); - case "org.javacord.api.entity.message.Message": - return button.getMessage(); - case "org.javacord.api.entity.server.Server": - return button.getServer().orElse(null); - case "org.javacord.api.entity.user.User": - return button.getUser(); - } - return null; + public ContextObjectProvider getContextObjectProvider() { + return contextObjectProvider; } - } diff --git a/wrapper/javacord/src/test/java/net/tomatentum/marinara/test/AutoCompleteTest.java b/wrapper/javacord/src/test/java/net/tomatentum/marinara/test/AutoCompleteTest.java new file mode 100644 index 0000000..443c2b4 --- /dev/null +++ b/wrapper/javacord/src/test/java/net/tomatentum/marinara/test/AutoCompleteTest.java @@ -0,0 +1,23 @@ +package net.tomatentum.marinara.test; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +import net.tomatentum.marinara.Marinara; +import net.tomatentum.marinara.test.mocks.AutocompleteInteractionMock; +import net.tomatentum.marinara.test.mocks.DiscordApiMock; +import net.tomatentum.marinara.wrapper.LibraryWrapper; +import net.tomatentum.marinara.wrapper.javacord.JavacordWrapper; + +public class AutoCompleteTest { + + @Test + public void testAutocomplete() { + LibraryWrapper wrapper = new JavacordWrapper(new DiscordApiMock()); //null okay as we don't use the discord API in this test. + Marinara marinara = Marinara.load(wrapper); + marinara.getRegistry().addInteractions(new TestAutocomplete()); + wrapper.handleInteraction(new AutocompleteInteractionMock()); + assertTrue(AutocompleteInteractionMock.didAutocompleteRun); + } +} diff --git a/wrapper/javacord/src/test/java/net/tomatentum/marinara/test/TestAutocomplete.java b/wrapper/javacord/src/test/java/net/tomatentum/marinara/test/TestAutocomplete.java new file mode 100644 index 0000000..c38f4a2 --- /dev/null +++ b/wrapper/javacord/src/test/java/net/tomatentum/marinara/test/TestAutocomplete.java @@ -0,0 +1,23 @@ +package net.tomatentum.marinara.test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Collections; + +import org.javacord.api.interaction.AutocompleteInteraction; + +import net.tomatentum.marinara.interaction.InteractionHandler; +import net.tomatentum.marinara.interaction.annotation.AutoComplete; +import net.tomatentum.marinara.interaction.commands.annotation.SlashCommand; + +public class TestAutocomplete implements InteractionHandler { + + @SlashCommand(name = "test") + @AutoComplete + public void autocomplete(AutocompleteInteraction context, String value) { + System.out.println("Success!"); + assertEquals(value, "test"); + context.respondWithChoices(Collections.emptyList()); + } + +} diff --git a/wrapper/javacord/src/test/java/net/tomatentum/marinara/test/TestChoiceEnum.java b/wrapper/javacord/src/test/java/net/tomatentum/marinara/test/TestChoiceEnum.java new file mode 100644 index 0000000..1c7fd0a --- /dev/null +++ b/wrapper/javacord/src/test/java/net/tomatentum/marinara/test/TestChoiceEnum.java @@ -0,0 +1,20 @@ +package net.tomatentum.marinara.test; + +import net.tomatentum.marinara.interaction.commands.ChoiceValueProvider; + +public enum TestChoiceEnum implements ChoiceValueProvider { + TestValue("testValue"), + FooBar("fooBar"), + Spongebob("spongebob"); + + private String value; + + private TestChoiceEnum(String value) { + this.value = value; + } + @Override + public String getChoiceValue() { + return value; + } + +} diff --git a/wrapper/javacord/src/test/java/net/tomatentum/marinara/test/TestCommand.java b/wrapper/javacord/src/test/java/net/tomatentum/marinara/test/TestCommand.java index f6710b0..2716fea 100644 --- a/wrapper/javacord/src/test/java/net/tomatentum/marinara/test/TestCommand.java +++ b/wrapper/javacord/src/test/java/net/tomatentum/marinara/test/TestCommand.java @@ -20,7 +20,8 @@ public class TestCommand implements InteractionHandler { @SlashCommandOption( name = "foo", description = "foo bar is very fooby", - type = SlashCommandOptionType.STRING + type = SlashCommandOptionType.STRING, + choiceEnum = TestChoiceEnum.class ) } ) diff --git a/wrapper/javacord/src/test/java/net/tomatentum/marinara/test/mocks/AutocompleteInteractionMock.java b/wrapper/javacord/src/test/java/net/tomatentum/marinara/test/mocks/AutocompleteInteractionMock.java new file mode 100644 index 0000000..29d64b9 --- /dev/null +++ b/wrapper/javacord/src/test/java/net/tomatentum/marinara/test/mocks/AutocompleteInteractionMock.java @@ -0,0 +1,179 @@ +package net.tomatentum.marinara.test.mocks; + +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; + +import org.javacord.api.DiscordApi; +import org.javacord.api.entity.channel.TextChannel; +import org.javacord.api.entity.message.component.HighLevelComponent; +import org.javacord.api.entity.permission.PermissionType; +import org.javacord.api.entity.server.Server; +import org.javacord.api.entity.user.User; +import org.javacord.api.interaction.AutocompleteInteraction; +import org.javacord.api.interaction.DiscordLocale; +import org.javacord.api.interaction.InteractionType; +import org.javacord.api.interaction.SlashCommandInteractionOption; +import org.javacord.api.interaction.SlashCommandOptionChoice; +import org.javacord.api.interaction.callback.InteractionFollowupMessageBuilder; +import org.javacord.api.interaction.callback.InteractionImmediateResponseBuilder; +import org.javacord.api.interaction.callback.InteractionOriginalResponseUpdater; + +public class AutocompleteInteractionMock implements AutocompleteInteraction { + + public static boolean didAutocompleteRun = false; + + @Override + public String getFullCommandName() { + return "test"; + } + + @Override + public long getCommandId() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'getCommandId'"); + } + + @Override + public String getCommandIdAsString() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'getCommandIdAsString'"); + } + + @Override + public String getCommandName() { + return "test"; + } + + @Override + public Optional getRegisteredCommandServerId() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'getRegisteredCommandServerId'"); + } + + @Override + public long getApplicationId() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'getApplicationId'"); + } + + @Override + public InteractionType getType() { + return InteractionType.APPLICATION_COMMAND_AUTOCOMPLETE; + } + + @Override + public InteractionImmediateResponseBuilder createImmediateResponder() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'createImmediateResponder'"); + } + + @Override + public CompletableFuture respondLater() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'respondLater'"); + } + + @Override + public CompletableFuture respondLater(boolean ephemeral) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'respondLater'"); + } + + @Override + public CompletableFuture respondWithModal(String customId, String title, + List components) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'respondWithModal'"); + } + + @Override + public InteractionFollowupMessageBuilder createFollowupMessageBuilder() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'createFollowupMessageBuilder'"); + } + + @Override + public Optional getServer() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'getServer'"); + } + + @Override + public Optional getChannel() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'getChannel'"); + } + + @Override + public User getUser() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'getUser'"); + } + + @Override + public String getToken() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'getToken'"); + } + + @Override + public int getVersion() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'getVersion'"); + } + + @Override + public DiscordLocale getLocale() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'getLocale'"); + } + + @Override + public Optional getServerLocale() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'getServerLocale'"); + } + + @Override + public Optional> getBotPermissions() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'getBotPermissions'"); + } + + @Override + public DiscordApi getApi() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'getApi'"); + } + + @Override + public long getId() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'getId'"); + } + + @Override + public List getOptions() { + return new ArrayList<>(); + } + + @Override + public List getArguments() { + return new ArrayList<>(); + } + + @Override + public CompletableFuture respondWithChoices(List choices) { + didAutocompleteRun = true; + return CompletableFuture.completedFuture(null); + } + + @Override + public SlashCommandInteractionOption getFocusedOption() { + return new SlashCommandInteractionOptionMock(); + } + +} diff --git a/wrapper/javacord/src/test/java/net/tomatentum/marinara/test/mocks/SlashCommandInteractionOptionMock.java b/wrapper/javacord/src/test/java/net/tomatentum/marinara/test/mocks/SlashCommandInteractionOptionMock.java index 4f077b1..b102a21 100644 --- a/wrapper/javacord/src/test/java/net/tomatentum/marinara/test/mocks/SlashCommandInteractionOptionMock.java +++ b/wrapper/javacord/src/test/java/net/tomatentum/marinara/test/mocks/SlashCommandInteractionOptionMock.java @@ -41,52 +41,52 @@ public class SlashCommandInteractionOptionMock implements SlashCommandInteractio @Override public Optional getLongValue() { - throw new UnsupportedOperationException("Unimplemented method 'getLongValue'"); + return Optional.empty(); } @Override public Optional getBooleanValue() { - throw new UnsupportedOperationException("Unimplemented method 'getBooleanValue'"); + return Optional.empty(); } @Override public Optional getUserValue() { - throw new UnsupportedOperationException("Unimplemented method 'getUserValue'"); + return Optional.empty(); } @Override public Optional> requestUserValue() { - throw new UnsupportedOperationException("Unimplemented method 'requestUserValue'"); + return Optional.empty(); } @Override public Optional getChannelValue() { - throw new UnsupportedOperationException("Unimplemented method 'getChannelValue'"); + return Optional.empty(); } @Override public Optional getAttachmentValue() { - throw new UnsupportedOperationException("Unimplemented method 'getAttachmentValue'"); + return Optional.empty(); } @Override public Optional getRoleValue() { - throw new UnsupportedOperationException("Unimplemented method 'getRoleValue'"); + return Optional.empty(); } @Override public Optional getMentionableValue() { - throw new UnsupportedOperationException("Unimplemented method 'getMentionableValue'"); + return Optional.empty(); } @Override public Optional getDecimalValue() { - throw new UnsupportedOperationException("Unimplemented method 'getDecimalValue'"); + return Optional.empty(); } @Override public Optional> requestMentionableValue() { - throw new UnsupportedOperationException("Unimplemented method 'requestMentionableValue'"); + return Optional.empty(); } @Override