diff --git a/.gitignore b/.gitignore index d11a53b..1dffc61 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ build .vscode lib/bin wrapper/javacord/bin +wrapper/discord4j/bin 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 deleted file mode 100644 index 76bc8eb..0000000 --- a/lib/src/main/java/net/tomatentum/marinara/interaction/commands/ExecutableSlashCommandDefinition.java +++ /dev/null @@ -1,94 +0,0 @@ -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, - SubCommand subCommand, - 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)) - return false; - ExecutableSlashCommandDefinition other = (ExecutableSlashCommandDefinition) o; - boolean equals = false; - - 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()); - - if (this.subCommand() != null && other.subCommand() != null) - equals = this.subCommand().name().equals(other.subCommand().name()); - - return equals; - } - - @Override - public final String toString() { - StringBuilder builder = new StringBuilder(); - builder.append(applicationCommand.name()); - if (subCommandGroup != null && subCommandGroup.name() != null) - builder.append("::").append(subCommandGroup.name()); - if (subCommand != null && subCommand.name() != null) - builder.append("::").append(subCommand.name()); - return builder.toString(); - } - - public boolean isRootCommand() { - return subCommand == null; - } - - public static class Builder { - private SlashCommand applicationCommand; - private SubCommand subCommand; - private SubCommandGroup subCommandGroup; - - public ExecutableSlashCommandDefinition build() { - if (applicationCommand == null) - throw new IllegalArgumentException("applicationCommandName cant be null"); - - return new ExecutableSlashCommandDefinition(applicationCommand, subCommand, subCommandGroup, subCommand != null ? subCommand.options() : applicationCommand.options()); - } - - public void setApplicationCommand(SlashCommand applicationCommand) { - this.applicationCommand = applicationCommand; - } - - public void setSubCommand(SubCommand subCommand) { - this.subCommand = subCommand; - } - - public void setSubCommandGroup(SubCommandGroup subCommandGroup) { - this.subCommandGroup = subCommandGroup; - } - - public SlashCommand getApplicationCommand() { - return applicationCommand; - } - - public SubCommand getSubCommand() { - return subCommand; - } - - public SubCommandGroup getSubCommandGroup() { - return subCommandGroup; - } - - } -} diff --git a/lib/src/main/java/net/tomatentum/marinara/interaction/commands/SlashCommandDefinition.java b/lib/src/main/java/net/tomatentum/marinara/interaction/commands/SlashCommandDefinition.java index 9b0b3fb..164eec4 100644 --- a/lib/src/main/java/net/tomatentum/marinara/interaction/commands/SlashCommandDefinition.java +++ b/lib/src/main/java/net/tomatentum/marinara/interaction/commands/SlashCommandDefinition.java @@ -1,99 +1,110 @@ package net.tomatentum.marinara.interaction.commands; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; +import java.util.HashSet; import java.util.List; +import java.util.Set; -import net.tomatentum.marinara.interaction.commands.annotation.SlashCommand; -import net.tomatentum.marinara.interaction.commands.annotation.SubCommand; -import net.tomatentum.marinara.interaction.commands.annotation.SubCommandGroup; +import org.apache.logging.log4j.Logger; + +import net.tomatentum.marinara.interaction.commands.annotation.SlashCommandOption; +import net.tomatentum.marinara.interaction.commands.annotation.SlashCommandOption.PlaceHolderEnum; +import net.tomatentum.marinara.interaction.commands.annotation.SlashCommandOptionChoice; +import net.tomatentum.marinara.interaction.commands.choice.EnumChoices; +import net.tomatentum.marinara.interaction.ident.InteractionIdentifier; +import net.tomatentum.marinara.interaction.ident.RootCommandIdentifier; +import net.tomatentum.marinara.interaction.ident.SlashCommandIdentifier; +import net.tomatentum.marinara.util.LoggerUtil; public class SlashCommandDefinition { - private List executableDefinitons; - private SlashCommand slashCommand; - private boolean isRootCommand; - public SlashCommandDefinition(SlashCommand applicationCommand) { - this.executableDefinitons = new ArrayList<>(); - this.slashCommand = applicationCommand; + 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; } - public SlashCommandDefinition addExecutableCommand(ExecutableSlashCommandDefinition def) { - if (def.applicationCommand() != null) { - if (slashCommand == null) - this.slashCommand = def.applicationCommand(); - if (!this.slashCommand.name().equals(def.applicationCommand().name())) - throw new IllegalArgumentException(def + ": has a non matching Application Command description. Please edit it to equal all other descriptions or remove it to use other definitions descriptions"); - } + private Set entries; + private RootCommandIdentifier rootIdentifier; + private boolean isRootCommand; - if (executableDefinitons.isEmpty()) - this.isRootCommand = def.isRootCommand(); + private Logger logger = LoggerUtil.getLogger(getClass()); - if ((isRootCommand && !def.isRootCommand()) || (!isRootCommand && def.isRootCommand())) { - throw new IllegalArgumentException(def + ": cannot have subcommands and rootcommand definitions together"); + public SlashCommandDefinition(RootCommandIdentifier rootIdentifier) { + this.entries = new HashSet<>(); + this.rootIdentifier = rootIdentifier; + this.isRootCommand = false; + } + + public SlashCommandDefinition addIdentifier(InteractionIdentifier identifier) { + RootCommandIdentifier rootIdentifier = (RootCommandIdentifier) identifier.rootNode(); + + if (!this.rootIdentifier.equals(rootIdentifier)) + throw new IllegalArgumentException("Root Node did not match."); + + if (this.rootIdentifier.description() == null) + this.rootIdentifier = rootIdentifier; + + if (!isRootCommand) + this.isRootCommand = identifier.parent() == null ? true : false; + + if ((isRootCommand && identifier.parent() != null) || (!isRootCommand && identifier.parent() == null)) { + throw new IllegalArgumentException(identifier.toString() + ": cannot have subcommands and rootcommand definitions together"); } - executableDefinitons.add(def); + entries.add(identifier); + this.logger.debug("Added identifer {} to command {}", identifier, rootIdentifier); return this; } - public SubCommandGroup[] getSubCommandGroups() { - List subCommandGroups = Arrays.stream(getExecutableDefinitons()) - .filter((x) -> x.subCommandGroup() != null) - .map((x) -> x.subCommandGroup()) - .toList(); - - HashMap subCommandGroupMap = new HashMap<>(); - subCommandGroups.forEach((x) -> { - SubCommandGroup current = subCommandGroupMap.get(x.name()); - if (current == null || (current.description().isBlank() && !x.description().isBlank())) - subCommandGroupMap.put(x.name(), x); - }); - - return subCommandGroupMap.values().toArray(new SubCommandGroup[0]); - } - - public SubCommand[] getSubCommands(String groupName) { - List subCommands; - if (groupName == null) - subCommands = Arrays.stream(getExecutableDefinitons()) - .filter((x) -> x.subCommandGroup() == null && x.subCommand() != null) - .map((x) -> x.subCommand()) - .toList(); - else - subCommands = Arrays.stream(getExecutableDefinitons()) - .filter((x) -> x.subCommandGroup().name().equals(groupName) && x.subCommand() != null) - .map((x) -> x.subCommand()) - .toList(); + public SlashCommandIdentifier[] getSubCommandGroups() { + if (isRootCommand) + return null; - HashMap subCommandMap = new HashMap<>(); - subCommands.forEach((x) -> { - SubCommand current = subCommandMap.get(x.name()); - if (current == null || (current.description().isBlank() && !x.description().isBlank())) - subCommandMap.put(x.name(), x); - }); + List subCommandGroups = entries().stream() + .filter(x -> x.parent().parent() != null) + .map(x -> x.parent()) + .toList(); - return subCommandMap.values().toArray(new SubCommand[0]); + return subCommandGroups.toArray(SlashCommandIdentifier[]::new); } - public SlashCommand getFullSlashCommand() { - if (isRootCommand()) - return getSlashCommand(); - for (ExecutableSlashCommandDefinition executableSlashCommandDefinition : executableDefinitons) { - if (executableSlashCommandDefinition.options().length > 0) - return executableSlashCommandDefinition.applicationCommand(); - } - - return null; + public SlashCommandIdentifier[] getSubCommands() { + if (isRootCommand) + return null; + return entries.stream().filter(x -> x.parent() instanceof RootCommandIdentifier).toArray(SlashCommandIdentifier[]::new); } - public SlashCommand getSlashCommand() { - return slashCommand; + public SlashCommandIdentifier[] getSubCommands(String groupName) { + if (isRootCommand) + return null; + + List subCommands = entries().stream() + .filter(x -> x.parent().parent() != null && x.parent().name().equals(groupName)) + .map(x -> x.parent().parent()) + .toList(); + + return subCommands.toArray(SlashCommandIdentifier[]::new); } - public ExecutableSlashCommandDefinition[] getExecutableDefinitons() { - return executableDefinitons.toArray(new ExecutableSlashCommandDefinition[0]); + @Override + public boolean equals(Object obj) { + if (!(obj instanceof SlashCommandDefinition)) + return false; + SlashCommandDefinition other = (SlashCommandDefinition) obj; + return this.rootIdentifier().equals(other.rootIdentifier()); + } + + public long[] serverIds() { + return rootIdentifier().serverIds(); + } + + public Set entries() { + return this.entries; + } + + public RootCommandIdentifier rootIdentifier() { + return rootIdentifier; } public boolean isRootCommand() { diff --git a/lib/src/main/java/net/tomatentum/marinara/interaction/commands/ChoiceValueProvider.java b/lib/src/main/java/net/tomatentum/marinara/interaction/commands/choice/ChoiceValueProvider.java similarity index 53% rename from lib/src/main/java/net/tomatentum/marinara/interaction/commands/ChoiceValueProvider.java rename to lib/src/main/java/net/tomatentum/marinara/interaction/commands/choice/ChoiceValueProvider.java index b7d13f6..b025047 100644 --- a/lib/src/main/java/net/tomatentum/marinara/interaction/commands/ChoiceValueProvider.java +++ b/lib/src/main/java/net/tomatentum/marinara/interaction/commands/choice/ChoiceValueProvider.java @@ -1,4 +1,4 @@ -package net.tomatentum.marinara.interaction.commands; +package net.tomatentum.marinara.interaction.commands.choice; 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/choice/EnumChoices.java similarity index 98% rename from lib/src/main/java/net/tomatentum/marinara/interaction/commands/EnumChoices.java rename to lib/src/main/java/net/tomatentum/marinara/interaction/commands/choice/EnumChoices.java index 1001db3..f012c1f 100644 --- a/lib/src/main/java/net/tomatentum/marinara/interaction/commands/EnumChoices.java +++ b/lib/src/main/java/net/tomatentum/marinara/interaction/commands/choice/EnumChoices.java @@ -1,4 +1,4 @@ -package net.tomatentum.marinara.interaction.commands; +package net.tomatentum.marinara.interaction.commands.choice; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; diff --git a/lib/src/main/java/net/tomatentum/marinara/interaction/ident/InteractionIdentifier.java b/lib/src/main/java/net/tomatentum/marinara/interaction/ident/InteractionIdentifier.java new file mode 100644 index 0000000..7207738 --- /dev/null +++ b/lib/src/main/java/net/tomatentum/marinara/interaction/ident/InteractionIdentifier.java @@ -0,0 +1,141 @@ +package net.tomatentum.marinara.interaction.ident; + +import java.util.Objects; + +import net.tomatentum.marinara.interaction.InteractionType; + +public class InteractionIdentifier { + + public static InteractionIdentifier.Builder builder() { + return new InteractionIdentifier.Builder(); + } + + public static RootCommandIdentifier.Builder rootBuilder() { + return new RootCommandIdentifier.Builder(); + } + + public static SlashCommandIdentifier.Builder slashBuilder() { + return new SlashCommandIdentifier.Builder(); + } + + public static InteractionIdentifier createHierarchy(InteractionType type, String... names) { + InteractionIdentifier last = null; + for (String string : names) { + last = builder().name(string).type(type).parent(last).build(); + } + return last; + } + + public static void tryAddDescriptions(InteractionIdentifier receiver, InteractionIdentifier provider) { + if (receiver == null || provider == null) + return; + + if (receiver.description().isBlank()) + receiver.description = provider.description(); + tryAddDescriptions(receiver.parent(), provider.parent()); + } + + private InteractionIdentifier parent; + private String name; + private String description; + private InteractionType type; + + InteractionIdentifier(InteractionIdentifier parent, String name, String description, InteractionType type) { + this.parent = parent; + this.name = name; + this.description = description; + this.type = type; + } + + public InteractionIdentifier rootNode() { return rootNode(this); } + + private InteractionIdentifier rootNode(InteractionIdentifier identifier) { + if (identifier.parent() == null) + return identifier; + return rootNode(identifier.parent()); + } + + public String name() { + return name; + } + + public String description() { + return description; + } + + public InteractionIdentifier parent() { + return parent; + } + + public InteractionType type() { + return type; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof InteractionIdentifier)) + return false; + InteractionIdentifier ident = (InteractionIdentifier) obj; + if (!type().equals(ident.type())) + return false; + if (!name().equals(ident.name())) + return false; + return Objects.equals(ident, obj); + } + + @Override + public String toString() { + if (parent() == null) + return name(); + return "{}.{}".formatted(name(), parent().toString()); + } + + public static class Builder { + private InteractionIdentifier parent; + private String name; + private String description; + private InteractionType type; + + public InteractionIdentifier parent() { + return parent; + } + + public Builder parent(InteractionIdentifier parent) { + this.parent = parent; + return this; + } + + public String name() { + return name; + } + + public Builder name(String name) { + this.name = name; + return this; + } + + public String description() { + return description; + } + + public Builder description(String description) { + this.description = description; + return this; + } + + public InteractionType type() { + return type; + } + + public Builder type(InteractionType type) { + this.type = type; + return this; + } + + public InteractionIdentifier build() { + return new InteractionIdentifier(parent, name, description, type); + } + + } + +} diff --git a/lib/src/main/java/net/tomatentum/marinara/interaction/ident/RootCommandIdentifier.java b/lib/src/main/java/net/tomatentum/marinara/interaction/ident/RootCommandIdentifier.java new file mode 100644 index 0000000..ae0e6d4 --- /dev/null +++ b/lib/src/main/java/net/tomatentum/marinara/interaction/ident/RootCommandIdentifier.java @@ -0,0 +1,90 @@ +package net.tomatentum.marinara.interaction.ident; + +import net.tomatentum.marinara.interaction.InteractionType; +import net.tomatentum.marinara.interaction.commands.annotation.SlashCommandOption; + +public class RootCommandIdentifier extends SlashCommandIdentifier { + + private long[] serverIds; + + public RootCommandIdentifier( + InteractionIdentifier parent, + String name, + String description, + InteractionType type, + SlashCommandOption[] options, + long[] serverIds) { + super(parent, name, description, type, options); + this.serverIds = serverIds; + } + + public long[] serverIds() { + return serverIds; + } + + public static class Builder { + private InteractionIdentifier parent; + private String name; + private String description; + private SlashCommandOption[] options; + private long[] serverIds; + + + public InteractionIdentifier parent() { + return parent; + } + + public Builder parent(InteractionIdentifier parent) { + this.parent = parent; + return this; + } + + public String name() { + return name; + } + + public Builder name(String name) { + this.name = name; + return this; + } + + public String description() { + return this.description; + } + + public Builder description(String description) { + this.description = description; + return this; + } + + public SlashCommandOption[] options() { + return this.options; + } + + public Builder options(SlashCommandOption[] options) { + this.options = options; + return this; + } + + public long[] serverIds() { + return this.serverIds; + } + + public Builder serverIds(long[] serverIds) { + this.serverIds = serverIds; + return this; + } + + public SlashCommandIdentifier build(boolean autocomplete) { + return new RootCommandIdentifier( + parent, + name, + description, + autocomplete ? InteractionType.AUTOCOMPLETE : InteractionType.COMMAND, + options, + serverIds); + } + + } + +} diff --git a/lib/src/main/java/net/tomatentum/marinara/interaction/ident/SlashCommandIdentifier.java b/lib/src/main/java/net/tomatentum/marinara/interaction/ident/SlashCommandIdentifier.java new file mode 100644 index 0000000..7284a2a --- /dev/null +++ b/lib/src/main/java/net/tomatentum/marinara/interaction/ident/SlashCommandIdentifier.java @@ -0,0 +1,78 @@ +package net.tomatentum.marinara.interaction.ident; + +import net.tomatentum.marinara.interaction.InteractionType; +import net.tomatentum.marinara.interaction.commands.annotation.SlashCommandOption; + +public class SlashCommandIdentifier extends InteractionIdentifier { + + private SlashCommandOption[] options; + + protected SlashCommandIdentifier( + InteractionIdentifier parent, + String name, + String description, + InteractionType type, + SlashCommandOption[] options + ) { + super(parent, name, description, type); + this.options = options; + } + + public SlashCommandOption[] options() { + return this.options; + } + + public static class Builder { + private InteractionIdentifier parent; + private String name; + private String description; + private SlashCommandOption[] options; + + public InteractionIdentifier parent() { + return parent; + } + + public Builder parent(InteractionIdentifier parent) { + this.parent = parent; + return this; + } + + public String name() { + return name; + } + + public Builder name(String name) { + this.name = name; + return this; + } + + public String description() { + return this.description; + } + + public Builder description(String description) { + this.description = description; + return this; + } + + public SlashCommandOption[] options() { + return this.options; + } + + public Builder options(SlashCommandOption[] options) { + this.options = options; + return this; + } + + public SlashCommandIdentifier build(boolean autocomplete) { + return new SlashCommandIdentifier( + parent, + name, + description, + autocomplete ? InteractionType.AUTOCOMPLETE : InteractionType.COMMAND, + options); + } + + } + +} 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 index ffed507..a5d53b4 100644 --- a/lib/src/main/java/net/tomatentum/marinara/interaction/methods/AutoCompleteInteractionMethod.java +++ b/lib/src/main/java/net/tomatentum/marinara/interaction/methods/AutoCompleteInteractionMethod.java @@ -4,14 +4,13 @@ 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.interaction.ident.InteractionIdentifier; import net.tomatentum.marinara.parser.AnnotationParser; import net.tomatentum.marinara.parser.SlashCommandParser; public class AutoCompleteInteractionMethod extends InteractionMethod { - private ExecutableSlashCommandDefinition commandDefinition; + private InteractionIdentifier interactionIdentifier; public AutoCompleteInteractionMethod(Method method, InteractionHandler handler, @@ -21,31 +20,25 @@ public class AutoCompleteInteractionMethod extends InteractionMethod { } @Override - public AnnotationParser[] getParsers() { + public AnnotationParser[] parsers() { return new AnnotationParser[] { - new SlashCommandParser(method, (x) -> { this.commandDefinition = x; } ) + new SlashCommandParser(method, true, (x) -> { this.interactionIdentifier = x; } ) }; } @Override public Object getParameter(Object context, int index) { - Class type = getMethod().getParameterTypes()[index+1]; + Class type = method().getParameterTypes()[index+1]; Object autocompleteOptionValue = marinara.getWrapper().getContextObjectProvider().getAutocompleteFocusedOption(context); if (autocompleteOptionValue != null) return autocompleteOptionValue; - return marinara.getWrapper().getContextObjectProvider().getComponentContextObject(context, type); + return marinara.getWrapper().getContextObjectProvider().getInteractionContextObject(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; + public InteractionIdentifier identifier() { + return interactionIdentifier; } } 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 fe039ac..ec011a4 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 @@ -5,6 +5,7 @@ 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.ident.InteractionIdentifier; import net.tomatentum.marinara.parser.AnnotationParser; import net.tomatentum.marinara.parser.ButtonParser; @@ -17,7 +18,7 @@ public class ButtonInteractionMethod extends InteractionMethod { } @Override - public AnnotationParser[] getParsers() { + public AnnotationParser[] parsers() { return new AnnotationParser[] { new ButtonParser(method, (x) -> { this.customId = x; } ) }; @@ -25,18 +26,17 @@ public class ButtonInteractionMethod extends InteractionMethod { @Override public Object getParameter(Object context, int index) { - Class type = getMethod().getParameterTypes()[index+1]; + Class type = method().getParameterTypes()[index+1]; return marinara.getWrapper().getContextObjectProvider().getComponentContextObject(context, type); } @Override - public boolean canRun(Object context) { - return marinara.getWrapper().getButtonId(context).equals(customId); + public InteractionIdentifier identifier() { + return InteractionIdentifier.builder() + .name(customId) + .description("Button") + .type(InteractionType.BUTTON) + .build(); } - @Override - 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 ddc3618..09d7c61 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 @@ -12,11 +12,11 @@ import org.apache.logging.log4j.Logger; 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; +import net.tomatentum.marinara.interaction.ident.InteractionIdentifier; import net.tomatentum.marinara.parser.AnnotationParser; import net.tomatentum.marinara.parser.InteractionCheckParser; import net.tomatentum.marinara.util.LoggerUtil; @@ -42,7 +42,8 @@ public abstract class InteractionMethod { private Logger logger = LoggerUtil.getLogger(getClass()); - protected InteractionMethod(Method method, + protected InteractionMethod( + Method method, InteractionHandler handler, Marinara marinara ) { @@ -52,7 +53,7 @@ public abstract class InteractionMethod { this.method = method; this.handler = handler; this.marinara = marinara; - this.parsers = new ArrayList<>(Arrays.asList(getParsers())); + this.parsers = new ArrayList<>(Arrays.asList(parsers())); this.appliedChecks = new ArrayList<>(); parsers.add(new InteractionCheckParser(method, appliedChecks::add, marinara.getCheckRegistry())); @@ -60,14 +61,6 @@ public abstract class InteractionMethod { parsers.stream().forEach(AnnotationParser::parse); } - public abstract AnnotationParser[] getParsers(); - - public abstract Object getParameter(Object context, int index); - - public abstract boolean canRun(Object context); - - public abstract InteractionType getType(); - public void run(Object context) { if (this.appliedChecks.stream().filter(x -> !x.pre(context)).count() > 0) return; @@ -82,7 +75,13 @@ public abstract class InteractionMethod { this.appliedChecks.forEach(x -> x.post(context)); } - public Method getMethod() { + public abstract AnnotationParser[] parsers(); + + public abstract Object getParameter(Object context, int index); + + public abstract InteractionIdentifier identifier(); + + public Method method() { return method; } 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 2f5faf4..d89d709 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 @@ -4,48 +4,34 @@ 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.interaction.ident.InteractionIdentifier; +import net.tomatentum.marinara.interaction.ident.SlashCommandIdentifier; import net.tomatentum.marinara.parser.AnnotationParser; import net.tomatentum.marinara.parser.SlashCommandParser; public class SlashCommandInteractionMethod extends InteractionMethod { - private ExecutableSlashCommandDefinition commandDefinition; + private SlashCommandIdentifier interactionIdentifier; SlashCommandInteractionMethod(Method method, InteractionHandler handler, Marinara marinara) { super(method, handler, marinara); } @Override - public AnnotationParser[] getParsers() { + public AnnotationParser[] parsers() { return new AnnotationParser[] { - new SlashCommandParser(method, (x) -> { this.commandDefinition = x; } ) + new SlashCommandParser(method, false, (x) -> { this.interactionIdentifier = x; } ) }; } @Override public Object getParameter(Object context, int index) { - return marinara.getWrapper().getContextObjectProvider().convertCommandOption(context, commandDefinition.options()[index].name()); + return marinara.getWrapper().getContextObjectProvider().convertCommandOption(context, interactionIdentifier.options()[index].name()); } @Override - public boolean canRun(Object context) { - ExecutableSlashCommandDefinition other = marinara.getWrapper().getCommandDefinition(context); - return commandDefinition.equals(other); - } - - @Override - public InteractionType getType() { - return InteractionType.COMMAND; - } - - public ExecutableSlashCommandDefinition getCommandDefinition() { - return commandDefinition; - } - - public void setCommandDefinition(ExecutableSlashCommandDefinition commandDefinition) { - this.commandDefinition = commandDefinition; + public InteractionIdentifier identifier() { + return interactionIdentifier; } } diff --git a/lib/src/main/java/net/tomatentum/marinara/parser/SlashCommandParser.java b/lib/src/main/java/net/tomatentum/marinara/parser/SlashCommandParser.java index cf2a244..ca07241 100644 --- a/lib/src/main/java/net/tomatentum/marinara/parser/SlashCommandParser.java +++ b/lib/src/main/java/net/tomatentum/marinara/parser/SlashCommandParser.java @@ -5,22 +5,26 @@ import java.util.function.Consumer; import org.apache.logging.log4j.Logger; -import net.tomatentum.marinara.interaction.commands.ExecutableSlashCommandDefinition; +import net.tomatentum.marinara.interaction.InteractionType; import net.tomatentum.marinara.interaction.commands.annotation.SlashCommand; import net.tomatentum.marinara.interaction.commands.annotation.SubCommand; import net.tomatentum.marinara.interaction.commands.annotation.SubCommandGroup; +import net.tomatentum.marinara.interaction.ident.InteractionIdentifier; +import net.tomatentum.marinara.interaction.ident.SlashCommandIdentifier; import net.tomatentum.marinara.util.LoggerUtil; import net.tomatentum.marinara.util.ReflectionUtil; public class SlashCommandParser implements AnnotationParser { private Method method; - private Consumer consumer; + private boolean isAutoComplete; + private Consumer consumer; private Logger logger = LoggerUtil.getLogger(getClass()); - public SlashCommandParser(Method method, Consumer consumer) { + public SlashCommandParser(Method method, boolean isAutoComplete, Consumer consumer) { this.method = method; + this.isAutoComplete = isAutoComplete; this.consumer = consumer; } @@ -29,23 +33,34 @@ public class SlashCommandParser implements AnnotationParser { this.checkValidCommandMethod(method); SlashCommand cmd = ReflectionUtil.getAnnotation(method, SlashCommand.class); - ExecutableSlashCommandDefinition.Builder builder = new ExecutableSlashCommandDefinition.Builder(); - builder.setApplicationCommand(cmd); + InteractionIdentifier lastIdentifier = InteractionIdentifier.rootBuilder() + .name(cmd.name()) + .description(cmd.description()) + .options(cmd.options()) + .serverIds(cmd.serverIds()) + .build(isAutoComplete); if (ReflectionUtil.isAnnotationPresent(method, SubCommandGroup.class)) { SubCommandGroup cmdGroup = ReflectionUtil.getAnnotation(method, SubCommandGroup.class); - builder.setSubCommandGroup(cmdGroup); + lastIdentifier = InteractionIdentifier.builder() + .name(cmdGroup.name()) + .description(cmdGroup.description()) + .type(isAutoComplete ? InteractionType.AUTOCOMPLETE : InteractionType.COMMAND) + .parent(lastIdentifier) + .build(); } if (ReflectionUtil.isAnnotationPresent(method, SubCommand.class)) { SubCommand subCmd = ReflectionUtil.getAnnotation(method, SubCommand.class); - builder.setSubCommand(subCmd); + lastIdentifier = InteractionIdentifier.slashBuilder() + .name(subCmd.name()) + .description(subCmd.description()) + .options(subCmd.options()) + .build(isAutoComplete); } - ExecutableSlashCommandDefinition def = builder.build(); - - logger.trace("Parsed using SlashCommandParser for method {} with the result:\n{}", ReflectionUtil.getFullMethodName(method), def.toString()); - consumer.accept(builder.build()); + logger.trace("Parsed using SlashCommandParser for method {} with the result:\n{}", ReflectionUtil.getFullMethodName(method), lastIdentifier.toString()); + consumer.accept((SlashCommandIdentifier) lastIdentifier); } @Override diff --git a/lib/src/main/java/net/tomatentum/marinara/registry/InteractionEntry.java b/lib/src/main/java/net/tomatentum/marinara/registry/InteractionEntry.java new file mode 100644 index 0000000..6e2735b --- /dev/null +++ b/lib/src/main/java/net/tomatentum/marinara/registry/InteractionEntry.java @@ -0,0 +1,65 @@ +package net.tomatentum.marinara.registry; + +import java.util.HashSet; +import java.util.Set; + +import org.apache.logging.log4j.Logger; + +import net.tomatentum.marinara.interaction.InteractionType; +import net.tomatentum.marinara.interaction.ident.InteractionIdentifier; +import net.tomatentum.marinara.interaction.methods.InteractionMethod; +import net.tomatentum.marinara.util.LoggerUtil; + +public class InteractionEntry { + private InteractionIdentifier identifier; + private Set methods; + + private Logger logger = LoggerUtil.getLogger(getClass()); + + public InteractionEntry(InteractionIdentifier identifier) { + this.identifier = identifier; + this.methods = new HashSet<>(); + } + + public InteractionEntry addMethod(InteractionMethod method) { + if (!method.identifier().equals(this.identifier)) + throw new IllegalArgumentException("Method's identifier did not match the entry's identifier"); + + this.methods.add(method); + InteractionIdentifier.tryAddDescriptions(identifier, method.identifier()); + return this; + } + + public void runAll(Object context) { + this.methods.stream().forEach(x -> { + logger.debug("Running Method {} from {} with context {}", x.toString(), this.toString(), context.toString()); + x.run(context); + }); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof InteractionEntry)) + return false; + InteractionEntry other = (InteractionEntry) obj; + return other.identifier().equals(identifier()); + } + + @Override + public String toString() { + return "InteractionEntry(%s)".formatted(identifier().toString()); + } + + public InteractionType type() { + return identifier().type(); + } + + public InteractionIdentifier identifier() { + return identifier; + } + + public Set methods() { + return methods; + } + +} diff --git a/lib/src/main/java/net/tomatentum/marinara/registry/InteractionRegistry.java b/lib/src/main/java/net/tomatentum/marinara/registry/InteractionRegistry.java index 27a2152..6314b88 100644 --- a/lib/src/main/java/net/tomatentum/marinara/registry/InteractionRegistry.java +++ b/lib/src/main/java/net/tomatentum/marinara/registry/InteractionRegistry.java @@ -1,9 +1,11 @@ package net.tomatentum.marinara.registry; import java.lang.reflect.Method; -import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; import java.util.List; import java.util.Optional; +import java.util.Set; import org.apache.logging.log4j.Logger; @@ -11,67 +13,68 @@ import net.tomatentum.marinara.Marinara; import net.tomatentum.marinara.interaction.InteractionHandler; import net.tomatentum.marinara.interaction.InteractionType; import net.tomatentum.marinara.interaction.commands.SlashCommandDefinition; -import net.tomatentum.marinara.interaction.commands.ExecutableSlashCommandDefinition; -import net.tomatentum.marinara.interaction.methods.SlashCommandInteractionMethod; +import net.tomatentum.marinara.interaction.ident.InteractionIdentifier; +import net.tomatentum.marinara.interaction.ident.RootCommandIdentifier; import net.tomatentum.marinara.util.LoggerUtil; +import net.tomatentum.marinara.util.ObjectAggregator; +import net.tomatentum.marinara.wrapper.IdentifierProvider; import net.tomatentum.marinara.interaction.methods.InteractionMethod; public class InteractionRegistry { private Logger logger = LoggerUtil.getLogger(getClass()); - private List interactionMethods; + private Set interactions; private Marinara marinara; + private IdentifierProvider identifierProvider; public InteractionRegistry(Marinara marinara) { - this.interactionMethods = new ArrayList<>(); + this.interactions = new HashSet<>(); this.marinara = marinara; + this.identifierProvider = marinara.getWrapper().createIdentifierProvider(); marinara.getWrapper().subscribeInteractions(this::handle); } + /* + * TODO: Maybe relocate InteractionEntry checking to another class with description merging. + */ public void addInteractions(InteractionHandler interactionHandler) { for (Method method : interactionHandler.getClass().getMethods()) { InteractionMethod iMethod = InteractionMethod.create(method, interactionHandler, marinara); if (iMethod != null) { - this.interactionMethods.add(iMethod); - logger.debug("Added {} method from {}", iMethod.getMethod().getName(), interactionHandler.getClass().getSimpleName()); + Optional oentry = this.interactions.stream() + .filter(i -> i.identifier().equals(iMethod.identifier())) + .findFirst(); + + InteractionEntry entry = oentry.orElse(new InteractionEntry(iMethod.identifier())).addMethod(iMethod); + if (oentry.isEmpty()) this.interactions.add(entry); + logger.debug("Added {} method from {}", iMethod.method().getName(), interactionHandler.getClass().getSimpleName()); } } logger.info("Added all Interactions from {}", interactionHandler.getClass().getSimpleName()); } public void registerCommands() { - List defs = new ArrayList<>(); - List execDefs = interactionMethods.stream() - .filter((x) -> x.getClass().isAssignableFrom(SlashCommandInteractionMethod.class)) - .map((x) -> ((SlashCommandInteractionMethod)x).getCommandDefinition()) + List slashIdentifiers = interactions.stream() + .filter((x) -> x.type().equals(InteractionType.COMMAND)) + .map((x) -> x.identifier()) .toList(); - execDefs.forEach((def) -> { - Optional appDef = defs.stream() - .filter((x) -> x.getSlashCommand().equals(def.applicationCommand())) - .findFirst(); - if (appDef.isPresent()) - appDef.get().addExecutableCommand(def); - else - defs.add(new SlashCommandDefinition(def.applicationCommand()).addExecutableCommand(def)); + SlashCommandDefinition[] defs = new ObjectAggregator( + i -> Arrays.asList((RootCommandIdentifier)i.rootNode()), + SlashCommandDefinition::addIdentifier, + SlashCommandDefinition::new) + .aggregate(slashIdentifiers) + .toArray(SlashCommandDefinition[]::new); - logger.debug("Added Executable Command {}{}{} for registration", - def.applicationCommand().name(), - def.subCommandGroup() == null ? "" : "." + def.subCommandGroup().name(), - def.subCommand() == null ? "" : "." + def.subCommand().name() - ); - }); - - marinara.getWrapper().registerSlashCommands(defs.toArray(SlashCommandDefinition[]::new)); + marinara.getWrapper().getRegisterer().register(defs); logger.info("Registered all SlashCommands"); } public void handle(Object context) { - InteractionType type = marinara.getWrapper().getInteractionType(context); logger.debug("Received {} interaction ", context); - interactionMethods.forEach((m) -> { - if (m.getType().equals(type) && m.canRun(context)) { - logger.info("Running {} interaction using {}\ncontext: {}", type, m.getMethod().toString(), context.toString()); - m.run(context); + interactions.forEach((e) -> { + if (this.identifierProvider.provide(context).equals(e.identifier())) { + logger.info("Running {} interaction using {}\ncontext: {}", e.type(), e.toString(), context.toString()); + e.runAll(context); } }); } diff --git a/lib/src/main/java/net/tomatentum/marinara/util/ObjectAggregator.java b/lib/src/main/java/net/tomatentum/marinara/util/ObjectAggregator.java new file mode 100644 index 0000000..b02488f --- /dev/null +++ b/lib/src/main/java/net/tomatentum/marinara/util/ObjectAggregator.java @@ -0,0 +1,46 @@ +package net.tomatentum.marinara.util; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.function.Function; +import java.util.function.Supplier; + +public class ObjectAggregator { + private Function> keySupplier; + private BiConsumer valueConsumer; + private Function defaultGenerator; + + public ObjectAggregator( + Function> keySupplier, + BiConsumer valueConsumer, + Function defaultGenerator) { + this.keySupplier = keySupplier; + this.valueConsumer = valueConsumer; + this.defaultGenerator = defaultGenerator; + } + + public ObjectAggregator( + Function> keySupplier, + BiConsumer valueConsumer, + Supplier defaultGenerator) { + this.keySupplier = keySupplier; + this.valueConsumer = valueConsumer; + this.defaultGenerator = _ -> defaultGenerator.get(); + } + + public Collection aggregate(Iterable iterator) { + Map map = new HashMap<>(); + for (O element : iterator) { + Iterable keys = this.keySupplier.apply(element); + for (K key : keys) { + V value = map.getOrDefault(key, this.defaultGenerator.apply(key)); + this.valueConsumer.accept(value, element); + map.putIfAbsent(key, value); + } + } + return map.values(); + } + +} diff --git a/lib/src/main/java/net/tomatentum/marinara/util/ObjectListAggregator.java b/lib/src/main/java/net/tomatentum/marinara/util/ObjectListAggregator.java new file mode 100644 index 0000000..c3ec89a --- /dev/null +++ b/lib/src/main/java/net/tomatentum/marinara/util/ObjectListAggregator.java @@ -0,0 +1,15 @@ +package net.tomatentum.marinara.util; + +import java.util.ArrayList; +import java.util.function.Function; + +public class ObjectListAggregator extends ObjectAggregator> { + + public ObjectListAggregator(Function> keySupplier, Function valueConsumer) { + super(keySupplier, + (l, o) -> l.add(valueConsumer.apply(o)), + () -> new ArrayList<>()); + } + + +} diff --git a/lib/src/main/java/net/tomatentum/marinara/wrapper/CommandConverter.java b/lib/src/main/java/net/tomatentum/marinara/wrapper/CommandConverter.java new file mode 100644 index 0000000..5cfc76c --- /dev/null +++ b/lib/src/main/java/net/tomatentum/marinara/wrapper/CommandConverter.java @@ -0,0 +1,61 @@ +package net.tomatentum.marinara.wrapper; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import net.tomatentum.marinara.interaction.commands.SlashCommandDefinition; +import net.tomatentum.marinara.interaction.commands.annotation.SlashCommandOption; +import net.tomatentum.marinara.interaction.commands.annotation.SlashCommandOptionChoice; +import net.tomatentum.marinara.interaction.ident.InteractionIdentifier; +import net.tomatentum.marinara.interaction.ident.RootCommandIdentifier; +import net.tomatentum.marinara.interaction.ident.SlashCommandIdentifier; + +public class CommandConverter { + + public static CommandConverter of(Spec spec) { + return new CommandConverter<>(spec); + } + + private Spec spec; + + CommandConverter(Spec spec) { + this.spec = spec; + } + + public A convert(SlashCommandDefinition def) { + List options = new ArrayList<>(); + if (!def.isRootCommand()) { + Arrays.stream(def.getSubCommands()).map(this::convertSubCommand).forEach(options::add); + Arrays.stream(def.getSubCommandGroups()).map(x -> this.convertSubCommandGroup(def, x)).forEach(options::add); + }else + Arrays.stream(def.rootIdentifier().options()).map(this::convertOption).forEach(options::add); + + return spec.convertCommand(def.rootIdentifier(), options); + } + + private O convertSubCommandGroup(SlashCommandDefinition def, InteractionIdentifier identifier) { + SlashCommandIdentifier[] subCommands = def.getSubCommands(identifier.name()); + List convertedSubCommands = Arrays.stream(subCommands).map(this::convertSubCommand).toList(); + return spec.convertSubCommandGroup(identifier, convertedSubCommands); + } + + private O convertSubCommand(SlashCommandIdentifier identifier) { + List options = Arrays.stream(identifier.options()).map(this::convertOption).toList(); + return spec.convertSubCommand(identifier, options); + } + + private O convertOption(SlashCommandOption option) { + List choices = Arrays.stream(SlashCommandDefinition.getActualChoices(option)).map(spec::convertChoice).toList(); + return spec.convertOption(option, choices); + } + + public static interface Spec { + + public A convertCommand(RootCommandIdentifier rootIdentifier, List options); + public O convertSubCommandGroup(InteractionIdentifier identifier, List subCommands); + public O convertSubCommand(InteractionIdentifier identifier, List options); + public O convertOption(SlashCommandOption option, List choices); + public C convertChoice(SlashCommandOptionChoice choice); + } +} diff --git a/lib/src/main/java/net/tomatentum/marinara/wrapper/CommandRegisterer.java b/lib/src/main/java/net/tomatentum/marinara/wrapper/CommandRegisterer.java new file mode 100644 index 0000000..41e17b0 --- /dev/null +++ b/lib/src/main/java/net/tomatentum/marinara/wrapper/CommandRegisterer.java @@ -0,0 +1,45 @@ +package net.tomatentum.marinara.wrapper; + +import java.util.Arrays; +import java.util.Set; +import java.util.stream.Collectors; + +import net.tomatentum.marinara.interaction.commands.SlashCommandDefinition; +import net.tomatentum.marinara.util.ObjectAggregator; + +public class CommandRegisterer { + + public static CommandRegisterer of(Strategy strategy, CommandConverter converter) { + return new CommandRegisterer(strategy, converter); + } + + private Strategy strategy; + private CommandConverter converter; + + CommandRegisterer(Strategy strategy, CommandConverter converter) { + this.strategy = strategy; + this.converter = converter; + } + + public void register(SlashCommandDefinition[] slashDefs) { + Set> serverCommands = new ObjectAggregator>( + def -> Arrays.stream(def.serverIds()).boxed().toList(), + (l, o) -> l.add(converter.convert(o)), + ServerCommandList::new) + .aggregate(Arrays.asList(slashDefs)).stream() + .collect(Collectors.toSet()); + + Set globalCommands = Arrays.stream(slashDefs) + .filter(x -> x.serverIds().length <= 0) + .map(converter::convert) + .collect(Collectors.toSet()); + + serverCommands.forEach(strategy::registerServer); + strategy.registerGlobal(globalCommands); + } + + public interface Strategy { + void registerServer(ServerCommandList commands); + void registerGlobal(Set defs); + } +} diff --git a/lib/src/main/java/net/tomatentum/marinara/wrapper/IdentifierProvider.java b/lib/src/main/java/net/tomatentum/marinara/wrapper/IdentifierProvider.java new file mode 100644 index 0000000..1d3f31d --- /dev/null +++ b/lib/src/main/java/net/tomatentum/marinara/wrapper/IdentifierProvider.java @@ -0,0 +1,72 @@ +package net.tomatentum.marinara.wrapper; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.logging.log4j.Logger; + +import io.leangen.geantyref.GenericTypeReflector; +import net.tomatentum.marinara.interaction.ident.InteractionIdentifier; +import net.tomatentum.marinara.util.LoggerUtil; +import net.tomatentum.marinara.util.ReflectionUtil; + +public class IdentifierProvider { + + public static IdentifierProvider of(Converter... converter) { + return new IdentifierProvider(Arrays.asList(converter)); + } + + private Map, Converter> converter; + private Logger logger = LoggerUtil.getLogger(getClass()); + + private IdentifierProvider(List> converter) { + this.converter = new HashMap<>(); + for (Converter conv : converter) { + if (conv.getClass().getName().contains("$$Lambda")) + throw new IllegalArgumentException("Lambdas cannot be used for IdentifierConverter because of Type erasure."); + Type type = GenericTypeReflector.getExactSuperType(conv.getClass(), Converter.class); + Type parameterType = (Class) ((ParameterizedType) type).getActualTypeArguments()[0]; + if (!(parameterType instanceof Class)) + throw new IllegalArgumentException("Only full Class types are supported by IdentiferConverters"); + this.converter.put((Class) parameterType, conv); + } + } + + public InteractionIdentifier provide(Object context) { + Type type = ReflectionUtil.getMostSpecificClass( + converter.keySet().stream().filter(x -> x.isAssignableFrom(context.getClass())).toArray(Class[]::new), + context.getClass()); + + if (type == null) + logger.debug("No Identifier converter found for context {}", context.getClass().toString()); + + @SuppressWarnings("unchecked") + Converter conv = (Converter) converter.get(type); + + return conv.convert(context); + } + + @FunctionalInterface + public interface Converter { + InteractionIdentifier convert(T context); + } + + public static class LambdaWrapper implements Converter { + + private Converter converter; + + LambdaWrapper(Converter converter) { + this.converter = converter; + } + + @Override + public InteractionIdentifier convert(T context) { + return this.converter.convert(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 7bf932a..5b3ba04 100644 --- a/lib/src/main/java/net/tomatentum/marinara/wrapper/LibraryWrapper.java +++ b/lib/src/main/java/net/tomatentum/marinara/wrapper/LibraryWrapper.java @@ -3,17 +3,12 @@ package net.tomatentum.marinara.wrapper; import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; - -import net.tomatentum.marinara.interaction.commands.SlashCommandDefinition; -import net.tomatentum.marinara.interaction.commands.ExecutableSlashCommandDefinition; -import net.tomatentum.marinara.interaction.InteractionType; - public abstract class LibraryWrapper { private List> interactionSubscriber; protected LibraryWrapper() { - interactionSubscriber = new ArrayList<>(); + this.interactionSubscriber = new ArrayList<>(); } public void handleInteraction(Object context) { @@ -27,13 +22,8 @@ public abstract class LibraryWrapper { interactionSubscriber.remove(consumer); } - public abstract InteractionType getInteractionType(Object context); - - public abstract void registerSlashCommands(SlashCommandDefinition[] defs); - public abstract ExecutableSlashCommandDefinition getCommandDefinition(Object context); - - public abstract String getButtonId(Object context); - + public abstract CommandRegisterer getRegisterer(); + public abstract IdentifierProvider createIdentifierProvider(); public abstract ContextObjectProvider getContextObjectProvider(); } \ No newline at end of file diff --git a/lib/src/main/java/net/tomatentum/marinara/wrapper/ServerCommandList.java b/lib/src/main/java/net/tomatentum/marinara/wrapper/ServerCommandList.java new file mode 100644 index 0000000..24fdf29 --- /dev/null +++ b/lib/src/main/java/net/tomatentum/marinara/wrapper/ServerCommandList.java @@ -0,0 +1,16 @@ +package net.tomatentum.marinara.wrapper; + +import java.util.HashSet; + +public class ServerCommandList extends HashSet{ + + private long serverId; + + public ServerCommandList(long serverId) { + this.serverId = serverId; + } + + public long serverId() { + return serverId; + } +} diff --git a/wrapper/discord4j/src/main/java/net/tomatentum/marinara/wrapper/discord4j/Discord4JConverterSpec.java b/wrapper/discord4j/src/main/java/net/tomatentum/marinara/wrapper/discord4j/Discord4JConverterSpec.java new file mode 100644 index 0000000..b5579b6 --- /dev/null +++ b/wrapper/discord4j/src/main/java/net/tomatentum/marinara/wrapper/discord4j/Discord4JConverterSpec.java @@ -0,0 +1,75 @@ +package net.tomatentum.marinara.wrapper.discord4j; + +import java.util.List; + +import discord4j.core.object.command.ApplicationCommandOption.Type; +import discord4j.discordjson.json.ApplicationCommandOptionChoiceData; +import discord4j.discordjson.json.ApplicationCommandOptionData; +import discord4j.discordjson.json.ApplicationCommandRequest; +import net.tomatentum.marinara.interaction.commands.annotation.SlashCommandOption; +import net.tomatentum.marinara.interaction.commands.annotation.SlashCommandOptionChoice; +import net.tomatentum.marinara.interaction.ident.InteractionIdentifier; +import net.tomatentum.marinara.interaction.ident.RootCommandIdentifier; +import net.tomatentum.marinara.wrapper.CommandConverter; + +public class Discord4JConverterSpec implements CommandConverter.Spec { + + @Override + public ApplicationCommandRequest convertCommand(RootCommandIdentifier rootIdentifier, + List options) { + return ApplicationCommandRequest.builder() + .name(rootIdentifier.name()) + .description(rootIdentifier.description()) + .options(options) + .build(); + } + + @Override + public ApplicationCommandOptionData convertSubCommandGroup(InteractionIdentifier identifier, + List subCommands) { + return ApplicationCommandOptionData.builder() + .type(Type.SUB_COMMAND_GROUP.getValue()) + .name(identifier.name()) + .description(identifier.description()) + .options(subCommands) + .build(); + } + + @Override + public ApplicationCommandOptionData convertSubCommand(InteractionIdentifier identifier, + List options) { + return ApplicationCommandOptionData.builder() + .type(Type.SUB_COMMAND_GROUP.getValue()) + .name(identifier.name()) + .description(identifier.description()) + .options(options) + .build(); + } + + @Override + public ApplicationCommandOptionData convertOption(SlashCommandOption option, + List choices) { + Type type = Type.of(option.type().getValue()); + return ApplicationCommandOptionData.builder() + .type(type.getValue()) + .name(option.name()) + .description(option.description()) + .required(option.required()) + .autocomplete(option.autocomplete()) + .choices(choices) + .build(); + } + + @Override + public ApplicationCommandOptionChoiceData convertChoice(SlashCommandOptionChoice choice) { + var builder = ApplicationCommandOptionChoiceData.builder().name(choice.name()); + if (choice.longValue() != Long.MAX_VALUE) + builder.value(choice.longValue()); + if (choice.doubleValue() != Double.MAX_VALUE) + builder.value(choice.doubleValue()); + if (!choice.stringValue().isEmpty()) + builder.value(choice.stringValue()); + return builder.build(); + } + +} diff --git a/wrapper/discord4j/src/main/java/net/tomatentum/marinara/wrapper/discord4j/Discord4JRegistererStrategy.java b/wrapper/discord4j/src/main/java/net/tomatentum/marinara/wrapper/discord4j/Discord4JRegistererStrategy.java new file mode 100644 index 0000000..fea6e5c --- /dev/null +++ b/wrapper/discord4j/src/main/java/net/tomatentum/marinara/wrapper/discord4j/Discord4JRegistererStrategy.java @@ -0,0 +1,32 @@ +package net.tomatentum.marinara.wrapper.discord4j; + +import java.util.ArrayList; +import java.util.Set; + +import discord4j.core.GatewayDiscordClient; +import discord4j.discordjson.json.ApplicationCommandRequest; +import discord4j.rest.service.ApplicationService; +import net.tomatentum.marinara.wrapper.CommandRegisterer; +import net.tomatentum.marinara.wrapper.ServerCommandList; + +public class Discord4JRegistererStrategy implements CommandRegisterer.Strategy { + + private ApplicationService appService; + private long applicationId; + + public Discord4JRegistererStrategy(GatewayDiscordClient api) { + this.appService = api.getRestClient().getApplicationService(); + this.applicationId = api.getRestClient().getApplicationId().block(); + } + + @Override + public void registerServer(ServerCommandList commands) { + appService.bulkOverwriteGuildApplicationCommand(applicationId, commands.serverId(), new ArrayList<>(commands)); + + } + + @Override + public void registerGlobal(Set defs) { + appService.bulkOverwriteGlobalApplicationCommand(applicationId, new ArrayList<>(defs)); + } +} diff --git a/wrapper/discord4j/src/main/java/net/tomatentum/marinara/wrapper/discord4j/Discord4JWrapper.java b/wrapper/discord4j/src/main/java/net/tomatentum/marinara/wrapper/discord4j/Discord4JWrapper.java index cb65bbc..b14ce0f 100644 --- a/wrapper/discord4j/src/main/java/net/tomatentum/marinara/wrapper/discord4j/Discord4JWrapper.java +++ b/wrapper/discord4j/src/main/java/net/tomatentum/marinara/wrapper/discord4j/Discord4JWrapper.java @@ -1,38 +1,25 @@ package net.tomatentum.marinara.wrapper.discord4j; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.function.Function; import org.apache.logging.log4j.Logger; import discord4j.core.GatewayDiscordClient; -import discord4j.core.event.domain.interaction.ButtonInteractionEvent; -import discord4j.core.event.domain.interaction.ChatInputAutoCompleteEvent; -import discord4j.core.event.domain.interaction.ChatInputInteractionEvent; import discord4j.core.event.domain.interaction.InteractionCreateEvent; import discord4j.core.object.command.ApplicationCommandInteractionOption; import discord4j.core.object.command.ApplicationCommandOption.Type; -import discord4j.discordjson.json.ApplicationCommandOptionChoiceData; -import discord4j.discordjson.json.ApplicationCommandOptionData; import discord4j.discordjson.json.ApplicationCommandRequest; -import io.leangen.geantyref.AnnotationFormatException; -import io.leangen.geantyref.TypeFactory; -import net.tomatentum.marinara.interaction.InteractionType; -import net.tomatentum.marinara.interaction.commands.ExecutableSlashCommandDefinition; -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.util.LoggerUtil; +import net.tomatentum.marinara.wrapper.CommandConverter; +import net.tomatentum.marinara.wrapper.CommandRegisterer; import net.tomatentum.marinara.wrapper.ContextObjectProvider; +import net.tomatentum.marinara.wrapper.IdentifierProvider; import net.tomatentum.marinara.wrapper.LibraryWrapper; +import net.tomatentum.marinara.wrapper.discord4j.identifierconverter.AutocompleteIdentifierConverter; +import net.tomatentum.marinara.wrapper.discord4j.identifierconverter.ButtonIdentifierConverter; +import net.tomatentum.marinara.wrapper.discord4j.identifierconverter.SlashCommandIdentifierConverter; public class Discord4JWrapper extends LibraryWrapper { @@ -46,166 +33,42 @@ public class Discord4JWrapper extends LibraryWrapper { .filter(o -> !o.getType().equals(Type.SUB_COMMAND) && !o.getType().equals(Type.SUB_COMMAND_GROUP)) .toList(); - private GatewayDiscordClient api; private Discord4JContextObjectProvider contextObjectProvider; + private CommandRegisterer commandRegisterer; private Logger logger = LoggerUtil.getLogger(getClass()); public Discord4JWrapper(GatewayDiscordClient api) { - this.api = api; this.contextObjectProvider = new Discord4JContextObjectProvider(); - if (api != null) + var converter = CommandConverter.of(new Discord4JConverterSpec()); + + if (api != null) { + this.commandRegisterer = CommandRegisterer.of(new Discord4JRegistererStrategy(api), converter); api.on(InteractionCreateEvent.class) .subscribe(event -> handleInteraction(event)); - else + }else logger.warn("GatewayDiscordClient was null so no Events were subscribed to."); logger.info("Discord4J wrapper loaded!"); } @Override - public InteractionType getInteractionType(Object context) { - if (ChatInputAutoCompleteEvent.class.isAssignableFrom(context.getClass())) - return InteractionType.AUTOCOMPLETE; - if (ChatInputInteractionEvent.class.isAssignableFrom(context.getClass())) - return InteractionType.COMMAND; - if (ButtonInteractionEvent.class.isAssignableFrom(context.getClass())) - return InteractionType.BUTTON; - - return null; + public CommandRegisterer getRegisterer() { + return this.commandRegisterer; } @Override - public void registerSlashCommands(SlashCommandDefinition[] defs) { - HashMap> serverCommands = new HashMap<>(); - List globalCommands = new ArrayList<>(); - long applicationId = api.getRestClient().getApplicationId().block(); - - for (SlashCommandDefinition slashCommandDefinition : defs) { - ApplicationCommandRequest request = convertSlashCommand(slashCommandDefinition); - if (slashCommandDefinition.getFullSlashCommand().serverIds().length > 0) { - for (long serverId : slashCommandDefinition.getFullSlashCommand().serverIds()) { - serverCommands.putIfAbsent(serverId, new ArrayList<>()); - serverCommands.get(serverId).add(request); - } - }else - globalCommands.add(request); - } - - for (long serverId : serverCommands.keySet()) { - api.getRestClient().getApplicationService().bulkOverwriteGuildApplicationCommand(applicationId, serverId, serverCommands.get(serverId)); - } - api.getRestClient().getApplicationService().bulkOverwriteGlobalApplicationCommand(applicationId, globalCommands); - } - - @Override - public ExecutableSlashCommandDefinition getCommandDefinition(Object context) { - List options; - String commandName; - - if (context instanceof ChatInputInteractionEvent) { - ChatInputInteractionEvent interaction = (ChatInputInteractionEvent) context; - options = SUB_FILTER.apply(interaction.getOptions()); - commandName = interaction.getCommandName(); - }else if (context instanceof ChatInputAutoCompleteEvent) { - ChatInputAutoCompleteEvent interaction = (ChatInputAutoCompleteEvent) context; - options = SUB_FILTER.apply(interaction.getOptions()); - commandName = interaction.getCommandName(); - }else - return null; - - ExecutableSlashCommandDefinition.Builder builder = new ExecutableSlashCommandDefinition.Builder(); - - try { - builder.setApplicationCommand(TypeFactory.annotation(SlashCommand.class, Map.of("name", commandName))); - if (!options.isEmpty()) { - if (!ARG_FILTER.apply(options.getFirst().getOptions()).isEmpty()) { - builder.setSubCommandGroup(TypeFactory.annotation(SubCommandGroup.class, Map.of("name", options.getFirst().getName()))); - builder.setSubCommand(TypeFactory.annotation(SubCommand.class, Map.of("name", SUB_FILTER.apply(options.getFirst().getOptions()).getFirst().getName()))); - }else - builder.setSubCommand(TypeFactory.annotation(SubCommand.class, Map.of("name", options.getFirst().getName()))); - } - } catch (AnnotationFormatException e) { - logger.fatal(e); - } - - return builder.build(); - } - - private ApplicationCommandRequest convertSlashCommand(SlashCommandDefinition def) { - List options = new ArrayList<>(); - SlashCommand cmd = def.getFullSlashCommand(); - if (!def.isRootCommand()) { - Arrays.stream(def.getSubCommands(null)).map(this::convertSubCommandDef).forEach(options::add); - Arrays.stream(def.getSubCommandGroups()).map((x) -> convertSubCommandGroupDef(def, x)).forEach(options::add); - }else { - Arrays.stream(cmd.options()).map(this::convertOptionDef).forEach(options::add); - } - - return ApplicationCommandRequest.builder() - .name(cmd.name()) - .description(cmd.description()) - .options(options) - .build(); - } - - private ApplicationCommandOptionData convertSubCommandGroupDef(SlashCommandDefinition def, SubCommandGroup subGroup) { - SubCommand[] subCommands = def.getSubCommands(subGroup.name()); - List convertedSubCommands = Arrays.stream(subCommands).map(this::convertSubCommandDef).toList(); - return ApplicationCommandOptionData.builder() - .type(Type.SUB_COMMAND_GROUP.getValue()) - .name(subGroup.name()) - .description(subGroup.description()) - .options(convertedSubCommands) - .build(); - } - - private ApplicationCommandOptionData convertSubCommandDef(SubCommand sub) { - List convertedOptions = Arrays.stream(sub.options()).map(this::convertOptionDef).toList(); - return ApplicationCommandOptionData.builder() - .type(Type.SUB_COMMAND_GROUP.getValue()) - .name(sub.name()) - .description(sub.description()) - .options(convertedOptions) - .build(); - } - - private ApplicationCommandOptionData convertOptionDef(SlashCommandOption option) { - Type type = Enum.valueOf(Type.class, option.type().toString()); - return ApplicationCommandOptionData.builder() - .type(type.getValue()) - .name(option.name()) - .description(option.description()) - .required(option.required()) - .autocomplete(option.autocomplete()) - .choices(convertChoices(option)) - .build(); - } - - private List convertChoices(SlashCommandOption option) { - List convertedChoices = new ArrayList<>(); - for (SlashCommandOptionChoice choice : ExecutableSlashCommandDefinition.getActualChoices(option)) { - var builder = ApplicationCommandOptionChoiceData.builder(); - builder.name(choice.name()); - if (choice.longValue() != Long.MAX_VALUE) - builder.value(choice.longValue()); - if (choice.doubleValue() != Double.MAX_VALUE) - builder.value(choice.doubleValue()); - if (!choice.stringValue().isEmpty()) - builder.value(choice.stringValue()); - } - return convertedChoices; - } - - @Override - public String getButtonId(Object context) { - ButtonInteractionEvent button = (ButtonInteractionEvent) context; - return button.getCustomId(); + public IdentifierProvider createIdentifierProvider() { + return IdentifierProvider.of( + new SlashCommandIdentifierConverter(), + new AutocompleteIdentifierConverter(), + new ButtonIdentifierConverter() + ); } @Override public ContextObjectProvider getContextObjectProvider() { return this.contextObjectProvider; } - + } diff --git a/wrapper/discord4j/src/main/java/net/tomatentum/marinara/wrapper/discord4j/identifierconverter/AutocompleteIdentifierConverter.java b/wrapper/discord4j/src/main/java/net/tomatentum/marinara/wrapper/discord4j/identifierconverter/AutocompleteIdentifierConverter.java new file mode 100644 index 0000000..49761d9 --- /dev/null +++ b/wrapper/discord4j/src/main/java/net/tomatentum/marinara/wrapper/discord4j/identifierconverter/AutocompleteIdentifierConverter.java @@ -0,0 +1,38 @@ +package net.tomatentum.marinara.wrapper.discord4j.identifierconverter; + +import java.util.List; + +import discord4j.core.event.domain.interaction.ChatInputAutoCompleteEvent; +import discord4j.core.object.command.ApplicationCommandInteractionOption; +import net.tomatentum.marinara.interaction.InteractionType; +import net.tomatentum.marinara.interaction.ident.InteractionIdentifier; +import net.tomatentum.marinara.wrapper.IdentifierProvider; +import net.tomatentum.marinara.wrapper.discord4j.Discord4JWrapper; + +public class AutocompleteIdentifierConverter implements IdentifierProvider.Converter { + + @Override + public InteractionIdentifier convert(ChatInputAutoCompleteEvent context) { + List options = Discord4JWrapper.SUB_FILTER.apply(context.getOptions()); + String commandName = context.getCommandName(); + + if (!options.isEmpty()) { + List sub_options = Discord4JWrapper.SUB_FILTER.apply(options.getFirst().getOptions()); + if (!sub_options.isEmpty()) + return InteractionIdentifier.createHierarchy( + InteractionType.AUTOCOMPLETE, + commandName, + options.getFirst().getName(), + sub_options.getFirst().getName()); + else + return InteractionIdentifier.createHierarchy( + InteractionType.AUTOCOMPLETE, + commandName, + options.getFirst().getName()); + }else + return InteractionIdentifier.createHierarchy( + InteractionType.AUTOCOMPLETE, + commandName); + } + +} diff --git a/wrapper/discord4j/src/main/java/net/tomatentum/marinara/wrapper/discord4j/identifierconverter/ButtonIdentifierConverter.java b/wrapper/discord4j/src/main/java/net/tomatentum/marinara/wrapper/discord4j/identifierconverter/ButtonIdentifierConverter.java new file mode 100644 index 0000000..8751d4f --- /dev/null +++ b/wrapper/discord4j/src/main/java/net/tomatentum/marinara/wrapper/discord4j/identifierconverter/ButtonIdentifierConverter.java @@ -0,0 +1,15 @@ +package net.tomatentum.marinara.wrapper.discord4j.identifierconverter; + +import discord4j.core.event.domain.interaction.ButtonInteractionEvent; +import net.tomatentum.marinara.interaction.InteractionType; +import net.tomatentum.marinara.interaction.ident.InteractionIdentifier; +import net.tomatentum.marinara.wrapper.IdentifierProvider; + +public class ButtonIdentifierConverter implements IdentifierProvider.Converter { + + @Override + public InteractionIdentifier convert(ButtonInteractionEvent context) { + return InteractionIdentifier.builder().name(context.getCustomId()).type(InteractionType.BUTTON).build(); + } + +} diff --git a/wrapper/discord4j/src/main/java/net/tomatentum/marinara/wrapper/discord4j/identifierconverter/SlashCommandIdentifierConverter.java b/wrapper/discord4j/src/main/java/net/tomatentum/marinara/wrapper/discord4j/identifierconverter/SlashCommandIdentifierConverter.java new file mode 100644 index 0000000..612cd0a --- /dev/null +++ b/wrapper/discord4j/src/main/java/net/tomatentum/marinara/wrapper/discord4j/identifierconverter/SlashCommandIdentifierConverter.java @@ -0,0 +1,39 @@ +package net.tomatentum.marinara.wrapper.discord4j.identifierconverter; + +import java.util.List; + +import discord4j.core.event.domain.interaction.ChatInputInteractionEvent; +import discord4j.core.object.command.ApplicationCommandInteractionOption; +import net.tomatentum.marinara.interaction.InteractionType; +import net.tomatentum.marinara.interaction.ident.InteractionIdentifier; +import net.tomatentum.marinara.wrapper.IdentifierProvider; +import net.tomatentum.marinara.wrapper.discord4j.Discord4JWrapper; + +public class SlashCommandIdentifierConverter implements IdentifierProvider.Converter { + + @Override + public InteractionIdentifier convert(ChatInputInteractionEvent context) { + List options = Discord4JWrapper.SUB_FILTER.apply(context.getOptions()); + String commandName = context.getCommandName(); + + if (!options.isEmpty()) { + List sub_options = Discord4JWrapper.SUB_FILTER.apply(options.getFirst().getOptions()); + if (!sub_options.isEmpty()) + return InteractionIdentifier.createHierarchy( + InteractionType.COMMAND, + commandName, + options.getFirst().getName(), + sub_options.getFirst().getName()); + else + return InteractionIdentifier.createHierarchy( + InteractionType.COMMAND, + commandName, + options.getFirst().getName()); + }else + return InteractionIdentifier.createHierarchy( + InteractionType.COMMAND, + commandName); + + } + +} diff --git a/wrapper/discord4j/src/test/java/net/tomatentum/marinara/test/discord4j/TestChoiceEnum.java b/wrapper/discord4j/src/test/java/net/tomatentum/marinara/test/discord4j/TestChoiceEnum.java index 66ab699..015510c 100644 --- a/wrapper/discord4j/src/test/java/net/tomatentum/marinara/test/discord4j/TestChoiceEnum.java +++ b/wrapper/discord4j/src/test/java/net/tomatentum/marinara/test/discord4j/TestChoiceEnum.java @@ -1,6 +1,6 @@ package net.tomatentum.marinara.test.discord4j; -import net.tomatentum.marinara.interaction.commands.ChoiceValueProvider; +import net.tomatentum.marinara.interaction.commands.choice.ChoiceValueProvider; public enum TestChoiceEnum implements ChoiceValueProvider { TestValue("testValue"), diff --git a/wrapper/javacord/src/main/java/net/tomatentum/marinara/wrapper/javacord/JavacordConverterSpec.java b/wrapper/javacord/src/main/java/net/tomatentum/marinara/wrapper/javacord/JavacordConverterSpec.java new file mode 100644 index 0000000..e6bde68 --- /dev/null +++ b/wrapper/javacord/src/main/java/net/tomatentum/marinara/wrapper/javacord/JavacordConverterSpec.java @@ -0,0 +1,73 @@ +package net.tomatentum.marinara.wrapper.javacord; + +import java.util.List; + +import org.javacord.api.interaction.SlashCommand; +import org.javacord.api.interaction.SlashCommandBuilder; +import org.javacord.api.interaction.SlashCommandOption; +import org.javacord.api.interaction.SlashCommandOptionBuilder; +import org.javacord.api.interaction.SlashCommandOptionChoice; +import org.javacord.api.interaction.SlashCommandOptionChoiceBuilder; +import org.javacord.api.interaction.SlashCommandOptionType; + +import net.tomatentum.marinara.interaction.ident.InteractionIdentifier; +import net.tomatentum.marinara.interaction.ident.RootCommandIdentifier; +import net.tomatentum.marinara.wrapper.CommandConverter; + +public class JavacordConverterSpec implements CommandConverter.Spec { + + @Override + public SlashCommandBuilder convertCommand(RootCommandIdentifier rootIdentifier, List options) { + return SlashCommand.with(rootIdentifier.name(), rootIdentifier.description(), options); + } + + @Override + public SlashCommandOption convertSubCommandGroup(InteractionIdentifier identifier, + List subCommands) { + return SlashCommandOption.createWithOptions( + SlashCommandOptionType.SUB_COMMAND_GROUP, + identifier.name(), + identifier.description(), + subCommands); + } + + @Override + public SlashCommandOption convertSubCommand(InteractionIdentifier identifier, List options) { + return SlashCommandOption.createWithOptions( + SlashCommandOptionType.SUB_COMMAND, + identifier.name(), + identifier.description(), + options); + } + + @Override + public SlashCommandOption convertOption( + net.tomatentum.marinara.interaction.commands.annotation.SlashCommandOption option, + List choices) { + SlashCommandOptionType type = SlashCommandOptionType.fromValue(option.type().getValue()); + return new SlashCommandOptionBuilder() + .setType(type) + .setName(option.name()) + .setDescription(option.description()) + .setRequired(option.required()) + .setAutocompletable(option.autocomplete()) + .setChoices(choices) + .build(); + } + + @Override + public SlashCommandOptionChoice convertChoice( + net.tomatentum.marinara.interaction.commands.annotation.SlashCommandOptionChoice choice) { + SlashCommandOptionChoiceBuilder builder = new SlashCommandOptionChoiceBuilder().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 builder.build(); + } +} diff --git a/wrapper/javacord/src/main/java/net/tomatentum/marinara/wrapper/javacord/JavacordRegistererStrategy.java b/wrapper/javacord/src/main/java/net/tomatentum/marinara/wrapper/javacord/JavacordRegistererStrategy.java new file mode 100644 index 0000000..4719f28 --- /dev/null +++ b/wrapper/javacord/src/main/java/net/tomatentum/marinara/wrapper/javacord/JavacordRegistererStrategy.java @@ -0,0 +1,29 @@ +package net.tomatentum.marinara.wrapper.javacord; + +import java.util.Set; + +import org.javacord.api.DiscordApi; +import org.javacord.api.interaction.SlashCommandBuilder; + +import net.tomatentum.marinara.wrapper.CommandRegisterer; +import net.tomatentum.marinara.wrapper.ServerCommandList; + +public class JavacordRegistererStrategy implements CommandRegisterer.Strategy { + + private DiscordApi api; + + public JavacordRegistererStrategy(DiscordApi api) { + this.api = api; + } + + @Override + public void registerServer(ServerCommandList commands) { + api.bulkOverwriteServerApplicationCommands(commands.serverId(), commands); + } + + @Override + public void registerGlobal(Set defs) { + api.bulkOverwriteGlobalApplicationCommands(defs); + } + +} 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 d18545c..0390fc0 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 @@ -1,180 +1,50 @@ package net.tomatentum.marinara.wrapper.javacord; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - import org.apache.logging.log4j.Logger; 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; -import net.tomatentum.marinara.interaction.InteractionType; -import net.tomatentum.marinara.interaction.commands.ExecutableSlashCommandDefinition; -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.wrapper.CommandConverter; +import net.tomatentum.marinara.wrapper.CommandRegisterer; import net.tomatentum.marinara.wrapper.ContextObjectProvider; +import net.tomatentum.marinara.wrapper.IdentifierProvider; import net.tomatentum.marinara.util.LoggerUtil; import net.tomatentum.marinara.wrapper.LibraryWrapper; +import net.tomatentum.marinara.wrapper.javacord.identifierconverter.AutocompleteIdentifierConverter; +import net.tomatentum.marinara.wrapper.javacord.identifierconverter.ButtonIdentifierConverter; +import net.tomatentum.marinara.wrapper.javacord.identifierconverter.SlashCommandIdentifierConverter; public class JavacordWrapper extends LibraryWrapper { - private DiscordApi api; private JavacordContextObjectProvider contextObjectProvider; + private CommandRegisterer commandRegisterer; private Logger logger = LoggerUtil.getLogger(getClass()); public JavacordWrapper(DiscordApi api) { - this.api = api; this.contextObjectProvider = new JavacordContextObjectProvider(); - if (api != null) + var converter = CommandConverter.of(new JavacordConverterSpec()); + + if (api != null) { + this.commandRegisterer = CommandRegisterer.of(new JavacordRegistererStrategy(api), converter); api.addInteractionCreateListener((e) -> handleInteraction(e.getInteraction())); - else + }else logger.warn("DiscordApi was null so no Events were subscribed to."); logger.info("Javacord wrapper loaded!"); } @Override - public InteractionType getInteractionType(Object context) { - if (AutocompleteInteraction.class.isAssignableFrom(context.getClass())) - return InteractionType.AUTOCOMPLETE; - if (ApplicationCommandInteraction.class.isAssignableFrom(context.getClass())) - return InteractionType.COMMAND; - if (ButtonInteraction.class.isAssignableFrom(context.getClass())) - return InteractionType.BUTTON; - return null; + public CommandRegisterer getRegisterer() { + return this.commandRegisterer; } @Override - public void registerSlashCommands(SlashCommandDefinition[] defs) { - HashMap> serverCommands = new HashMap<>(); - Set globalCommands = new HashSet<>(); - for (SlashCommandDefinition slashCommandDefinition : defs) { - SlashCommandBuilder builder = convertSlashCommand(slashCommandDefinition); - if (slashCommandDefinition.getFullSlashCommand().serverIds().length > 0) { - for (long serverId : slashCommandDefinition.getFullSlashCommand().serverIds()) { - serverCommands.putIfAbsent(serverId, new HashSet<>()); - serverCommands.get(serverId).add(builder); - } - }else - globalCommands.add(builder); - } - - for (long serverId : serverCommands.keySet()) { - api.bulkOverwriteServerApplicationCommands(serverId, serverCommands.get(serverId)); - } - api.bulkOverwriteGlobalApplicationCommands(globalCommands); - } - - @Override - public ExecutableSlashCommandDefinition getCommandDefinition(Object context) { - if (!(context instanceof SlashCommandInteraction)) - return null; - - SlashCommandInteraction interaction = (SlashCommandInteraction) context; - ExecutableSlashCommandDefinition.Builder builder = new ExecutableSlashCommandDefinition.Builder(); - List options = interaction.getOptions(); - try { - builder.setApplicationCommand(TypeFactory.annotation(SlashCommand.class, Map.of("name", interaction.getCommandName()))); - 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) { - logger.fatal(e); - } - - return builder.build(); - } - - private SlashCommandBuilder convertSlashCommand(SlashCommandDefinition def) { - List options = new ArrayList<>(); - SlashCommand cmd = def.getFullSlashCommand(); - if (!def.isRootCommand()) { - Arrays.stream(def.getSubCommands(null)).map(this::convertSubCommandDef).forEach(options::add); - Arrays.stream(def.getSubCommandGroups()).map((x) -> convertSubCommandGroupDef(def, x)).forEach(options::add); - }else { - Arrays.stream(cmd.options()).map(this::convertOptionDef).forEach(options::add); - } - - return org.javacord.api.interaction.SlashCommand.with(cmd.name(), cmd.description(), options); - } - - private org.javacord.api.interaction.SlashCommandOption convertSubCommandGroupDef(SlashCommandDefinition def, SubCommandGroup subGroup) { - SubCommand[] subCommands = def.getSubCommands(subGroup.name()); - List convertedSubCommands = Arrays.stream(subCommands).map(this::convertSubCommandDef).toList(); - return org.javacord.api.interaction.SlashCommandOption.createWithOptions( - org.javacord.api.interaction.SlashCommandOptionType.SUB_COMMAND_GROUP, - subGroup.name(), - subGroup.description(), - convertedSubCommands); - } - - private org.javacord.api.interaction.SlashCommandOption convertSubCommandDef(SubCommand sub) { - List convertedOptions = Arrays.stream(sub.options()).map(this::convertOptionDef).toList(); - 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) { - 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 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 - public String getButtonId(Object context) { - ButtonInteraction button = (ButtonInteraction) context; - return button.getCustomId(); + public IdentifierProvider createIdentifierProvider() { + return IdentifierProvider.of( + new SlashCommandIdentifierConverter(), + new AutocompleteIdentifierConverter(), + new ButtonIdentifierConverter() + ); } @Override diff --git a/wrapper/javacord/src/main/java/net/tomatentum/marinara/wrapper/javacord/identifierconverter/AutocompleteIdentifierConverter.java b/wrapper/javacord/src/main/java/net/tomatentum/marinara/wrapper/javacord/identifierconverter/AutocompleteIdentifierConverter.java new file mode 100644 index 0000000..65fef44 --- /dev/null +++ b/wrapper/javacord/src/main/java/net/tomatentum/marinara/wrapper/javacord/identifierconverter/AutocompleteIdentifierConverter.java @@ -0,0 +1,37 @@ +package net.tomatentum.marinara.wrapper.javacord.identifierconverter; + +import java.util.List; + +import org.javacord.api.interaction.AutocompleteInteraction; +import org.javacord.api.interaction.SlashCommandInteractionOption; + +import net.tomatentum.marinara.interaction.InteractionType; +import net.tomatentum.marinara.interaction.ident.InteractionIdentifier; +import net.tomatentum.marinara.wrapper.IdentifierProvider; + +public class AutocompleteIdentifierConverter implements IdentifierProvider.Converter { + + @Override + public InteractionIdentifier convert(AutocompleteInteraction context) { + List options = context.getOptions(); + String commandName = context.getCommandName(); + if (!options.isEmpty()) { + List sub_options = context.getOptions().getFirst().getOptions(); + if (!sub_options.isEmpty()) + return InteractionIdentifier.createHierarchy( + InteractionType.AUTOCOMPLETE, + commandName, + options.getFirst().getName(), + sub_options.getFirst().getName()); + else + return InteractionIdentifier.createHierarchy( + InteractionType.AUTOCOMPLETE, + commandName, + options.getFirst().getName()); + }else + return InteractionIdentifier.createHierarchy( + InteractionType.AUTOCOMPLETE, + commandName); + } + +} diff --git a/wrapper/javacord/src/main/java/net/tomatentum/marinara/wrapper/javacord/identifierconverter/ButtonIdentifierConverter.java b/wrapper/javacord/src/main/java/net/tomatentum/marinara/wrapper/javacord/identifierconverter/ButtonIdentifierConverter.java new file mode 100644 index 0000000..209ce32 --- /dev/null +++ b/wrapper/javacord/src/main/java/net/tomatentum/marinara/wrapper/javacord/identifierconverter/ButtonIdentifierConverter.java @@ -0,0 +1,16 @@ +package net.tomatentum.marinara.wrapper.javacord.identifierconverter; + +import org.javacord.api.interaction.ButtonInteraction; + +import net.tomatentum.marinara.interaction.InteractionType; +import net.tomatentum.marinara.interaction.ident.InteractionIdentifier; +import net.tomatentum.marinara.wrapper.IdentifierProvider; + +public class ButtonIdentifierConverter implements IdentifierProvider.Converter { + + @Override + public InteractionIdentifier convert(ButtonInteraction context) { + return InteractionIdentifier.builder().name(context.getCustomId()).type(InteractionType.BUTTON).build(); + } + +} diff --git a/wrapper/javacord/src/main/java/net/tomatentum/marinara/wrapper/javacord/identifierconverter/SlashCommandIdentifierConverter.java b/wrapper/javacord/src/main/java/net/tomatentum/marinara/wrapper/javacord/identifierconverter/SlashCommandIdentifierConverter.java new file mode 100644 index 0000000..4b90bd1 --- /dev/null +++ b/wrapper/javacord/src/main/java/net/tomatentum/marinara/wrapper/javacord/identifierconverter/SlashCommandIdentifierConverter.java @@ -0,0 +1,38 @@ +package net.tomatentum.marinara.wrapper.javacord.identifierconverter; + +import java.util.List; + +import org.javacord.api.interaction.SlashCommandInteraction; +import org.javacord.api.interaction.SlashCommandInteractionOption; + +import net.tomatentum.marinara.interaction.InteractionType; +import net.tomatentum.marinara.interaction.ident.InteractionIdentifier; +import net.tomatentum.marinara.wrapper.IdentifierProvider; + +public class SlashCommandIdentifierConverter implements IdentifierProvider.Converter { + + @Override + public InteractionIdentifier convert(SlashCommandInteraction context) { + List options = context.getOptions(); + String commandName = context.getCommandName(); + if (!options.isEmpty()) { + List sub_options = context.getOptions().getFirst().getOptions(); + if (!sub_options.isEmpty()) + return InteractionIdentifier.createHierarchy( + InteractionType.COMMAND, + commandName, + options.getFirst().getName(), + sub_options.getFirst().getName()); + else + return InteractionIdentifier.createHierarchy( + InteractionType.COMMAND, + commandName, + options.getFirst().getName()); + }else + return InteractionIdentifier.createHierarchy( + InteractionType.COMMAND, + commandName); + + } + +} diff --git a/wrapper/javacord/src/test/java/net/tomatentum/marinara/test/javacord/TestChoiceEnum.java b/wrapper/javacord/src/test/java/net/tomatentum/marinara/test/javacord/TestChoiceEnum.java index 1423ef9..955bab9 100644 --- a/wrapper/javacord/src/test/java/net/tomatentum/marinara/test/javacord/TestChoiceEnum.java +++ b/wrapper/javacord/src/test/java/net/tomatentum/marinara/test/javacord/TestChoiceEnum.java @@ -1,6 +1,6 @@ package net.tomatentum.marinara.test.javacord; -import net.tomatentum.marinara.interaction.commands.ChoiceValueProvider; +import net.tomatentum.marinara.interaction.commands.choice.ChoiceValueProvider; public enum TestChoiceEnum implements ChoiceValueProvider { TestValue("testValue"),