Add Choices and Autocomplete #8

Open
tueem wants to merge 12 commits from feat/choices into dev
7 changed files with 126 additions and 3 deletions
Showing only changes of commit 69b27e4554 - Show all commits
lib/src/main/java/net/tomatentum/marinara/interaction
wrapper/javacord/src/main/java/net/tomatentum/marinara/wrapper/javacord

@ -0,0 +1,5 @@
package net.tomatentum.marinara.interaction.annotation;
public @interface AutoComplete {
}

@ -0,0 +1,5 @@
package net.tomatentum.marinara.interaction.commands;
public interface ChoiceValueProvider<T> {
T getChoiceValue();
}

@ -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<? extends Enum<?>> 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<? extends Enum<?>> 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<? extends Enum<?>> 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<? extends Enum<?>> enumClass, ChoiceType type) {
Enum<? extends Enum<?>>[] constants = enumClass.getEnumConstants();
List<SlashCommandOptionChoice> choices = new ArrayList<>();
for (Enum<? extends 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
}
}

@ -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.SlashCommand;
import net.tomatentum.marinara.interaction.commands.annotation.SlashCommandOption; 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.SubCommand;
import net.tomatentum.marinara.interaction.commands.annotation.SubCommandGroup; import net.tomatentum.marinara.interaction.commands.annotation.SubCommandGroup;
@ -11,6 +12,13 @@ public record ExecutableSlashCommandDefinition(
SubCommandGroup subCommandGroup, SubCommandGroup subCommandGroup,
SlashCommandOption[] options) { 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 @Override
public final boolean equals(Object o) { public final boolean equals(Object o) {
if (!(o instanceof ExecutableSlashCommandDefinition)) if (!(o instanceof ExecutableSlashCommandDefinition))

@ -14,4 +14,10 @@ public @interface SlashCommandOption {
public String description() default ""; public String description() default "";
public SlashCommandOptionType type() default SlashCommandOptionType.STRING; public SlashCommandOptionType type() default SlashCommandOptionType.STRING;
public boolean required() default false; public boolean required() default false;
public SlashCommandOptionChoice[] choices() default {};
public Class<? extends Enum<?>> choiceEnum() default PlaceHolderEnum.class;
public static enum PlaceHolderEnum {
}
} }

@ -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 "";
}

@ -22,6 +22,7 @@ import net.tomatentum.marinara.interaction.commands.ExecutableSlashCommandDefini
import net.tomatentum.marinara.interaction.commands.SlashCommandDefinition; import net.tomatentum.marinara.interaction.commands.SlashCommandDefinition;
import net.tomatentum.marinara.interaction.commands.annotation.SlashCommand; import net.tomatentum.marinara.interaction.commands.annotation.SlashCommand;
import net.tomatentum.marinara.interaction.commands.annotation.SlashCommandOption; 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.SubCommand;
import net.tomatentum.marinara.interaction.commands.annotation.SubCommandGroup; import net.tomatentum.marinara.interaction.commands.annotation.SubCommandGroup;
import net.tomatentum.marinara.interaction.commands.option.SlashCommandOptionType; 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) { private org.javacord.api.interaction.SlashCommandOption convertSubCommandGroupDef(SlashCommandDefinition def, SubCommandGroup subGroup) {
SubCommand[] subCommands = def.getSubCommands(subGroup.name()); SubCommand[] subCommands = def.getSubCommands(subGroup.name());
org.javacord.api.interaction.SlashCommandOption[] convertedSubCommands = (org.javacord.api.interaction.SlashCommandOption[]) Arrays.stream(subCommands).map(this::convertSubCommandDef).toArray(); 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) { private org.javacord.api.interaction.SlashCommandOption convertSubCommandDef(SubCommand sub) {
List<org.javacord.api.interaction.SlashCommandOption> convertedOptions = new ArrayList<>(); List<org.javacord.api.interaction.SlashCommandOption> convertedOptions = new ArrayList<>();
Arrays.stream(sub.options()).map(this::convertOptionDef).forEach(convertedOptions::add); 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) { 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()); 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<org.javacord.api.interaction.SlashCommandOptionChoice> 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) { private Object getOptionValue(SlashCommandInteractionOption option, SlashCommandOptionType type) {