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..69e80e0 --- /dev/null +++ b/lib/src/main/java/net/tomatentum/marinara/interaction/annotation/AutoComplete.java @@ -0,0 +1,5 @@ +package net.tomatentum.marinara.interaction.annotation; + +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..411c5ac --- /dev/null +++ b/lib/src/main/java/net/tomatentum/marinara/interaction/commands/EnumChoices.java @@ -0,0 +1,74 @@ +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 (String.class.isAssignableFrom((Class) typeParam)) + return ChoiceType.String; + else if (Number.class.isAssignableFrom((Class) typeParam)) + return ChoiceType.Number; + else + throw new IllegalArgumentException("ChoiceValueProvider need either a String or Number 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.String)) + choices.add(TypeFactory.annotation(SlashCommandOptionChoice.class, Map.of("name", enumInstance.name(), "stringValue", value))); + else if (type.equals(ChoiceType.Number)) + choices.add(TypeFactory.annotation(SlashCommandOptionChoice.class, Map.of("name", enumInstance.name(), "longValue", value))); + } catch (IllegalAccessException | InvocationTargetException | AnnotationFormatException e) { + e.printStackTrace(); + return null; + } + } + return choices.toArray(new SlashCommandOptionChoice[0]); + } + + public static enum ChoiceType { + String, + Number + } +} 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..543311c 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,6 +2,7 @@ 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; @@ -11,6 +12,13 @@ public record ExecutableSlashCommandDefinition( SubCommandGroup subCommandGroup, SlashCommandOption[] options) { + public static SlashCommandOptionChoice[] getActualChoices(SlashCommandOption option) { + SlashCommandOptionChoice[] choices = option.choices(); + if (choices.length <= 0) + choices = EnumChoices.of(option.choiceEnum()).choices(); + return choices; + } + @Override public final boolean equals(Object o) { if (!(o instanceof ExecutableSlashCommandDefinition)) 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..eea19e3 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,10 @@ public @interface SlashCommandOption { public String description() default ""; public SlashCommandOptionType type() default SlashCommandOptionType.STRING; public boolean required() 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..0f5c8a7 --- /dev/null +++ b/lib/src/main/java/net/tomatentum/marinara/interaction/commands/annotation/SlashCommandOptionChoice.java @@ -0,0 +1,7 @@ +package net.tomatentum.marinara.interaction.commands.annotation; + +public @interface SlashCommandOptionChoice { + public String name(); + public long longValue() default Long.MAX_VALUE; + public String stringValue() default ""; +} 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..a0f7337 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 @@ -22,6 +22,7 @@ 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; @@ -121,18 +122,35 @@ 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()); + + List choices = new ArrayList<>(); + for (SlashCommandOptionChoice choice : ExecutableSlashCommandDefinition.getActualChoices(option)) { + if (choice.stringValue().isEmpty()) + choices.add(org.javacord.api.interaction.SlashCommandOptionChoice.create(choice.name(), choice.longValue())); + else + choices.add(org.javacord.api.interaction.SlashCommandOptionChoice.create(choice.name(), choice.stringValue())); + } + + return org.javacord.api.interaction.SlashCommandOption.createWithChoices(type, option.name(), option.description(), option.required(), choices); } private Object getOptionValue(SlashCommandInteractionOption option, SlashCommandOptionType type) {