Compare commits
	
		
			65 Commits
		
	
	
		
			f81602f5db
			...
			feat/messa
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 272225ac07 | |||
| 42a675dc96 | |||
| eea1597b15 | |||
| 83b446e6fb | |||
| ef9384336a | |||
| ebf5600e29 | |||
| 070319853a | |||
| 60ead419e2 | |||
| 0b7b607a23 | |||
| 991d1c047b | |||
| ec17952375 | |||
| 0114cffcbd | |||
| 450f1fdaa1 | |||
| 92540576df | |||
| 8a3cde52fd | |||
| 8495659364 | |||
| 0973016a74 | |||
| 0590789359 | |||
| 2647a1f0b4 | |||
| a3c5eb62ac | |||
| 996f854ff7 | |||
| d2eec8b07c | |||
| caa2ee7089 | |||
| 2e5979e6e4 | |||
| ab1eb74e85 | |||
| a5737b9eaa | |||
| faca21724c | |||
| 4c5e28b679 | |||
| 33f355e6ea | |||
| d32ac62b4a | |||
| e7c35d9308 | |||
| d4a91f3251 | |||
| bce4ce7812 | |||
| bae077654e | |||
| 203498de68 | |||
| 24df1731da | |||
| e3fc10a1ce | |||
| 78cacb7eb6 | |||
| 7287d44645 | |||
| 630c8ddee5 | |||
| 4e27e6ce56 | |||
| 432cf78a2e | |||
| f940f48566 | |||
| f4ee258eb1 | |||
| 56b668851b | |||
| 823402e0cd | |||
| 91b1df8d5b | |||
| 76d7e9ef4f | |||
| 236c584da8 | |||
| f6db113deb | |||
| 9058629af5 | |||
| 8f14b0feb9 | |||
| 3e02e7b85b | |||
| ba560cb909 | |||
| 6cdca411c6 | |||
| 3a39b268c1 | |||
| ca0ab13316 | |||
| ded81983e3 | |||
| 92704ca230 | |||
| 5d8f737481 | |||
| 8943d6d4a4 | |||
| 842fcfe5ac | |||
| 858fab5e32 | |||
| 20471fefea | |||
| 8d27ec28db | 
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -6,3 +6,4 @@ build | ||||
| .vscode | ||||
| lib/bin | ||||
| wrapper/javacord/bin | ||||
| wrapper/discord4j/bin | ||||
|   | ||||
| @@ -8,7 +8,19 @@ allprojects { | ||||
|     group = "net.tomatentum.Marinara" | ||||
|     version = "1.0.0-RC1" + (if (!project.hasProperty("release")) ("-" + getGitHash()) else "") | ||||
|     description = "A simple but powerful, library-agnostic Discord Interaction Wrapper." | ||||
|     plugins.withType<JavaPlugin> { | ||||
|         tasks.withType<Jar>().configureEach { | ||||
|             archiveBaseName.set("marinara-" + archiveBaseName.get()) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     repositories { | ||||
|         // Use Maven Central for resolving dependencies. | ||||
|         mavenCentral() | ||||
|         maven { | ||||
|             url = uri("https://git.tomatentum.net/api/packages/tueem/maven") | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| subprojects { | ||||
|   | ||||
| @@ -3,14 +3,19 @@ | ||||
|  | ||||
| [versions] | ||||
| junit-jupiter = "5.10.2" | ||||
| log4j = "2.24.1" | ||||
| slf4j = "2.0.17" | ||||
| javacord = "3.8.0" | ||||
| discord4j = "3.2.7" | ||||
| geantyref = "2.0.0" | ||||
| mockito = "5.15.2" | ||||
| cutin = "0.2.0" | ||||
|  | ||||
| [libraries] | ||||
| junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit-jupiter" } | ||||
| log4j = { module = "org.apache.logging.log4j:log4j-api", version.ref = "log4j"} | ||||
| slf4j = { module = "org.slf4j:slf4j-api", version.ref = "slf4j"} | ||||
| slf4j-simple = { module = "org.slf4j:slf4j-simple", version.ref = "slf4j"} | ||||
| javacord = { module = "org.javacord:javacord", version.ref = "javacord"} | ||||
| discord4j = { module = "com.discord4j:discord4j-core", version.ref = "discord4j"} | ||||
| geantyref = { module = "io.leangen.geantyref:geantyref", version.ref = "geantyref"} | ||||
| mockito = {module = "org.mockito:mockito-core", version.ref = "mockito"} | ||||
| cutin = {module = "net.tomatentum.cutin:lib", version.ref = "cutin"} | ||||
|   | ||||
| @@ -10,18 +10,14 @@ plugins { | ||||
|     `java-library` | ||||
| } | ||||
|  | ||||
| repositories { | ||||
|     // Use Maven Central for resolving dependencies. | ||||
|     mavenCentral() | ||||
| } | ||||
|  | ||||
| dependencies { | ||||
|     // Use JUnit Jupiter for testing. | ||||
|     testImplementation(libs.junit.jupiter) | ||||
|  | ||||
|     testRuntimeOnly("org.junit.platform:junit-platform-launcher") | ||||
|     implementation(libs.log4j) | ||||
|     implementation(libs.slf4j) | ||||
|     implementation(libs.geantyref) | ||||
|     api(libs.cutin) | ||||
| } | ||||
|  | ||||
| // Apply a specific Java toolchain to ease working on different environments. | ||||
|   | ||||
| @@ -1,40 +1,85 @@ | ||||
| package net.tomatentum.marinara; | ||||
|  | ||||
| import org.apache.logging.log4j.Logger; | ||||
| import java.util.Arrays; | ||||
| import java.util.List; | ||||
|  | ||||
| import net.tomatentum.marinara.registry.InteractionCheckRegistry; | ||||
| import net.tomatentum.marinara.registry.InteractionRegistry; | ||||
| import net.tomatentum.marinara.util.LoggerUtil; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
|  | ||||
| import net.tomatentum.cutin.MethodExecutor; | ||||
| import net.tomatentum.cutin.ProcessorMethodExecutor; | ||||
| import net.tomatentum.cutin.container.MethodContainer; | ||||
| import net.tomatentum.marinara.checks.CheckExecutionContext; | ||||
| import net.tomatentum.marinara.checks.CheckMethodIdentifier; | ||||
| import net.tomatentum.marinara.container.InteractionCheckContainer; | ||||
| import net.tomatentum.marinara.container.InteractionMethodContainer; | ||||
| import net.tomatentum.marinara.interaction.InteractionType; | ||||
| import net.tomatentum.marinara.interaction.commands.SlashCommandDefinition; | ||||
| import net.tomatentum.marinara.interaction.ident.InteractionIdentifier; | ||||
| import net.tomatentum.marinara.interaction.ident.RootCommandIdentifier; | ||||
| import net.tomatentum.marinara.interaction.processor.AutocompleteInteractionProcessor; | ||||
| import net.tomatentum.marinara.interaction.processor.DirectInteractionProcessor; | ||||
| import net.tomatentum.marinara.util.ObjectAggregator; | ||||
| import net.tomatentum.marinara.wrapper.IdentifierProvider; | ||||
| import net.tomatentum.marinara.wrapper.LibraryWrapper; | ||||
|  | ||||
| public class Marinara { | ||||
|  | ||||
|     private Logger logger = LoggerUtil.getLogger(getClass()); | ||||
|     private Logger logger = LoggerFactory.getLogger(getClass()); | ||||
|      | ||||
|     public static <T extends LibraryWrapper> Marinara load(LibraryWrapper wrapper) { | ||||
|     public static Marinara load(LibraryWrapper wrapper) { | ||||
|         return new Marinara(wrapper); | ||||
|     } | ||||
|  | ||||
|     private InteractionRegistry registry; | ||||
|     private InteractionCheckRegistry checkRegistry; | ||||
|     private LibraryWrapper wrapper; | ||||
|     private MethodContainer<CheckMethodIdentifier, CheckExecutionContext> checkContainer; | ||||
|     private MethodContainer<InteractionIdentifier, Object> interactionContainer; | ||||
|     private MethodExecutor<Object> interactionExecutor; | ||||
|  | ||||
|     private Marinara(LibraryWrapper wrapper) { | ||||
|         this.wrapper = wrapper; | ||||
|         this.registry = new InteractionRegistry(this); | ||||
|         this.checkRegistry = new InteractionCheckRegistry(); | ||||
|         this.checkContainer = new InteractionCheckContainer(); | ||||
|         this.interactionContainer = new InteractionMethodContainer(getCheckContainer(), getWrapper().getContextObjectProvider()); | ||||
|         IdentifierProvider provider = wrapper.createIdentifierProvider(); | ||||
|         ProcessorMethodExecutor<InteractionIdentifier, Object> exec = new ProcessorMethodExecutor<>(getInteractionContainer()); | ||||
|         exec | ||||
|             .addProcessor(new DirectInteractionProcessor(provider, InteractionType.COMMAND, InteractionType.BUTTON)) | ||||
|             .addProcessor(new AutocompleteInteractionProcessor(getWrapper(), provider)); | ||||
|         this.interactionExecutor = exec; | ||||
|         wrapper.subscribeInteractions(this.interactionExecutor::handle); | ||||
|         logger.info("Marinara loaded successfully!"); | ||||
|     } | ||||
|  | ||||
|     public InteractionRegistry getRegistry() { | ||||
|         return registry; | ||||
|     } | ||||
|     //TODO move to future interactionstructure module | ||||
|     public void registerCommands() { | ||||
|         List<InteractionIdentifier> slashIdentifiers = getInteractionContainer().identifiers().stream() | ||||
|             .filter(i -> i.type().equals(InteractionType.COMMAND)) | ||||
|             .toList(); | ||||
|  | ||||
|     public InteractionCheckRegistry getCheckRegistry() { | ||||
|         return checkRegistry; | ||||
|         SlashCommandDefinition[] defs = new ObjectAggregator<InteractionIdentifier, RootCommandIdentifier, SlashCommandDefinition>( | ||||
|             i -> Arrays.asList((RootCommandIdentifier)i.rootNode()), | ||||
|             SlashCommandDefinition::addIdentifier, | ||||
|             SlashCommandDefinition::new) | ||||
|             .aggregate(slashIdentifiers) | ||||
|             .toArray(SlashCommandDefinition[]::new); | ||||
|  | ||||
|         wrapper.getRegisterer().register(defs); | ||||
|     } | ||||
|  | ||||
|     public LibraryWrapper getWrapper() { | ||||
|         return wrapper; | ||||
|         return this.wrapper; | ||||
|     } | ||||
|  | ||||
|     public MethodContainer<InteractionIdentifier, Object> getInteractionContainer() { | ||||
|         return this.interactionContainer; | ||||
|     } | ||||
|  | ||||
|     public MethodContainer<CheckMethodIdentifier, CheckExecutionContext> getCheckContainer() { | ||||
|         return this.checkContainer; | ||||
|     } | ||||
|  | ||||
|     public MethodExecutor<Object> getInteractionExecutor() { | ||||
|         return interactionExecutor; | ||||
|     } | ||||
|      | ||||
| } | ||||
|   | ||||
| @@ -1,50 +1,28 @@ | ||||
| package net.tomatentum.marinara.checks; | ||||
|  | ||||
| import java.lang.annotation.Annotation; | ||||
| import java.lang.reflect.InvocationTargetException; | ||||
| import java.lang.reflect.Method; | ||||
| import java.util.Arrays; | ||||
|  | ||||
| import org.apache.logging.log4j.Logger; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
|  | ||||
| import net.tomatentum.marinara.util.LoggerUtil; | ||||
| import net.tomatentum.marinara.util.ReflectionUtil; | ||||
| import net.tomatentum.cutin.method.ReflectedMethod; | ||||
|  | ||||
| public record AppliedCheck(InteractionCheck<?> check, Annotation annotation) { | ||||
| public record AppliedCheck( | ||||
|         Annotation annotation,  | ||||
|         ReflectedMethod<CheckMethodIdentifier, CheckExecutionContext> preExec, | ||||
|         ReflectedMethod<CheckMethodIdentifier, CheckExecutionContext> postExec | ||||
|     ) { | ||||
|  | ||||
|     private static Logger logger = LoggerUtil.getLogger(AppliedCheck.class); | ||||
|     private static Logger logger = LoggerFactory.getLogger(AppliedCheck.class);  | ||||
|  | ||||
|     public boolean pre(Object context) { | ||||
|         Method[] methods = Arrays.stream(check.getClass().getMethods()) | ||||
|             .filter(x -> x.getName().equals("preExec")) | ||||
|             .filter(x -> !x.isBridge()) | ||||
|             .toArray(s -> new Method[s]); | ||||
|         Method method = ReflectionUtil.getMostSpecificMethod(methods, context.getClass(), annotation.annotationType()); | ||||
|         method.setAccessible(true); | ||||
|         try { | ||||
|             logger.debug("Executing pre check {} with context {}", check.getClass().getName(), context.toString()); | ||||
|             boolean result = (boolean) method.invoke(check, context, annotation); | ||||
|             logger.debug("Pre Check {} {} with context {}", check.getClass().getName(), result ? "succeeded" : "failed", context.toString()); | ||||
|             return result; | ||||
|         } catch (IllegalAccessException | InvocationTargetException | SecurityException e) { | ||||
|             logger.fatal(e); | ||||
|             return false; | ||||
|         } | ||||
|         logger.debug("Running InteractionCheck preExec {} with annotation {}", preExec(), annotation()); | ||||
|         return (boolean) preExec().run(new CheckExecutionContext(annotation, context)); | ||||
|     } | ||||
|  | ||||
|     public void post(Object context) { | ||||
|         Method[] methods = Arrays.stream(check.getClass().getMethods()) | ||||
|             .filter(x -> x.getName().equals("postExec")) | ||||
|             .filter(x -> !x.isBridge()) | ||||
|             .toArray(s -> new Method[s]); | ||||
|         Method method = ReflectionUtil.getMostSpecificMethod(methods, context.getClass(), annotation.annotationType()); | ||||
|         method.setAccessible(true); | ||||
|         try { | ||||
|             logger.debug("Executing post check {} with context {}", check.getClass().getName(), context.toString()); | ||||
|             method.invoke(check, context, annotation); | ||||
|         } catch (IllegalAccessException | InvocationTargetException | SecurityException e) { | ||||
|             logger.fatal(e); | ||||
|         } | ||||
|         logger.debug("Running InteractionCheck postExec {} with annotation {}", postExec(), annotation()); | ||||
|         postExec().run(new CheckExecutionContext(annotation, context)); | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -0,0 +1,7 @@ | ||||
| package net.tomatentum.marinara.checks; | ||||
|  | ||||
| import java.lang.annotation.Annotation; | ||||
|  | ||||
| public record CheckExecutionContext(Annotation annotation, Object originalContext) { | ||||
|      | ||||
| } | ||||
| @@ -0,0 +1,27 @@ | ||||
| package net.tomatentum.marinara.checks; | ||||
|  | ||||
| import java.lang.reflect.Type; | ||||
|  | ||||
| public record CheckMethodIdentifier(Type annotationType, CheckMethodType type) { | ||||
|  | ||||
|     public enum CheckMethodType { | ||||
|         PRE("preExec"), | ||||
|         POST("postExec"); | ||||
|  | ||||
|         private String methodName; | ||||
|  | ||||
|         private CheckMethodType(String methodName) { | ||||
|             this.methodName = methodName; | ||||
|         } | ||||
|  | ||||
|         public String methodName() { | ||||
|             return this.methodName; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public final String toString() { | ||||
|         return "InteractionCheck(%s, %s)".formatted(annotationType, type); | ||||
|     } | ||||
|      | ||||
| } | ||||
| @@ -0,0 +1,74 @@ | ||||
| package net.tomatentum.marinara.checks; | ||||
|  | ||||
| import java.util.Optional; | ||||
| import java.util.Set; | ||||
|  | ||||
| import net.tomatentum.cutin.MethodParser; | ||||
| import net.tomatentum.cutin.ReflectedMethodFactory.ParserResults; | ||||
| import net.tomatentum.cutin.container.MethodContainer; | ||||
| import net.tomatentum.cutin.method.BestCandidateMethod; | ||||
| import net.tomatentum.marinara.checks.CheckMethodIdentifier.CheckMethodType; | ||||
| import net.tomatentum.marinara.parser.InteractionCheckClassParser; | ||||
|  | ||||
| public class InteractionCheckMethod extends BestCandidateMethod<CheckMethodIdentifier, CheckExecutionContext> { | ||||
|  | ||||
|     private CheckMethodIdentifier identifier; | ||||
|  | ||||
|     public InteractionCheckMethod( | ||||
|             String methodName,  | ||||
|             Object containingObject, | ||||
|             CheckMethodIdentifier identifier | ||||
|         ) { | ||||
|         super(methodName, containingObject); | ||||
|         this.identifier = identifier; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Object getParameter(CheckExecutionContext context, int index) { | ||||
|         switch (index) { | ||||
|             case 0: | ||||
|                 return context.originalContext(); | ||||
|             case 1: | ||||
|                 return context.annotation(); | ||||
|             default: | ||||
|                 return null; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public CheckMethodIdentifier identifier() { | ||||
|         return this.identifier; | ||||
|     } | ||||
|  | ||||
|     public static class InteractionCheckMethodFactory extends BestCandidateMethod.Factory<CheckMethodIdentifier, CheckExecutionContext> { | ||||
|  | ||||
|         private CheckMethodType type; | ||||
|  | ||||
|         public InteractionCheckMethodFactory(MethodContainer<CheckMethodIdentifier, CheckExecutionContext> methodContainer, CheckMethodType type) { | ||||
|             super(methodContainer, type.methodName()); | ||||
|             this.type = type; | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public void addParser(Set<MethodParser> parsers) { | ||||
|             parsers.add( | ||||
|                 new InteractionCheckClassParser() | ||||
|             ); | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         protected Optional<BestCandidateMethod<CheckMethodIdentifier, CheckExecutionContext>> bcProduce( | ||||
|                     String methodName, | ||||
|                     Object containingObject, | ||||
|                     ParserResults parserResults | ||||
|                 ) { | ||||
|  | ||||
|             CheckMethodIdentifier identifier = new CheckMethodIdentifier(parserResults.get(InteractionCheckClassParser.class), type); | ||||
|             if (identifier.annotationType() == null) | ||||
|                 return null; | ||||
|             return Optional.of(new InteractionCheckMethod(methodName, containingObject, identifier)); | ||||
|         } | ||||
|  | ||||
|     } | ||||
|      | ||||
| } | ||||
| @@ -0,0 +1,19 @@ | ||||
| package net.tomatentum.marinara.container; | ||||
|  | ||||
| import net.tomatentum.cutin.ReflectedMethodFactoryImpl; | ||||
| import net.tomatentum.cutin.container.LoneMethodContainer; | ||||
| import net.tomatentum.marinara.checks.CheckExecutionContext; | ||||
| import net.tomatentum.marinara.checks.CheckMethodIdentifier; | ||||
| import net.tomatentum.marinara.checks.CheckMethodIdentifier.CheckMethodType; | ||||
| import net.tomatentum.marinara.checks.InteractionCheckMethod.InteractionCheckMethodFactory; | ||||
|  | ||||
| public class InteractionCheckContainer extends LoneMethodContainer<CheckMethodIdentifier, CheckExecutionContext> { | ||||
|      | ||||
|     public InteractionCheckContainer() { | ||||
|         super(new ReflectedMethodFactoryImpl<>()); | ||||
|         super.factory() | ||||
|             .addFactory(new InteractionCheckMethodFactory(this, CheckMethodType.PRE)) | ||||
|             .addFactory(new InteractionCheckMethodFactory(this, CheckMethodType.POST)); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,38 @@ | ||||
| package net.tomatentum.marinara.container; | ||||
|  | ||||
| import net.tomatentum.cutin.ReflectedMethodFactory; | ||||
| import net.tomatentum.cutin.ReflectedMethodFactoryImpl; | ||||
| import net.tomatentum.cutin.container.MethodContainer; | ||||
| import net.tomatentum.cutin.container.MultiMethodContainer; | ||||
| import net.tomatentum.cutin.method.ReflectedMethod; | ||||
| import net.tomatentum.marinara.checks.CheckExecutionContext; | ||||
| import net.tomatentum.marinara.checks.CheckMethodIdentifier; | ||||
| import net.tomatentum.marinara.interaction.components.methods.ButtonInteractionMethod; | ||||
| import net.tomatentum.marinara.interaction.ident.InteractionIdentifier; | ||||
| import net.tomatentum.marinara.wrapper.ContextObjectProvider; | ||||
| import net.tomatentum.marinara.interaction.methods.AutoCompleteInteractionMethod; | ||||
| import net.tomatentum.marinara.interaction.methods.SlashCommandInteractionMethod; | ||||
|  | ||||
| public class InteractionMethodContainer extends MultiMethodContainer<InteractionIdentifier, Object> { | ||||
|  | ||||
|  | ||||
|     private static ReflectedMethodFactory<InteractionIdentifier, Object> createFactory(MethodContainer<CheckMethodIdentifier, CheckExecutionContext> checkRegistry, ContextObjectProvider cop) { | ||||
|         return new ReflectedMethodFactoryImpl<InteractionIdentifier, Object>() | ||||
|             .addFactory(new AutoCompleteInteractionMethod.Factory(checkRegistry, cop)) | ||||
|             .addFactory(new SlashCommandInteractionMethod.Factory(checkRegistry, cop)) | ||||
|             .addFactory(new ButtonInteractionMethod.Factory(checkRegistry, cop)); | ||||
|     } | ||||
|  | ||||
|     public InteractionMethodContainer(MethodContainer<CheckMethodIdentifier, CheckExecutionContext> checkRegistry, ContextObjectProvider cop) { | ||||
|         super(createFactory(checkRegistry, cop)); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public MethodContainer<InteractionIdentifier, Object> addMethod(ReflectedMethod<InteractionIdentifier, Object> method) { | ||||
|         super.identifiers().stream() | ||||
|             .filter(method.identifier()::equals) | ||||
|             .forEach(i -> InteractionIdentifier.tryAddDescriptions(i, method.identifier())); | ||||
|         return super.addMethod(method); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -8,5 +8,5 @@ import java.lang.annotation.Target; | ||||
| @Target({ElementType.METHOD}) | ||||
| @Retention(RetentionPolicy.RUNTIME) | ||||
| public @interface AutoComplete { | ||||
|      | ||||
|     public String value(); | ||||
| } | ||||
|   | ||||
| @@ -5,8 +5,15 @@ import java.lang.annotation.Retention; | ||||
| import java.lang.annotation.RetentionPolicy; | ||||
| import java.lang.annotation.Target; | ||||
|  | ||||
| import net.tomatentum.marinara.structure.data.ButtonStructureData.ButtonStyle; | ||||
|  | ||||
| @Target({ElementType.METHOD}) | ||||
| @Retention(RetentionPolicy.RUNTIME) | ||||
| public @interface Button { | ||||
|     public String value(); //aka customId | ||||
|     public String label() default "default_button"; | ||||
|     public ButtonStyle style() default ButtonStyle.PRIMARY; | ||||
|     public String url() default ""; | ||||
|     public boolean disabled() default false; | ||||
|     public String emoji() default ""; | ||||
| } | ||||
|   | ||||
| @@ -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; | ||||
|         } | ||||
|  | ||||
|     } | ||||
| } | ||||
| @@ -1,99 +1,112 @@ | ||||
| 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.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
|  | ||||
| import net.tomatentum.marinara.interaction.commands.annotation.CommandChoices; | ||||
| 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; | ||||
|  | ||||
| public class SlashCommandDefinition { | ||||
|     private List<ExecutableSlashCommandDefinition> executableDefinitons; | ||||
|     private SlashCommand slashCommand; | ||||
|  | ||||
|     public static SlashCommandOptionChoice[] getActualChoices(SlashCommandOption option) { | ||||
|         CommandChoices choices = option.choices(); | ||||
|         SlashCommandOptionChoice[] actualChoices = choices.value(); | ||||
|         if (choices.value().length <= 0 && !choices.cenum().equals(PlaceHolderEnum.class)) | ||||
|             actualChoices = EnumChoices.of(choices.cenum()).choices(); | ||||
|         return actualChoices; | ||||
|     } | ||||
|  | ||||
|     private Set<InteractionIdentifier> entries; | ||||
|     private RootCommandIdentifier rootIdentifier; | ||||
|     private boolean isRootCommand; | ||||
|  | ||||
|     public SlashCommandDefinition(SlashCommand applicationCommand) { | ||||
|         this.executableDefinitons = new ArrayList<>(); | ||||
|         this.slashCommand = applicationCommand; | ||||
|     private Logger logger = LoggerFactory.getLogger(getClass()); | ||||
|  | ||||
|     public SlashCommandDefinition(RootCommandIdentifier rootIdentifier) { | ||||
|         this.entries = new HashSet<>(); | ||||
|         this.rootIdentifier = rootIdentifier; | ||||
|         this.isRootCommand = false; | ||||
|     } | ||||
|  | ||||
|     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"); | ||||
|     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"); | ||||
|         } | ||||
|          | ||||
|         if (executableDefinitons.isEmpty()) | ||||
|             this.isRootCommand = def.isRootCommand(); | ||||
|  | ||||
|         if ((isRootCommand && !def.isRootCommand()) || (!isRootCommand && def.isRootCommand())) { | ||||
|             throw new IllegalArgumentException(def + ": 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<SubCommandGroup> subCommandGroups = Arrays.stream(getExecutableDefinitons()) | ||||
|             .filter((x) -> x.subCommandGroup() != null) | ||||
|             .map((x) -> x.subCommandGroup()) | ||||
|             .toList(); | ||||
|  | ||||
|         HashMap<String, SubCommandGroup> 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<SubCommand> 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(); | ||||
|          | ||||
|         HashMap<String, SubCommand> 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); | ||||
|         }); | ||||
|  | ||||
|         return subCommandMap.values().toArray(new SubCommand[0]); | ||||
|     } | ||||
|  | ||||
|     public SlashCommand getFullSlashCommand() { | ||||
|         if (isRootCommand()) | ||||
|             return getSlashCommand(); | ||||
|         for (ExecutableSlashCommandDefinition executableSlashCommandDefinition : executableDefinitons) { | ||||
|             if (executableSlashCommandDefinition.options().length > 0) | ||||
|                 return executableSlashCommandDefinition.applicationCommand(); | ||||
|         } | ||||
|  | ||||
|     public SlashCommandIdentifier[] getSubCommandGroups() { | ||||
|         if (isRootCommand) | ||||
|             return null; | ||||
|          | ||||
|         List<InteractionIdentifier> subCommandGroups = entries().stream() | ||||
|             .filter(x -> x.parent().parent() != null) | ||||
|             .map(x -> x.parent()) | ||||
|             .toList(); | ||||
|  | ||||
|         return subCommandGroups.toArray(SlashCommandIdentifier[]::new); | ||||
|     } | ||||
|  | ||||
|     public SlashCommand getSlashCommand() { | ||||
|         return slashCommand; | ||||
|     public SlashCommandIdentifier[] getSubCommands() { | ||||
|         if (isRootCommand) | ||||
|             return null; | ||||
|         return entries.stream().filter(x -> x.parent() instanceof RootCommandIdentifier).toArray(SlashCommandIdentifier[]::new); | ||||
|     } | ||||
|  | ||||
|     public ExecutableSlashCommandDefinition[] getExecutableDefinitons() { | ||||
|         return executableDefinitons.toArray(new ExecutableSlashCommandDefinition[0]); | ||||
|     public SlashCommandIdentifier[] getSubCommands(String groupName) { | ||||
|         if (isRootCommand) | ||||
|             return null; | ||||
|  | ||||
|         List<InteractionIdentifier> 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); | ||||
|     } | ||||
|  | ||||
|     @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<InteractionIdentifier> entries() { | ||||
|         return this.entries; | ||||
|     } | ||||
|  | ||||
|     public RootCommandIdentifier rootIdentifier() { | ||||
|         return rootIdentifier; | ||||
|     } | ||||
|  | ||||
|     public boolean isRootCommand() { | ||||
|   | ||||
| @@ -0,0 +1,8 @@ | ||||
| package net.tomatentum.marinara.interaction.commands.annotation; | ||||
|  | ||||
| import net.tomatentum.marinara.interaction.commands.annotation.SlashCommandOption.PlaceHolderEnum; | ||||
|  | ||||
| public @interface CommandChoices { | ||||
|     public SlashCommandOptionChoice[] value() default {}; | ||||
|     public Class<? extends Enum<?>> cenum() default PlaceHolderEnum.class; | ||||
| } | ||||
| @@ -0,0 +1,6 @@ | ||||
| package net.tomatentum.marinara.interaction.commands.annotation; | ||||
|  | ||||
| public @interface Range { | ||||
|     public double min() default Double.MIN_VALUE; | ||||
|     public double max() default Double.MAX_VALUE; | ||||
| } | ||||
| @@ -5,6 +5,7 @@ import java.lang.annotation.Retention; | ||||
| import java.lang.annotation.RetentionPolicy; | ||||
| import java.lang.annotation.Target; | ||||
|  | ||||
| import net.tomatentum.marinara.interaction.annotation.AutoComplete; | ||||
| import net.tomatentum.marinara.interaction.commands.option.SlashCommandOptionType; | ||||
|  | ||||
| @Target({ElementType.ANNOTATION_TYPE}) | ||||
| @@ -14,11 +15,11 @@ public @interface SlashCommandOption { | ||||
|     public String description() default ""; | ||||
|     public SlashCommandOptionType type() default SlashCommandOptionType.STRING; | ||||
|     public boolean required() default false; | ||||
|     public boolean autocomplete() default false; | ||||
|     public SlashCommandOptionChoice[] choices() default {}; | ||||
|     public Class<? extends Enum<?>> choiceEnum() default PlaceHolderEnum.class; | ||||
|     public AutoComplete[] autocompletes() default {}; | ||||
|     public Range range() default @Range; | ||||
|     public CommandChoices choices() default @CommandChoices; | ||||
|  | ||||
|     public static enum PlaceHolderEnum { | ||||
|     public enum PlaceHolderEnum { | ||||
|  | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| package net.tomatentum.marinara.interaction.commands; | ||||
| package net.tomatentum.marinara.interaction.commands.choice; | ||||
| 
 | ||||
| public interface ChoiceValueProvider<T> { | ||||
|     T getChoiceValue(); | ||||
| @@ -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; | ||||
| @@ -0,0 +1,16 @@ | ||||
| package net.tomatentum.marinara.interaction.commands.option; | ||||
|  | ||||
| import java.util.Arrays; | ||||
|  | ||||
| import net.tomatentum.marinara.interaction.commands.annotation.SlashCommandOption; | ||||
|  | ||||
| public record AutocompleteOptionData(String name, Object input) { | ||||
|      | ||||
|     public String[] getAutocompleteRefs(SlashCommandOption[] options) { | ||||
|         return Arrays.stream(options) | ||||
|             .filter(o -> o.name().equals(this.name())) | ||||
|             .flatMap(o -> Arrays.stream(o.autocompletes())) | ||||
|             .map(a -> a.value()) | ||||
|             .toArray(String[]::new); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,93 @@ | ||||
| package net.tomatentum.marinara.interaction.components.methods; | ||||
|  | ||||
| import java.lang.reflect.Method; | ||||
| import java.util.List; | ||||
| import java.util.Optional; | ||||
| import java.util.Set; | ||||
|  | ||||
| import net.tomatentum.cutin.MethodParser; | ||||
| import net.tomatentum.cutin.ReflectedMethodFactory.ParserResults; | ||||
| import net.tomatentum.cutin.container.MethodContainer; | ||||
| import net.tomatentum.cutin.method.ReflectedMethod; | ||||
| import net.tomatentum.marinara.checks.AppliedCheck; | ||||
| import net.tomatentum.marinara.checks.CheckExecutionContext; | ||||
| import net.tomatentum.marinara.checks.CheckMethodIdentifier; | ||||
| import net.tomatentum.marinara.interaction.InteractionHandler; | ||||
| import net.tomatentum.marinara.interaction.InteractionType; | ||||
| import net.tomatentum.marinara.interaction.ident.InteractionIdentifier; | ||||
| import net.tomatentum.marinara.interaction.methods.InteractionMethod; | ||||
| import net.tomatentum.marinara.parser.ButtonParser; | ||||
| import net.tomatentum.marinara.parser.InteractionCheckParser; | ||||
| import net.tomatentum.marinara.wrapper.ContextObjectProvider; | ||||
|  | ||||
| public class ButtonInteractionMethod extends InteractionMethod { | ||||
|  | ||||
|     private String customId; | ||||
|     private ContextObjectProvider cop; | ||||
|  | ||||
|     private ButtonInteractionMethod( | ||||
|             Method method,  | ||||
|             InteractionHandler handler, | ||||
|             List<AppliedCheck> appliedChecks, | ||||
|             String customId,  | ||||
|             ContextObjectProvider cop | ||||
|         ) { | ||||
|         super(method, handler, appliedChecks); | ||||
|         this.customId = customId; | ||||
|         this.cop = cop; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Object getParameter(Object context, int index) { | ||||
|         Class<?> type = method().getParameterTypes()[index]; | ||||
|         Object superResult = super.getParameter(context, index); | ||||
|         if (superResult == null) | ||||
|             return this.cop.getComponentContextObject(context, type); | ||||
|         else | ||||
|             return superResult; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public InteractionIdentifier identifier() { | ||||
|         return InteractionIdentifier.builder() | ||||
|             .name(customId) | ||||
|             .description("Button") | ||||
|             .type(InteractionType.BUTTON) | ||||
|             .build(); | ||||
|     } | ||||
|  | ||||
|     public static class Factory extends InteractionMethod.Factory { | ||||
|  | ||||
|         private ContextObjectProvider cop; | ||||
|  | ||||
|         public Factory(MethodContainer<CheckMethodIdentifier, CheckExecutionContext> checkContainer, ContextObjectProvider cop) { | ||||
|             super(checkContainer); | ||||
|             this.cop = cop; | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public Optional<ReflectedMethod<InteractionIdentifier, Object>> produce(Method method, Object containingObject, ParserResults parserResults) { | ||||
|             if (!(containingObject instanceof InteractionHandler)) return Optional.empty(); | ||||
|             String customId = parserResults.get(ButtonParser.class); | ||||
|             if (customId == null) return Optional.empty(); | ||||
|             return Optional.of(new ButtonInteractionMethod( | ||||
|                 method,  | ||||
|                 (InteractionHandler) containingObject, | ||||
|                 parserResults.get(InteractionCheckParser.class), | ||||
|                 customId, | ||||
|                 this.cop | ||||
|             )); | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public void addParser(Set<MethodParser> parser) { | ||||
|             super.addParser(parser); | ||||
|  | ||||
|             parser.add( | ||||
|                 new ButtonParser() | ||||
|             ); | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,146 @@ | ||||
| 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(parent(), ident.parent()); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public int hashCode() { | ||||
|         return Objects.hash(type(), name(), parent()); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public String toString() { | ||||
|         if (parent() == null) | ||||
|             return name() + " - " + type(); | ||||
|         return "%s:%s".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); | ||||
|         } | ||||
|  | ||||
|     } | ||||
|      | ||||
| } | ||||
| @@ -0,0 +1,99 @@ | ||||
| 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; | ||||
|         private String[] autocompleteRef; | ||||
|  | ||||
|         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 String[] autocompleteRef() { | ||||
|             return this.autocompleteRef; | ||||
|         } | ||||
|  | ||||
|         public Builder autocompleteRef(String[] autocompleteRef) { | ||||
|             this.autocompleteRef = autocompleteRef; | ||||
|             return this; | ||||
|         } | ||||
|  | ||||
|         public SlashCommandIdentifier build() { | ||||
|             return new RootCommandIdentifier( | ||||
|                 parent,  | ||||
|                 name,  | ||||
|                 description,  | ||||
|                 InteractionType.COMMAND,  | ||||
|                 options,  | ||||
|                 serverIds); | ||||
|         } | ||||
|  | ||||
|     } | ||||
|      | ||||
| } | ||||
| @@ -0,0 +1,87 @@ | ||||
| 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; | ||||
|         private String[] autocompleteRef; | ||||
|  | ||||
|         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 String[] autocompleteRef() { | ||||
|             return this.autocompleteRef; | ||||
|         } | ||||
|  | ||||
|         public Builder autocompleteRef(String[] autocompleteRef) { | ||||
|             this.autocompleteRef = autocompleteRef; | ||||
|             return this; | ||||
|         } | ||||
|  | ||||
|         public SlashCommandIdentifier build() { | ||||
|             return new SlashCommandIdentifier( | ||||
|                 parent,  | ||||
|                 name,  | ||||
|                 description,  | ||||
|                 InteractionType.COMMAND,  | ||||
|                 options); | ||||
|         } | ||||
|  | ||||
|     } | ||||
|      | ||||
| } | ||||
| @@ -1,51 +1,98 @@ | ||||
| package net.tomatentum.marinara.interaction.methods; | ||||
|  | ||||
| import java.lang.reflect.Method; | ||||
| import java.util.List; | ||||
| import java.util.Optional; | ||||
| import java.util.Set; | ||||
|  | ||||
| import net.tomatentum.marinara.Marinara; | ||||
| import net.tomatentum.cutin.MethodParser; | ||||
| import net.tomatentum.cutin.ReflectedMethodFactory.ParserResults; | ||||
| import net.tomatentum.cutin.container.MethodContainer; | ||||
| import net.tomatentum.cutin.method.ReflectedMethod; | ||||
| import net.tomatentum.marinara.checks.AppliedCheck; | ||||
| import net.tomatentum.marinara.checks.CheckExecutionContext; | ||||
| import net.tomatentum.marinara.checks.CheckMethodIdentifier; | ||||
| import net.tomatentum.marinara.interaction.InteractionHandler; | ||||
| import net.tomatentum.marinara.interaction.InteractionType; | ||||
| import net.tomatentum.marinara.interaction.commands.ExecutableSlashCommandDefinition; | ||||
| import net.tomatentum.marinara.parser.AnnotationParser; | ||||
| import net.tomatentum.marinara.parser.SlashCommandParser; | ||||
| import net.tomatentum.marinara.interaction.ident.InteractionIdentifier; | ||||
| import net.tomatentum.marinara.parser.AutocompleteParser; | ||||
| import net.tomatentum.marinara.parser.InteractionCheckParser; | ||||
| import net.tomatentum.marinara.wrapper.ContextObjectProvider; | ||||
|  | ||||
| public class AutoCompleteInteractionMethod extends InteractionMethod { | ||||
|  | ||||
|     private ExecutableSlashCommandDefinition commandDefinition; | ||||
|     private String autocompleteRef; | ||||
|     private ContextObjectProvider cop; | ||||
|  | ||||
|     public AutoCompleteInteractionMethod(Method method,  | ||||
|     private AutoCompleteInteractionMethod( | ||||
|             Method method,  | ||||
|             InteractionHandler handler, | ||||
|         Marinara marinara | ||||
|             List<AppliedCheck> appliedChecks, | ||||
|             String autocompleteRef, | ||||
|             ContextObjectProvider cop | ||||
|         ) { | ||||
|         super(method, handler, marinara); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public AnnotationParser[] getParsers() { | ||||
|         return new AnnotationParser[] {  | ||||
|             new SlashCommandParser(method, (x) -> { this.commandDefinition = x; } )  | ||||
|         }; | ||||
|         super(method, handler, appliedChecks); | ||||
|         this.autocompleteRef = autocompleteRef; | ||||
|         this.cop = cop; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Object getParameter(Object context, int index) { | ||||
|         Class<?> type = getMethod().getParameterTypes()[index+1]; | ||||
|         Object autocompleteOptionValue = marinara.getWrapper().getContextObjectProvider().getAutocompleteFocusedOption(context); | ||||
|         if (autocompleteOptionValue != null) | ||||
|         Class<?> type = method().getParameterTypes()[index]; | ||||
|         Object contextObject = this.cop.getInteractionContextObject(context, type); | ||||
|         if (contextObject != null) | ||||
|             return contextObject; | ||||
|  | ||||
|         Object autocompleteOptionValue = this.cop.getAutocompleteFocusedOption(context).input(); | ||||
|         if (type.isInstance(autocompleteOptionValue)) | ||||
|             return autocompleteOptionValue; | ||||
|  | ||||
|         return marinara.getWrapper().getContextObjectProvider().getComponentContextObject(context, type); | ||||
|         return super.getParameter(context, index); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public boolean canRun(Object context) { | ||||
|         ExecutableSlashCommandDefinition other = marinara.getWrapper().getCommandDefinition(context); | ||||
|         return commandDefinition.equals(other); | ||||
|     public InteractionIdentifier identifier() { | ||||
|         return InteractionIdentifier.builder() | ||||
|             .type(InteractionType.AUTOCOMPLETE) | ||||
|             .name(autocompleteRef) | ||||
|             .description("AUTOCOMPLETE") | ||||
|             .build(); | ||||
|     } | ||||
|  | ||||
|     public static class Factory extends InteractionMethod.Factory { | ||||
|  | ||||
|         private ContextObjectProvider cop; | ||||
|  | ||||
|         public Factory(MethodContainer<CheckMethodIdentifier, CheckExecutionContext> checkContainer, ContextObjectProvider cop) { | ||||
|             super(checkContainer); | ||||
|             this.cop = cop; | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|     public InteractionType getType() { | ||||
|         return InteractionType.AUTOCOMPLETE; | ||||
|         public Optional<ReflectedMethod<InteractionIdentifier, Object>> produce(Method method, Object containingObject, ParserResults parserResults) { | ||||
|             if (!(containingObject instanceof InteractionHandler)) return Optional.empty(); | ||||
|             String[] autocompletes = parserResults.get(AutocompleteParser.class); | ||||
|             if (autocompletes.length <= 0) return Optional.empty(); | ||||
|              | ||||
|             return Optional.of(new AutoCompleteInteractionMethod( | ||||
|                 method,  | ||||
|                 (InteractionHandler) containingObject, | ||||
|                 parserResults.get(InteractionCheckParser.class), | ||||
|                 autocompletes[0], | ||||
|                 cop | ||||
|             )); | ||||
|  | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public void addParser(Set<MethodParser> parser) { | ||||
|             super.addParser(parser); | ||||
|  | ||||
|             parser.add( | ||||
|                 new AutocompleteParser() | ||||
|             ); | ||||
|         } | ||||
|  | ||||
|     } | ||||
|      | ||||
| } | ||||
|   | ||||
| @@ -1,42 +0,0 @@ | ||||
| package net.tomatentum.marinara.interaction.methods; | ||||
|  | ||||
| import java.lang.reflect.Method; | ||||
|  | ||||
| import net.tomatentum.marinara.Marinara; | ||||
| import net.tomatentum.marinara.interaction.InteractionHandler; | ||||
| import net.tomatentum.marinara.interaction.InteractionType; | ||||
| import net.tomatentum.marinara.parser.AnnotationParser; | ||||
| import net.tomatentum.marinara.parser.ButtonParser; | ||||
|  | ||||
| public class ButtonInteractionMethod extends InteractionMethod { | ||||
|  | ||||
|     private String customId; | ||||
|  | ||||
|     ButtonInteractionMethod(Method method, InteractionHandler handler, Marinara marinara) { | ||||
|         super(method, handler, marinara); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public AnnotationParser[] getParsers() { | ||||
|         return new AnnotationParser[] { | ||||
|             new ButtonParser(method, (x) -> { this.customId = x; } ) | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Object getParameter(Object context, int index) { | ||||
|         Class<?> type = getMethod().getParameterTypes()[index+1]; | ||||
|         return marinara.getWrapper().getContextObjectProvider().getComponentContextObject(context, type); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public boolean canRun(Object context) { | ||||
|         return marinara.getWrapper().getButtonId(context).equals(customId); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public InteractionType getType() { | ||||
|         return InteractionType.BUTTON; | ||||
|     } | ||||
|      | ||||
| } | ||||
| @@ -1,106 +1,72 @@ | ||||
| package net.tomatentum.marinara.interaction.methods; | ||||
|  | ||||
| import java.lang.reflect.InvocationTargetException; | ||||
| import java.lang.reflect.Method; | ||||
| import java.security.InvalidParameterException; | ||||
| import java.util.ArrayList; | ||||
| import java.util.Arrays; | ||||
| import java.util.List; | ||||
| import java.util.Set; | ||||
|  | ||||
| import org.apache.logging.log4j.Logger; | ||||
|  | ||||
| import net.tomatentum.marinara.Marinara; | ||||
| import net.tomatentum.cutin.MethodParser; | ||||
| import net.tomatentum.cutin.ReflectedMethodFactory; | ||||
| import net.tomatentum.cutin.container.MethodContainer; | ||||
| import net.tomatentum.cutin.method.ReflectedMethod; | ||||
| import net.tomatentum.marinara.checks.AppliedCheck; | ||||
| import net.tomatentum.marinara.checks.CheckExecutionContext; | ||||
| import net.tomatentum.marinara.checks.CheckMethodIdentifier; | ||||
| 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.parser.AnnotationParser; | ||||
| import net.tomatentum.marinara.interaction.ident.InteractionIdentifier; | ||||
| import net.tomatentum.marinara.parser.InteractionCheckParser; | ||||
| import net.tomatentum.marinara.util.LoggerUtil; | ||||
| import net.tomatentum.marinara.util.ReflectionUtil; | ||||
|  | ||||
| public abstract class InteractionMethod { | ||||
| public abstract class InteractionMethod extends ReflectedMethod<InteractionIdentifier, Object> { | ||||
|  | ||||
|     public static InteractionMethod create(Method method, InteractionHandler handler, Marinara marinara) { | ||||
|         if (method.isAnnotationPresent(AutoComplete.class)) | ||||
|             return new AutoCompleteInteractionMethod(method, handler, marinara); | ||||
|         if (method.isAnnotationPresent(SlashCommand.class) || method.isAnnotationPresent(SubCommand.class)) | ||||
|             return new SlashCommandInteractionMethod(method, handler, marinara); | ||||
|         if (method.isAnnotationPresent(Button.class)) | ||||
|             return new ButtonInteractionMethod(method, handler, marinara); | ||||
|     protected List<AppliedCheck> appliedChecks; | ||||
|  | ||||
|     protected InteractionMethod( | ||||
|         Method method,  | ||||
|         InteractionHandler handler, | ||||
|         List<AppliedCheck> appliedChecks | ||||
|         ) { | ||||
|         super(method, handler); | ||||
|         this.appliedChecks = appliedChecks; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Object getParameter(Object context, int index) { | ||||
|         if (index == 0) | ||||
|             return context; | ||||
|         return null; | ||||
|     } | ||||
|      | ||||
|     protected Method method; | ||||
|     protected InteractionHandler handler; | ||||
|     protected Marinara marinara; | ||||
|     protected List<AnnotationParser> parsers; | ||||
|     protected List<AppliedCheck> appliedChecks; | ||||
|  | ||||
|     private Logger logger = LoggerUtil.getLogger(getClass()); | ||||
|  | ||||
|     protected InteractionMethod(Method method,  | ||||
|         InteractionHandler handler,  | ||||
|         Marinara marinara | ||||
|         ) { | ||||
|         if (!Arrays.asList(handler.getClass().getMethods()).contains(method)) | ||||
|             throw new InvalidParameterException("Method does not apply to specified handler"); | ||||
|  | ||||
|         this.method = method; | ||||
|         this.handler = handler; | ||||
|         this.marinara = marinara; | ||||
|         this.parsers = new ArrayList<>(Arrays.asList(getParsers())); | ||||
|         this.appliedChecks = new ArrayList<>(); | ||||
|  | ||||
|         parsers.add(new InteractionCheckParser(method, appliedChecks::add, marinara.getCheckRegistry())); | ||||
|  | ||||
|         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) { | ||||
|     @Override | ||||
|     public Object run(Object context) { | ||||
|         Object result = null; | ||||
|         if (this.appliedChecks.stream().filter(x -> !x.pre(context)).count() > 0) | ||||
|             return; | ||||
|             return null; | ||||
|  | ||||
|         method.setAccessible(true); | ||||
|         try { | ||||
|             method.invoke(handler, getParameters(context)); | ||||
|         }catch (IllegalAccessException | InvocationTargetException ex) { | ||||
|             logger.fatal(ex); | ||||
|         } | ||||
|         result = super.run(context); | ||||
|  | ||||
|         this.appliedChecks.forEach(x -> x.post(context)); | ||||
|  | ||||
|         return result; | ||||
|     } | ||||
|  | ||||
|     public Method getMethod() { | ||||
|         return method; | ||||
|     public List<AppliedCheck> appliedChecks() { | ||||
|         return this.appliedChecks; | ||||
|     } | ||||
|  | ||||
|     private Object[] getParameters(Object context) { | ||||
|         int parameterCount = method.getParameterCount(); | ||||
|         List<Object> parameters = new ArrayList<>(); | ||||
|     public abstract static class Factory implements ReflectedMethodFactory.Factory<InteractionIdentifier, Object> { | ||||
|  | ||||
|         for (int i = 0; i < parameterCount; i++) { | ||||
|             Object parameter; | ||||
|             if (i == 0) { | ||||
|                 parameter = context; | ||||
|             }else | ||||
|                 parameter = getParameter(context, i-1); | ||||
|         private MethodContainer<CheckMethodIdentifier, CheckExecutionContext> checkContainer; | ||||
|  | ||||
|             logger.trace("Found parameter {}={} for method {}", parameter.getClass().toString(), parameter, ReflectionUtil.getFullMethodName(method)); | ||||
|             parameters.add(parameter);    | ||||
|         protected Factory(MethodContainer<CheckMethodIdentifier, CheckExecutionContext> checkContainer) { | ||||
|             this.checkContainer = checkContainer; | ||||
|         } | ||||
|         return parameters.toArray(); | ||||
|  | ||||
|         @Override | ||||
|         public void addParser(Set<MethodParser> parser) { | ||||
|             parser.add( | ||||
|                 new InteractionCheckParser(this.checkContainer) | ||||
|             ); | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -1,51 +1,87 @@ | ||||
| package net.tomatentum.marinara.interaction.methods; | ||||
|  | ||||
| import java.lang.reflect.Method; | ||||
| import java.util.List; | ||||
| import java.util.Optional; | ||||
| import java.util.Set; | ||||
|  | ||||
| import net.tomatentum.marinara.Marinara; | ||||
| import net.tomatentum.cutin.MethodParser; | ||||
| import net.tomatentum.cutin.ReflectedMethodFactory.ParserResults; | ||||
| import net.tomatentum.cutin.container.MethodContainer; | ||||
| import net.tomatentum.cutin.method.ReflectedMethod; | ||||
| import net.tomatentum.marinara.checks.AppliedCheck; | ||||
| import net.tomatentum.marinara.checks.CheckExecutionContext; | ||||
| import net.tomatentum.marinara.checks.CheckMethodIdentifier; | ||||
| import net.tomatentum.marinara.interaction.InteractionHandler; | ||||
| import net.tomatentum.marinara.interaction.InteractionType; | ||||
| import net.tomatentum.marinara.interaction.commands.ExecutableSlashCommandDefinition; | ||||
| import net.tomatentum.marinara.parser.AnnotationParser; | ||||
| import net.tomatentum.marinara.interaction.ident.InteractionIdentifier; | ||||
| import net.tomatentum.marinara.interaction.ident.SlashCommandIdentifier; | ||||
| import net.tomatentum.marinara.parser.InteractionCheckParser; | ||||
| import net.tomatentum.marinara.parser.SlashCommandParser; | ||||
| import net.tomatentum.marinara.wrapper.ContextObjectProvider; | ||||
|  | ||||
| public class SlashCommandInteractionMethod extends InteractionMethod { | ||||
|  | ||||
|     private ExecutableSlashCommandDefinition commandDefinition; | ||||
|     private SlashCommandIdentifier identifier; | ||||
|     private ContextObjectProvider cop; | ||||
|  | ||||
|     SlashCommandInteractionMethod(Method method, InteractionHandler handler, Marinara marinara) { | ||||
|         super(method, handler, marinara); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public AnnotationParser[] getParsers() { | ||||
|         return new AnnotationParser[] {  | ||||
|             new SlashCommandParser(method, (x) -> { this.commandDefinition = x; } )  | ||||
|         }; | ||||
|     private SlashCommandInteractionMethod( | ||||
|             Method method,  | ||||
|             InteractionHandler handler, | ||||
|             List<AppliedCheck> appliedChecks, | ||||
|             SlashCommandIdentifier identifier, | ||||
|             ContextObjectProvider cop | ||||
|         ) { | ||||
|         super(method, handler, appliedChecks); | ||||
|         this.identifier = identifier; | ||||
|         this.cop = cop; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Object getParameter(Object context, int index) { | ||||
|         return marinara.getWrapper().getContextObjectProvider().convertCommandOption(context, commandDefinition.options()[index].name()); | ||||
|         Object superResult = super.getParameter(context, index); | ||||
|         if (superResult == null) | ||||
|             return this.cop.convertCommandOption(context, identifier.options()[index-1].name()); | ||||
|         else | ||||
|             return superResult; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public boolean canRun(Object context) { | ||||
|         ExecutableSlashCommandDefinition other = marinara.getWrapper().getCommandDefinition(context); | ||||
|         return commandDefinition.equals(other); | ||||
|     public InteractionIdentifier identifier() { | ||||
|         return identifier; | ||||
|     } | ||||
|  | ||||
|     public static class Factory extends InteractionMethod.Factory { | ||||
|  | ||||
|         private ContextObjectProvider cop; | ||||
|  | ||||
|         public Factory(MethodContainer<CheckMethodIdentifier, CheckExecutionContext> checkContainer, ContextObjectProvider cop) { | ||||
|             super(checkContainer); | ||||
|             this.cop = cop; | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|     public InteractionType getType() { | ||||
|         return InteractionType.COMMAND; | ||||
|         public Optional<ReflectedMethod<InteractionIdentifier, Object>> produce(Method method, Object containingObject, ParserResults parserResults) { | ||||
|             if (!(containingObject instanceof InteractionHandler)) return Optional.empty(); | ||||
|             SlashCommandIdentifier ident = parserResults.get(SlashCommandParser.class); | ||||
|             if (ident == null) return Optional.empty(); | ||||
|             return Optional.of(new SlashCommandInteractionMethod( | ||||
|                 method,  | ||||
|                 (InteractionHandler) containingObject, | ||||
|                 parserResults.get(InteractionCheckParser.class), | ||||
|                 ident, | ||||
|                 cop | ||||
|             )); | ||||
|         } | ||||
|  | ||||
|     public ExecutableSlashCommandDefinition getCommandDefinition() { | ||||
|         return commandDefinition; | ||||
|     } | ||||
|         @Override | ||||
|         public void addParser(Set<MethodParser> parser) { | ||||
|             super.addParser(parser); | ||||
|  | ||||
|             parser.add( | ||||
|                 new SlashCommandParser() | ||||
|             ); | ||||
|         } | ||||
|  | ||||
|     public void setCommandDefinition(ExecutableSlashCommandDefinition commandDefinition) { | ||||
|         this.commandDefinition = commandDefinition; | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -0,0 +1,54 @@ | ||||
| package net.tomatentum.marinara.interaction.processor; | ||||
|  | ||||
| import java.util.Arrays; | ||||
| import java.util.List; | ||||
| import java.util.Objects; | ||||
| import java.util.Optional; | ||||
| import java.util.Set; | ||||
|  | ||||
| import net.tomatentum.cutin.container.MethodContainer; | ||||
| import net.tomatentum.marinara.interaction.InteractionType; | ||||
| import net.tomatentum.marinara.interaction.ident.InteractionIdentifier; | ||||
| import net.tomatentum.marinara.interaction.ident.SlashCommandIdentifier; | ||||
| import net.tomatentum.marinara.wrapper.IdentifierProvider; | ||||
| import net.tomatentum.marinara.wrapper.LibraryWrapper; | ||||
|  | ||||
| public class AutocompleteInteractionProcessor extends InteractionMethodProcessor { | ||||
|  | ||||
|     private LibraryWrapper wrapper; | ||||
|  | ||||
|     public AutocompleteInteractionProcessor(LibraryWrapper wrapper, IdentifierProvider provider) { | ||||
|         super(provider, Set.of(InteractionType.AUTOCOMPLETE)); | ||||
|         this.wrapper = wrapper; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void processInteraction(Object context, MethodContainer<InteractionIdentifier, Object> container, InteractionIdentifier identifier) { | ||||
|         Optional<InteractionIdentifier> oIdent = container.identifiers().stream() | ||||
|             .filter(i -> convertToCommandIdentifier(identifier).equals(i)) | ||||
|             .findFirst(); | ||||
|         if (oIdent.isPresent() && oIdent.get() instanceof SlashCommandIdentifier sIdent) { | ||||
|             List<String> autocompleteRefs = Arrays.asList(this.wrapper.getContextObjectProvider() | ||||
|                 .getAutocompleteFocusedOption(context).getAutocompleteRefs(sIdent.options())); | ||||
|             List<Object> results = container.methods().stream() | ||||
|                 .filter(m -> m.identifier().type().equals(InteractionType.AUTOCOMPLETE)) | ||||
|                 .filter(m -> autocompleteRefs.contains(m.identifier().name())) | ||||
|                 .map(m -> m.run(context)) | ||||
|                 .filter(Objects::nonNull) | ||||
|                 .toList(); | ||||
|             if (!results.isEmpty()) | ||||
|                 this.wrapper.respondAutocomplete(context, results); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private InteractionIdentifier convertToCommandIdentifier(InteractionIdentifier identifier) { | ||||
|         if (Objects.isNull(identifier)) | ||||
|             return null; | ||||
|         return InteractionIdentifier.builder() | ||||
|             .type(InteractionType.COMMAND) | ||||
|             .name(identifier.name()) | ||||
|             .parent(convertToCommandIdentifier(identifier.parent())) | ||||
|             .build(); | ||||
|     } | ||||
|      | ||||
| } | ||||
| @@ -0,0 +1,22 @@ | ||||
| package net.tomatentum.marinara.interaction.processor; | ||||
|  | ||||
| import java.util.Set; | ||||
|  | ||||
| import net.tomatentum.cutin.container.MethodContainer; | ||||
| import net.tomatentum.marinara.interaction.InteractionType; | ||||
| import net.tomatentum.marinara.interaction.ident.InteractionIdentifier; | ||||
| import net.tomatentum.marinara.wrapper.IdentifierProvider; | ||||
|  | ||||
| public class DirectInteractionProcessor extends InteractionMethodProcessor { | ||||
|  | ||||
|  | ||||
|     public DirectInteractionProcessor(IdentifierProvider provider, InteractionType... types) { | ||||
|         super(provider, Set.of(types)); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected void processInteraction(Object context, MethodContainer<InteractionIdentifier, Object> container, InteractionIdentifier identifier) { | ||||
|         container.findFor(identifier).forEach(m -> m.run(context)); | ||||
|     } | ||||
|      | ||||
| } | ||||
| @@ -0,0 +1,39 @@ | ||||
| package net.tomatentum.marinara.interaction.processor; | ||||
|  | ||||
| import java.util.Set; | ||||
|  | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
|  | ||||
| import net.tomatentum.cutin.MethodProcessor; | ||||
| import net.tomatentum.cutin.container.MethodContainer; | ||||
| import net.tomatentum.marinara.interaction.InteractionType; | ||||
| import net.tomatentum.marinara.interaction.ident.InteractionIdentifier; | ||||
| import net.tomatentum.marinara.wrapper.IdentifierProvider; | ||||
|  | ||||
| public abstract class InteractionMethodProcessor implements MethodProcessor<InteractionIdentifier, Object> { | ||||
|  | ||||
|     private Logger logger = LoggerFactory.getLogger(getClass()); | ||||
|  | ||||
|     private IdentifierProvider provider; | ||||
|     private Set<InteractionType> types; | ||||
|  | ||||
|     protected InteractionMethodProcessor(IdentifierProvider provider, Set<InteractionType> types) { | ||||
|         this.provider = provider; | ||||
|         this.types = types; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void process(Object context, MethodContainer<InteractionIdentifier, Object> container) { | ||||
|         InteractionIdentifier identifier = this.provider.provide(context); | ||||
|         if (!this.types.contains(identifier.type())) return; | ||||
|         logger.debug("Processing {} : {} with context {}", identifier, identifier.type(), context); | ||||
|         this.processInteraction(context, container, identifier); | ||||
|     } | ||||
|  | ||||
|     protected abstract void processInteraction( | ||||
|         Object context,  | ||||
|         MethodContainer<InteractionIdentifier, Object> container,  | ||||
|         InteractionIdentifier identifier); | ||||
|      | ||||
| } | ||||
| @@ -1,8 +0,0 @@ | ||||
| package net.tomatentum.marinara.parser; | ||||
|  | ||||
| import java.lang.reflect.Method; | ||||
|  | ||||
| public interface AnnotationParser { | ||||
|     void parse(); | ||||
|     Method getMethod(); | ||||
| } | ||||
| @@ -0,0 +1,27 @@ | ||||
| package net.tomatentum.marinara.parser; | ||||
|  | ||||
| import java.lang.reflect.Method; | ||||
| import java.util.Arrays; | ||||
|  | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
|  | ||||
| import net.tomatentum.cutin.MethodParser; | ||||
| import net.tomatentum.cutin.util.ReflectionUtil; | ||||
| import net.tomatentum.marinara.interaction.annotation.AutoComplete; | ||||
|  | ||||
| public class AutocompleteParser implements MethodParser { | ||||
|  | ||||
|     private Logger logger = LoggerFactory.getLogger(getClass()); | ||||
|  | ||||
|     @Override | ||||
|     public Object parse(Method method, Object containingObject) { | ||||
|         String[] autoCompletes = Arrays.stream(method.getAnnotationsByType(AutoComplete.class)) | ||||
|             .map(AutoComplete::value) | ||||
|             .toArray(String[]::new); | ||||
|         if (autoCompletes.length > 0) | ||||
|             logger.trace("Parsed AutoComplete annotations {} for method {}", autoCompletes, ReflectionUtil.getFullMethodName(method)); | ||||
|         return autoCompletes; | ||||
|     } | ||||
|      | ||||
| } | ||||
| @@ -1,36 +1,27 @@ | ||||
| package net.tomatentum.marinara.parser; | ||||
|  | ||||
| import java.lang.reflect.Method; | ||||
| import java.util.function.Consumer; | ||||
|  | ||||
| import org.apache.logging.log4j.Logger; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
|  | ||||
| import net.tomatentum.cutin.MethodParser; | ||||
| import net.tomatentum.cutin.util.ReflectionUtil; | ||||
| import net.tomatentum.marinara.interaction.annotation.Button; | ||||
| import net.tomatentum.marinara.util.LoggerUtil; | ||||
| import net.tomatentum.marinara.util.ReflectionUtil; | ||||
|  | ||||
| public class ButtonParser implements AnnotationParser { | ||||
| public class ButtonParser implements MethodParser { | ||||
|      | ||||
|     private Method method; | ||||
|     private Consumer<String> consumer; | ||||
|     private Logger logger = LoggerFactory.getLogger(getClass()); | ||||
|  | ||||
|     private Logger logger = LoggerUtil.getLogger(getClass()); | ||||
|  | ||||
|     public ButtonParser(Method method, Consumer<String> consumer) { | ||||
|         this.method = method; | ||||
|         this.consumer = consumer; | ||||
|     public ButtonParser() { | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void parse() { | ||||
|         Button button = getMethod().getAnnotation(Button.class); | ||||
|         logger.trace("Parsed Button annotation {} for method {}", button.toString(), ReflectionUtil.getFullMethodName(method)); | ||||
|         this.consumer.accept(button.value()); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Method getMethod() { | ||||
|         return this.method; | ||||
|     public Object parse(Method method, Object containingObject) { | ||||
|         Button button = method.getAnnotation(Button.class); | ||||
|         if (button == null) return null; | ||||
|         logger.trace("Parsed Button annotation {} for method {}", button, ReflectionUtil.getFullMethodName(method)); | ||||
|         return button.value(); | ||||
|     } | ||||
|      | ||||
| } | ||||
|   | ||||
| @@ -0,0 +1,28 @@ | ||||
| package net.tomatentum.marinara.parser; | ||||
|  | ||||
| import java.lang.reflect.Method; | ||||
| import java.lang.reflect.ParameterizedType; | ||||
| import java.lang.reflect.Type; | ||||
|  | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
|  | ||||
| import io.leangen.geantyref.GenericTypeReflector; | ||||
| import net.tomatentum.cutin.MethodParser; | ||||
| import net.tomatentum.marinara.checks.InteractionCheck; | ||||
|  | ||||
| public class InteractionCheckClassParser implements MethodParser { | ||||
|  | ||||
|     private Logger logger = LoggerFactory.getLogger(getClass()); | ||||
|  | ||||
|     @Override | ||||
|     public Object parse(Method method, Object containingObject) { | ||||
|         ParameterizedType type = (ParameterizedType) GenericTypeReflector.getExactSuperType(containingObject.getClass(), InteractionCheck.class); | ||||
|         if (type == null) return null; | ||||
|         Type typeParam = type.getActualTypeArguments().length == 1 ? type.getActualTypeArguments()[0] : null; | ||||
|         if (typeParam != null) | ||||
|             logger.trace("Parsed InteractionCheck Annotation {}", typeParam); | ||||
|         return typeParam; | ||||
|     } | ||||
|      | ||||
| } | ||||
| @@ -3,49 +3,47 @@ package net.tomatentum.marinara.parser; | ||||
| import java.lang.annotation.Annotation; | ||||
| import java.lang.reflect.Method; | ||||
| import java.util.Arrays; | ||||
| import java.util.Optional; | ||||
| import java.util.function.Consumer; | ||||
| import java.util.Objects; | ||||
|  | ||||
| import org.apache.logging.log4j.Logger; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
|  | ||||
| import net.tomatentum.cutin.MethodParser; | ||||
| import net.tomatentum.cutin.container.MethodContainer; | ||||
| import net.tomatentum.cutin.util.ReflectionUtil; | ||||
| import net.tomatentum.marinara.checks.AppliedCheck; | ||||
| import net.tomatentum.marinara.checks.InteractionCheck; | ||||
| import net.tomatentum.marinara.registry.InteractionCheckRegistry; | ||||
| import net.tomatentum.marinara.util.LoggerUtil; | ||||
| import net.tomatentum.marinara.util.ReflectionUtil; | ||||
| import net.tomatentum.marinara.checks.CheckExecutionContext; | ||||
| import net.tomatentum.marinara.checks.CheckMethodIdentifier; | ||||
| import net.tomatentum.marinara.checks.CheckMethodIdentifier.CheckMethodType; | ||||
|  | ||||
| public class InteractionCheckParser implements AnnotationParser { | ||||
| public class InteractionCheckParser implements MethodParser { | ||||
|  | ||||
|     private InteractionCheckRegistry checkRegistry; | ||||
|     private Method method; | ||||
|     private Consumer<AppliedCheck> consumer; | ||||
|     private MethodContainer<CheckMethodIdentifier, CheckExecutionContext> checkContainer; | ||||
|  | ||||
|     private Logger logger = LoggerUtil.getLogger(getClass()); | ||||
|     private Logger logger = LoggerFactory.getLogger(getClass()); | ||||
|  | ||||
|     public InteractionCheckParser(Method method, Consumer<AppliedCheck> consumer, InteractionCheckRegistry checkRegistry) { | ||||
|         this.checkRegistry = checkRegistry; | ||||
|         this.method = method; | ||||
|         this.consumer = consumer; | ||||
|     public InteractionCheckParser(MethodContainer<CheckMethodIdentifier, CheckExecutionContext> checkContainer) { | ||||
|         this.checkContainer = checkContainer; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void parse() { | ||||
|     public Object parse(Method method, Object containingObject) { | ||||
|         Annotation[] annotations = method.getAnnotations(); | ||||
|         Arrays.stream(annotations).forEach(this::convertAnnotation); | ||||
|         return Arrays.stream(annotations) | ||||
|             .map(a -> convertAnnotation(a, method)) | ||||
|             .filter(Objects::nonNull) | ||||
|             .toList(); | ||||
|     } | ||||
|  | ||||
|     private void convertAnnotation(Annotation annotation) { | ||||
|             Optional<InteractionCheck<?>> check = this.checkRegistry.getCheckFromAnnotation(annotation.annotationType()); | ||||
|             if (check.isPresent())  { | ||||
|                 AppliedCheck appliedCheck = new AppliedCheck(check.get(), annotation); | ||||
|                 logger.trace("Parsed InteractionCheck {} for annotation {} for method {}", check.getClass().getName(), annotation.toString(), ReflectionUtil.getFullMethodName(method)); | ||||
|                 consumer.accept(appliedCheck); | ||||
|     private AppliedCheck convertAnnotation(Annotation annotation, Method method) { | ||||
|             var preExec = this.checkContainer.findFirstFor(new CheckMethodIdentifier(annotation.annotationType(), CheckMethodType.PRE)); | ||||
|             var postExec = this.checkContainer.findFirstFor(new CheckMethodIdentifier(annotation.annotationType(), CheckMethodType.POST)); | ||||
|             if (preExec.isPresent() && postExec.isPresent())  { | ||||
|                 AppliedCheck appliedCheck = new AppliedCheck(annotation, preExec.get(), postExec.get()); | ||||
|                 logger.trace("Parsed InteractionCheck {} for annotation {} for method {}", preExec.get().containingObject(), annotation, ReflectionUtil.getFullMethodName(method)); | ||||
|                 return appliedCheck; | ||||
|             } | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Method getMethod() { | ||||
|         return this.method; | ||||
|             return null; | ||||
|     } | ||||
|      | ||||
| } | ||||
|   | ||||
| @@ -1,70 +1,73 @@ | ||||
| package net.tomatentum.marinara.parser; | ||||
|  | ||||
| import java.lang.reflect.Method; | ||||
| import java.util.function.Consumer; | ||||
|  | ||||
| import org.apache.logging.log4j.Logger; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
|  | ||||
| import net.tomatentum.marinara.interaction.commands.ExecutableSlashCommandDefinition; | ||||
| import net.tomatentum.cutin.MethodParser; | ||||
| import net.tomatentum.cutin.util.ReflectionUtil; | ||||
| 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.util.LoggerUtil; | ||||
| import net.tomatentum.marinara.util.ReflectionUtil; | ||||
| import net.tomatentum.marinara.interaction.ident.InteractionIdentifier; | ||||
|  | ||||
| public class SlashCommandParser implements AnnotationParser { | ||||
| public class SlashCommandParser implements MethodParser { | ||||
|  | ||||
|     private Method method; | ||||
|     private Consumer<ExecutableSlashCommandDefinition> consumer; | ||||
|     private Logger logger = LoggerFactory.getLogger(getClass()); | ||||
|  | ||||
|     private Logger logger = LoggerUtil.getLogger(getClass()); | ||||
|  | ||||
|     public SlashCommandParser(Method method, Consumer<ExecutableSlashCommandDefinition> consumer) { | ||||
|         this.method = method; | ||||
|         this.consumer = consumer; | ||||
|     public SlashCommandParser() { | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void parse() { | ||||
|     public Object parse(Method method, Object containingObject) { | ||||
|         if (!method.isAnnotationPresent(SlashCommand.class) && !method.isAnnotationPresent(SubCommand.class)) return null; | ||||
|         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(); | ||||
|  | ||||
|         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(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(); | ||||
|         } | ||||
|  | ||||
|         ExecutableSlashCommandDefinition def = builder.build(); | ||||
|  | ||||
|         logger.trace("Parsed using SlashCommandParser for method {} with the result:\n{}", ReflectionUtil.getFullMethodName(method), def.toString()); | ||||
|         consumer.accept(builder.build()); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Method getMethod() { | ||||
|        return this.method; | ||||
|         logger.trace("Parsed using SlashCommandParser for method {} with the result: {}", ReflectionUtil.getFullMethodName(method), lastIdentifier); | ||||
|         return lastIdentifier; | ||||
|     } | ||||
|  | ||||
|     private void checkValidCommandMethod(Method method) { | ||||
|         if (method.isAnnotationPresent(SlashCommand.class) &&  | ||||
|             method.getDeclaringClass().isAnnotationPresent(SlashCommand.class)) { | ||||
|             throw new RuntimeException(method.getName() + ": Can't have ApplicationCommand Annotation on Class and Method"); | ||||
|             throw new RuntimeException(method.getName() + ": Can't have SlashCommand Annotation on Class and Method"); | ||||
|         } | ||||
|  | ||||
|         if (!ReflectionUtil.isAnnotationPresent(method, SlashCommand.class)) | ||||
|             throw new RuntimeException(method.getName() + ": Missing ApplicationCommand Annotation on either Class or Method"); | ||||
|             throw new RuntimeException(method.getName() + ": Missing SlashCommand Annotation on either Class or Method"); | ||||
|  | ||||
|         if ((method.isAnnotationPresent(SubCommand.class) &&  | ||||
|             !ReflectionUtil.isAnnotationPresent(method, SlashCommand.class))) { | ||||
|             throw new RuntimeException(method.getName() + ": Missing ApplicationCommand Annotation on either Method or Class"); | ||||
|             throw new RuntimeException(method.getName() + ": Missing SlashCommand Annotation on either Method or Class"); | ||||
|         } | ||||
|     } | ||||
|      | ||||
|   | ||||
| @@ -1,40 +0,0 @@ | ||||
| package net.tomatentum.marinara.registry; | ||||
|  | ||||
| import java.lang.reflect.ParameterizedType; | ||||
| import java.lang.reflect.Type; | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
| import java.util.Optional; | ||||
|  | ||||
| import org.apache.logging.log4j.Logger; | ||||
|  | ||||
| import io.leangen.geantyref.GenericTypeReflector; | ||||
| import net.tomatentum.marinara.checks.InteractionCheck; | ||||
| import net.tomatentum.marinara.util.LoggerUtil; | ||||
|  | ||||
| public class InteractionCheckRegistry { | ||||
|      | ||||
|     private List<InteractionCheck<?>> checks; | ||||
|  | ||||
|     private Logger logger = LoggerUtil.getLogger(getClass()); | ||||
|  | ||||
|     public InteractionCheckRegistry() { | ||||
|         this.checks = new ArrayList<>(); | ||||
|     } | ||||
|  | ||||
|     public void addCheck(InteractionCheck<?> check) { | ||||
|         checks.add(check); | ||||
|         logger.info("Registered Check {}", check.getClass().getName()); | ||||
|     } | ||||
|  | ||||
|     public Optional<InteractionCheck<?>> getCheckFromAnnotation(Type annotation) { | ||||
|         for (InteractionCheck<?> interactionCheck : checks) { | ||||
|             ParameterizedType type = (ParameterizedType) GenericTypeReflector.getExactSuperType(interactionCheck.getClass(), InteractionCheck.class); | ||||
|             Type typeParam = type.getActualTypeArguments()[0]; | ||||
|             if (typeParam.equals(annotation)) | ||||
|                 return Optional.of(interactionCheck); | ||||
|         } | ||||
|         return Optional.empty(); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -1,78 +0,0 @@ | ||||
| package net.tomatentum.marinara.registry; | ||||
|  | ||||
| import java.lang.reflect.Method; | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
| import java.util.Optional; | ||||
|  | ||||
| import org.apache.logging.log4j.Logger; | ||||
|  | ||||
| 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.util.LoggerUtil; | ||||
| import net.tomatentum.marinara.interaction.methods.InteractionMethod; | ||||
|  | ||||
| public class InteractionRegistry { | ||||
|     private Logger logger = LoggerUtil.getLogger(getClass()); | ||||
|     private List<InteractionMethod> interactionMethods; | ||||
|     private Marinara marinara; | ||||
|  | ||||
|     public InteractionRegistry(Marinara marinara) { | ||||
|         this.interactionMethods = new ArrayList<>(); | ||||
|         this.marinara = marinara; | ||||
|         marinara.getWrapper().subscribeInteractions(this::handle); | ||||
|     } | ||||
|  | ||||
|     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()); | ||||
|             } | ||||
|         } | ||||
|         logger.info("Added all Interactions from {}", interactionHandler.getClass().getSimpleName()); | ||||
|     } | ||||
|  | ||||
|     public void registerCommands() { | ||||
|         List<SlashCommandDefinition> defs = new ArrayList<>(); | ||||
|         List<ExecutableSlashCommandDefinition> execDefs = interactionMethods.stream() | ||||
|             .filter((x) -> x.getClass().isAssignableFrom(SlashCommandInteractionMethod.class)) | ||||
|             .map((x) -> ((SlashCommandInteractionMethod)x).getCommandDefinition()) | ||||
|             .toList(); | ||||
|  | ||||
|         execDefs.forEach((def) -> { | ||||
|             Optional<SlashCommandDefinition> 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)); | ||||
|  | ||||
|             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)); | ||||
|         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); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,64 @@ | ||||
| package net.tomatentum.marinara.structure; | ||||
|  | ||||
| import java.lang.reflect.Method; | ||||
| import java.util.Optional; | ||||
| import java.util.Set; | ||||
|  | ||||
| import net.tomatentum.cutin.MethodParser; | ||||
| import net.tomatentum.cutin.ReflectedMethodFactory; | ||||
| import net.tomatentum.cutin.ReflectedMethodFactory.ParserResults; | ||||
| import net.tomatentum.cutin.method.ReflectedMethod; | ||||
| import net.tomatentum.marinara.interaction.ident.InteractionIdentifier; | ||||
| import net.tomatentum.marinara.structure.parser.ComponentStructureParser; | ||||
|  | ||||
| public class ComponentStructureMethod extends ReflectedMethod<InteractionIdentifier, Void> { | ||||
|  | ||||
|     private InteractionIdentifier identifier; | ||||
|  | ||||
|     private ComponentStructureMethod( | ||||
|             Method method,  | ||||
|             Object containingObject, | ||||
|             InteractionIdentifier identifier | ||||
|         ) { | ||||
|         super(method, containingObject); | ||||
|         this.identifier = identifier; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Object getParameter(Void context, int index) { | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public InteractionIdentifier identifier() { | ||||
|         return this.identifier; | ||||
|     } | ||||
|  | ||||
|     public static class Factory implements ReflectedMethodFactory.Factory<InteractionIdentifier, Void> { | ||||
|  | ||||
|         private Class<? extends Object> buttonClass; | ||||
|  | ||||
|         public Factory(Class<? extends Object> buttonClass) { | ||||
|             this.buttonClass = buttonClass; | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public void addParser(Set<MethodParser> parser) { | ||||
|             parser.add(new ComponentStructureParser(buttonClass)); | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public Optional<ReflectedMethod<InteractionIdentifier, Void>> produce( | ||||
|                 Method method,  | ||||
|                 Object containingObject, | ||||
|                 ParserResults parserResults) { | ||||
|             InteractionIdentifier identifier = parserResults.get(ComponentStructureParser.class); | ||||
|             if (identifier == null) | ||||
|                 return Optional.empty(); | ||||
|              | ||||
|             return Optional.of(new ComponentStructureMethod(method, containingObject, identifier)); | ||||
|         } | ||||
|  | ||||
|     } | ||||
|      | ||||
| } | ||||
| @@ -0,0 +1,39 @@ | ||||
| package net.tomatentum.marinara.structure; | ||||
|  | ||||
| import java.util.Optional; | ||||
|  | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
|  | ||||
| import net.tomatentum.cutin.ReflectedMethodFactoryImpl; | ||||
| import net.tomatentum.cutin.container.LoneMethodContainer; | ||||
| import net.tomatentum.cutin.method.ReflectedMethod; | ||||
| import net.tomatentum.marinara.interaction.ident.InteractionIdentifier; | ||||
|  | ||||
| public class MethodStructureProvider<B extends Object>  | ||||
|     extends LoneMethodContainer<InteractionIdentifier, Void> | ||||
|     implements StructureProvider<B> { | ||||
|  | ||||
|     private Logger logger = LoggerFactory.getLogger(getClass()); | ||||
|  | ||||
|     private Class<? extends B> buttonClass; | ||||
|  | ||||
|     public MethodStructureProvider(Class<? extends B> buttonClass) { | ||||
|         super(new ReflectedMethodFactoryImpl<>()); | ||||
|         super.factory().addFactory(new ComponentStructureMethod.Factory(buttonClass)); | ||||
|         this.buttonClass = buttonClass; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public B button(InteractionIdentifier identifier) { | ||||
|         Optional<ReflectedMethod<InteractionIdentifier, Void>> method = super.findFirstFor(identifier); | ||||
|         if (method.isEmpty()) return null; | ||||
|         try { | ||||
|             return buttonClass.cast(method.get().run(null)); | ||||
|         }catch (ClassCastException ex) { | ||||
|             logger.warn("Structure Method {} return type did not match expected {}", method.get(), buttonClass); | ||||
|             return null; | ||||
|         } | ||||
|     } | ||||
|      | ||||
| } | ||||
| @@ -0,0 +1,31 @@ | ||||
| package net.tomatentum.marinara.structure; | ||||
|  | ||||
| import java.util.Map; | ||||
| import java.util.TreeMap; | ||||
|  | ||||
| import net.tomatentum.marinara.interaction.ident.InteractionIdentifier; | ||||
|  | ||||
| public class PriorityStructureProvider<B extends Object> implements StructureProvider<B> { | ||||
|  | ||||
|     private Map<Short, StructureProvider<B>> providerMap; | ||||
|  | ||||
|     public PriorityStructureProvider() { | ||||
|         this.providerMap = new TreeMap<>(); | ||||
|     } | ||||
|  | ||||
|     public PriorityStructureProvider<B> addProvider(StructureProvider<B> provider, short priority) { | ||||
|         this.providerMap.put(priority, provider); | ||||
|         return this; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public B button(InteractionIdentifier identifier) { | ||||
|         for (short priority : this.providerMap.keySet()) { | ||||
|             B result = this.providerMap.get(priority).button(identifier); | ||||
|             if (result != null) | ||||
|                 return result; | ||||
|         } | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,31 @@ | ||||
| package net.tomatentum.marinara.structure; | ||||
|  | ||||
| import java.util.HashMap; | ||||
| import java.util.Map; | ||||
|  | ||||
| import net.tomatentum.marinara.interaction.ident.InteractionIdentifier; | ||||
| import net.tomatentum.marinara.structure.data.ButtonStructureData; | ||||
| import net.tomatentum.marinara.wrapper.ComponentStructureConverter; | ||||
|  | ||||
| public class StaticStructureProvider<B extends Object> implements StructureProvider<B> { | ||||
|  | ||||
|     private ComponentStructureConverter<B> converter; | ||||
|  | ||||
|     private Map<InteractionIdentifier, B> buttonMap; | ||||
|  | ||||
|     public StaticStructureProvider(ComponentStructureConverter<B> converter) { | ||||
|         this.converter = converter; | ||||
|         this.buttonMap = new HashMap<>(); | ||||
|     } | ||||
|  | ||||
|     public StaticStructureProvider<B> addButton(InteractionIdentifier identifier, ButtonStructureData data) { | ||||
|         this.buttonMap.put(identifier, this.converter.convertButton(data)); | ||||
|         return this; | ||||
|     } | ||||
|      | ||||
|     @Override | ||||
|     public B button(InteractionIdentifier identifier) { | ||||
|         return this.buttonMap.get(identifier); | ||||
|     } | ||||
|      | ||||
| } | ||||
| @@ -0,0 +1,7 @@ | ||||
| package net.tomatentum.marinara.structure; | ||||
|  | ||||
| import net.tomatentum.marinara.interaction.ident.InteractionIdentifier; | ||||
|  | ||||
| public interface StructureProvider<B extends Object> { | ||||
|     B button(InteractionIdentifier identifier); | ||||
| } | ||||
| @@ -0,0 +1,5 @@ | ||||
| package net.tomatentum.marinara.structure.annotation; | ||||
|  | ||||
| public @interface ComponentStructure { | ||||
|     public String customId(); | ||||
| } | ||||
| @@ -0,0 +1,26 @@ | ||||
| package net.tomatentum.marinara.structure.data; | ||||
|  | ||||
| import net.tomatentum.marinara.interaction.annotation.Button; | ||||
|  | ||||
| public record ButtonStructureData( | ||||
|     String customId,  | ||||
|     String label, | ||||
|     ButtonStyle style, | ||||
|     String url, | ||||
|     boolean disabled, | ||||
|     String emoji | ||||
|     ) { | ||||
|  | ||||
|     public ButtonStructureData(Button button) { | ||||
|         this(button.value(), button.label(), button.style(), button.url(), button.disabled(), button.emoji()); | ||||
|     } | ||||
|  | ||||
|     public enum ButtonStyle { | ||||
|         PRIMARY, | ||||
|         SECONDARY, | ||||
|         SUCCESS, | ||||
|         DANGER, | ||||
|         LINK; | ||||
|     } | ||||
|      | ||||
| } | ||||
| @@ -0,0 +1,44 @@ | ||||
| package net.tomatentum.marinara.structure.parser; | ||||
|  | ||||
| import java.lang.reflect.Method; | ||||
|  | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
|  | ||||
| import net.tomatentum.cutin.MethodParser; | ||||
| import net.tomatentum.cutin.util.ReflectionUtil; | ||||
| import net.tomatentum.marinara.interaction.InteractionType; | ||||
| import net.tomatentum.marinara.interaction.ident.InteractionIdentifier; | ||||
| import net.tomatentum.marinara.structure.annotation.ComponentStructure; | ||||
|  | ||||
| public class ComponentStructureParser implements MethodParser { | ||||
|  | ||||
|     private Logger logger = LoggerFactory.getLogger(getClass()); | ||||
|  | ||||
|     private Class<? extends Object> buttonClass; | ||||
|  | ||||
|     public ComponentStructureParser(Class<? extends Object> buttonClass) { | ||||
|         this.buttonClass = buttonClass; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Object parse(Method method, Object containingObject) { | ||||
|         if (!method.isAnnotationPresent(ComponentStructure.class))  | ||||
|             return null; | ||||
|         InteractionIdentifier.Builder builder = InteractionIdentifier.builder() | ||||
|             .name(method.getAnnotation(ComponentStructure.class).customId()); | ||||
|  | ||||
|         if (buttonClass.isAssignableFrom(method.getReturnType())) | ||||
|             builder.type(InteractionType.BUTTON); | ||||
|  | ||||
|         if (builder.type() == null) { | ||||
|             logger.error("Structure Method {} return type did not match any of the required {}", String.join(","), buttonClass); | ||||
|             return null; | ||||
|         } | ||||
|  | ||||
|         logger.trace("Parsed Structure Method of {} with result {}", ReflectionUtil.getFullMethodName(method), builder.build()); | ||||
|         return builder.build(); | ||||
|  | ||||
|     } | ||||
|      | ||||
| } | ||||
| @@ -1,24 +0,0 @@ | ||||
| package net.tomatentum.marinara.util; | ||||
|  | ||||
| import java.util.Properties; | ||||
|  | ||||
| import org.apache.logging.log4j.Level; | ||||
| import org.apache.logging.log4j.LogManager; | ||||
| import org.apache.logging.log4j.Logger; | ||||
| import org.apache.logging.log4j.simple.SimpleLogger; | ||||
| import org.apache.logging.log4j.util.PropertiesUtil; | ||||
| import org.apache.logging.log4j.util.ProviderUtil; | ||||
|  | ||||
| public class LoggerUtil { | ||||
|     public static Logger getLogger(String name) { | ||||
|         if (ProviderUtil.hasProviders()) { | ||||
|             return LogManager.getLogger(name); | ||||
|         }else | ||||
|             return new SimpleLogger(name, Level.DEBUG, true, false, true, true, "yyyy-MM-dd HH:mm:ss.SSSZ", null, | ||||
|                                                  new PropertiesUtil(new Properties()), System.out); | ||||
|     } | ||||
|  | ||||
|     public static Logger getLogger(Class<?> clazz) { | ||||
|         return getLogger(clazz.getName()); | ||||
|     } | ||||
| } | ||||
| @@ -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<O, K, V> { | ||||
|     private Function<O, Iterable<K>> keySupplier; | ||||
|     private BiConsumer<V, O> valueConsumer; | ||||
|     private Function<K, V> defaultGenerator; | ||||
|  | ||||
|     public ObjectAggregator( | ||||
|             Function<O, Iterable<K>> keySupplier,  | ||||
|             BiConsumer<V, O> valueConsumer,  | ||||
|             Function<K, V> defaultGenerator) { | ||||
|         this.keySupplier = keySupplier; | ||||
|         this.valueConsumer = valueConsumer; | ||||
|         this.defaultGenerator = defaultGenerator; | ||||
|     } | ||||
|  | ||||
|     public ObjectAggregator( | ||||
|             Function<O, Iterable<K>> keySupplier,  | ||||
|             BiConsumer<V, O> valueConsumer,  | ||||
|             Supplier<V> defaultGenerator) { | ||||
|         this.keySupplier = keySupplier; | ||||
|         this.valueConsumer = valueConsumer; | ||||
|         this.defaultGenerator = _ -> defaultGenerator.get(); | ||||
|     } | ||||
|  | ||||
|     public Collection<V> aggregate(Iterable<O> iterator) { | ||||
|         Map<K, V> map = new HashMap<>(); | ||||
|         for (O element : iterator) { | ||||
|             Iterable<K> 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(); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,15 @@ | ||||
| package net.tomatentum.marinara.util; | ||||
|  | ||||
| import java.util.ArrayList; | ||||
| import java.util.function.Function; | ||||
|  | ||||
| public class ObjectListAggregator<O, K, V> extends ObjectAggregator<O, K, ArrayList<V>> { | ||||
|  | ||||
|     public ObjectListAggregator(Function<O, Iterable<K>> keySupplier, Function<O, V> valueConsumer) { | ||||
|         super(keySupplier,  | ||||
|         (l, o) -> l.add(valueConsumer.apply(o)), | ||||
|         () -> new ArrayList<>()); | ||||
|     } | ||||
|  | ||||
|      | ||||
| } | ||||
| @@ -1,107 +0,0 @@ | ||||
| package net.tomatentum.marinara.util; | ||||
|  | ||||
| import java.lang.annotation.Annotation; | ||||
| import java.lang.reflect.Method; | ||||
| import java.util.ArrayList; | ||||
| import java.util.Arrays; | ||||
| import java.util.List; | ||||
| import java.util.Objects; | ||||
|  | ||||
| public final class ReflectionUtil { | ||||
|  | ||||
|     public static <T extends Annotation> boolean isAnnotationPresent(Method method, Class<T> annotationClass) { | ||||
|         if (method.isAnnotationPresent(annotationClass) || method.getDeclaringClass().isAnnotationPresent(annotationClass)) | ||||
|             return true; | ||||
|  | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     public static <T extends Annotation> T getAnnotation(Method method, Class<T> annotationClass) { | ||||
|         return method.isAnnotationPresent(annotationClass) ?  | ||||
|             method.getAnnotation(annotationClass) :  | ||||
|             method.getDeclaringClass().getAnnotation(annotationClass); | ||||
|     } | ||||
|  | ||||
|     public static int getCastDepth(Class<?> child, Class<?> parent) { | ||||
|  | ||||
|         if (parent.equals(Object.class)) | ||||
|             return Integer.MAX_VALUE; | ||||
|  | ||||
|         if (!parent.isAssignableFrom(child)) { | ||||
|             throw new IllegalArgumentException("The specified class is not a child class of the specified parent."); | ||||
|         } | ||||
|  | ||||
|         int depth = 0; | ||||
|         Class<?> curr = child; | ||||
|         List<Class<?>> parents = new ArrayList<>(); | ||||
|  | ||||
|         while (!curr.equals(parent)) { | ||||
|             depth++; | ||||
|             parents.add(curr.getSuperclass()); | ||||
|             parents.addAll(Arrays.asList(curr.getInterfaces())); | ||||
|  | ||||
|             for (Class<?> currParent : parents) { | ||||
|                 if (currParent != null && parent.isAssignableFrom(currParent)) { | ||||
|                     curr = currParent; | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|             parents.clear(); | ||||
|         } | ||||
|  | ||||
|         return depth; | ||||
|     } | ||||
|      | ||||
|     public static Method getMostSpecificMethod(Method[] methods, Class<?>... parameters) { | ||||
|         List<Method> compatibleMethods = Arrays.stream(methods) | ||||
|             .filter(x -> isMethodCallable(x, parameters)) | ||||
|             .toList(); | ||||
|  | ||||
|         if (compatibleMethods.size() == 0) | ||||
|             throw new IllegalArgumentException("There are no compatible Methods provided"); | ||||
|  | ||||
|         for (int i = 0; i < parameters.length; i++) { | ||||
|             final int currI = i; | ||||
|             Class<?>[] parameterTypes = compatibleMethods.stream() | ||||
|                 .map(x -> x.getParameterTypes()[currI]) | ||||
|                 .toArray(x -> new Class[x]); | ||||
|  | ||||
|             Class<?> mostSpecific = getMostSpecificClass(parameterTypes, parameters[i]); | ||||
|  | ||||
|             compatibleMethods = compatibleMethods.stream() | ||||
|                 .filter(x -> Objects.equals(x.getParameterTypes()[currI], mostSpecific)) | ||||
|                 .toList(); | ||||
|         } | ||||
|          | ||||
|         return compatibleMethods.getFirst(); | ||||
|     } | ||||
|  | ||||
|     public static Class<?> getMostSpecificClass(Class<?>[] classes, Class<?> base) { | ||||
|         int min = Integer.MAX_VALUE; | ||||
|         Class<?> currMostSpecific = null; | ||||
|         for (Class<?> currClass : classes) { | ||||
|             int currCastDepth = getCastDepth(base, currClass); | ||||
|             if (currCastDepth <= min) { | ||||
|                 min = currCastDepth; | ||||
|                 currMostSpecific = currClass; | ||||
|             } | ||||
|         } | ||||
|         return currMostSpecific; | ||||
|     } | ||||
|  | ||||
|     public static boolean isMethodCallable(Method method, Class<?>... parameters) { | ||||
|         if (!Objects.equals(method.getParameterCount(), parameters.length)) | ||||
|             return false; | ||||
|  | ||||
|         Class<?>[] methodParams = method.getParameterTypes(); | ||||
|         for (int i = 0; i < parameters.length; i++) { | ||||
|             if (!methodParams[i].isAssignableFrom(parameters[i])) | ||||
|                 return false; | ||||
|         } | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     public static String getFullMethodName(Method method) { | ||||
|         return method.getClass().getName() + "." + method.getName(); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,70 @@ | ||||
| package net.tomatentum.marinara.wrapper; | ||||
|  | ||||
| import java.util.ArrayList; | ||||
| import java.util.Arrays; | ||||
| import java.util.List; | ||||
|  | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
|  | ||||
| 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<A extends Object, O extends Object, C extends Object> { | ||||
|  | ||||
|     public static <A, O, C> CommandConverter<A, O, C> of(Spec<A, O, C> spec) { | ||||
|         return new CommandConverter<>(spec); | ||||
|     } | ||||
|  | ||||
|     private Logger logger = LoggerFactory.getLogger(getClass()); | ||||
|  | ||||
|     private Spec<A, O, C> spec; | ||||
|  | ||||
|     CommandConverter(Spec<A, O, C> spec) { | ||||
|         this.spec = spec; | ||||
|     } | ||||
|      | ||||
|     public A convert(SlashCommandDefinition def) { | ||||
|         logger.debug("Converting command {}", def); | ||||
|         List<O> 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) { | ||||
|         logger.debug("Converting subCommandGroup {} of {}", identifier, def); | ||||
|         SlashCommandIdentifier[] subCommands = def.getSubCommands(identifier.name()); | ||||
|         List<O> convertedSubCommands = Arrays.stream(subCommands).map(this::convertSubCommand).toList(); | ||||
|         return spec.convertSubCommandGroup(identifier, convertedSubCommands); | ||||
|     } | ||||
|  | ||||
|     private O convertSubCommand(SlashCommandIdentifier identifier) { | ||||
|         logger.debug("Converting subCommand {}", identifier); | ||||
|         List<O> options = Arrays.stream(identifier.options()).map(this::convertOption).toList(); | ||||
|         return spec.convertSubCommand(identifier, options); | ||||
|     } | ||||
|  | ||||
|     private O convertOption(SlashCommandOption option) { | ||||
|         logger.debug("Converting option {}", option); | ||||
|         List<C> choices = Arrays.stream(SlashCommandDefinition.getActualChoices(option)).map(spec::convertChoice).toList(); | ||||
|         return spec.convertOption(option, choices); | ||||
|     } | ||||
|  | ||||
|     public static interface Spec<A extends Object, O extends Object, C extends Object> { | ||||
|  | ||||
|         public A convertCommand(RootCommandIdentifier rootIdentifier, List<O>  options); | ||||
|         public O convertSubCommandGroup(InteractionIdentifier identifier, List<O> subCommands); | ||||
|         public O convertSubCommand(InteractionIdentifier identifier, List<O>  options); | ||||
|         public O convertOption(SlashCommandOption option, List<C> choices); | ||||
|         public C convertChoice(SlashCommandOptionChoice choice); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,55 @@ | ||||
| package net.tomatentum.marinara.wrapper; | ||||
|  | ||||
| import java.util.Arrays; | ||||
| import java.util.Set; | ||||
| import java.util.stream.Collectors; | ||||
|  | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
|  | ||||
| import net.tomatentum.marinara.interaction.commands.SlashCommandDefinition; | ||||
| import net.tomatentum.marinara.util.ObjectAggregator; | ||||
|  | ||||
| public class CommandRegisterer<A extends Object> { | ||||
|  | ||||
|     public static <A extends Object> CommandRegisterer<A> of(Strategy<A> strategy, CommandConverter<A, ?, ?> converter) { | ||||
|         return new CommandRegisterer<A>(strategy, converter); | ||||
|     } | ||||
|  | ||||
|     private Logger logger = LoggerFactory.getLogger(getClass()); | ||||
|  | ||||
|     private Strategy<A> strategy; | ||||
|     private CommandConverter<A, ?, ?> converter; | ||||
|  | ||||
|     CommandRegisterer(Strategy<A> strategy, CommandConverter<A, ?, ?> converter) { | ||||
|         this.strategy = strategy; | ||||
|         this.converter = converter; | ||||
|     } | ||||
|  | ||||
|     public void register(SlashCommandDefinition[] slashDefs) { | ||||
|         Set<ServerCommandList<A>> serverCommands = new ObjectAggregator<SlashCommandDefinition, Long, ServerCommandList<A>>( | ||||
|                 def -> Arrays.stream(def.serverIds()).boxed().toList(), | ||||
|                 (l, o) -> { | ||||
|                     logger.debug("Added {} for server ({}) registration.", o.rootIdentifier(), l.serverId()); | ||||
|                     l.add(converter.convert(o)); | ||||
|                 }, | ||||
|                 ServerCommandList::new) | ||||
|             .aggregate(Arrays.asList(slashDefs)).stream() | ||||
|             .collect(Collectors.toSet()); | ||||
|  | ||||
|         Set<A> globalCommands = Arrays.stream(slashDefs) | ||||
|             .filter(x -> x.serverIds().length <= 0) | ||||
|             .peek(c -> logger.debug("Added {} for global registration.", c.rootIdentifier())) | ||||
|             .map(converter::convert) | ||||
|             .collect(Collectors.toSet()); | ||||
|  | ||||
|         serverCommands.forEach(strategy::registerServer); | ||||
|         strategy.registerGlobal(globalCommands); | ||||
|         logger.info("Registered all SlashCommands"); | ||||
|     } | ||||
|      | ||||
|     public interface Strategy<A extends Object> { | ||||
|         void registerServer(ServerCommandList<A> commands); | ||||
|         void registerGlobal(Set<A> defs); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,9 @@ | ||||
| package net.tomatentum.marinara.wrapper; | ||||
|  | ||||
| import net.tomatentum.marinara.structure.data.ButtonStructureData; | ||||
|  | ||||
| public interface ComponentStructureConverter<B extends Object> { | ||||
|  | ||||
|     B convertButton(ButtonStructureData data); | ||||
|      | ||||
| } | ||||
| @@ -1,5 +1,7 @@ | ||||
| package net.tomatentum.marinara.wrapper; | ||||
|  | ||||
| import net.tomatentum.marinara.interaction.commands.option.AutocompleteOptionData; | ||||
|  | ||||
| public interface ContextObjectProvider { | ||||
|  | ||||
|     public Object convertCommandOption(Object context, String optionName); | ||||
| @@ -7,5 +9,5 @@ public interface ContextObjectProvider { | ||||
|     public Object getComponentContextObject(Object context, Class<?> type); | ||||
|     public Object getInteractionContextObject(Object context, Class<?> type); | ||||
|  | ||||
|     public Object getAutocompleteFocusedOption(Object context); | ||||
|     public AutocompleteOptionData getAutocompleteFocusedOption(Object context); | ||||
| } | ||||
|   | ||||
| @@ -0,0 +1,75 @@ | ||||
| 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.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
|  | ||||
| import io.leangen.geantyref.GenericTypeReflector; | ||||
| import net.tomatentum.cutin.util.ReflectionUtil; | ||||
| import net.tomatentum.marinara.interaction.ident.InteractionIdentifier; | ||||
|  | ||||
| public class IdentifierProvider { | ||||
|  | ||||
|     public static IdentifierProvider of(Converter<?>... converter) { | ||||
|         return new IdentifierProvider(Arrays.asList(converter)); | ||||
|     } | ||||
|  | ||||
|     private Logger logger = LoggerFactory.getLogger(getClass()); | ||||
|  | ||||
|     private Map<Class<?>, Converter<?>> converter; | ||||
|  | ||||
|     private IdentifierProvider(List<Converter<?>> 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 = ((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()); | ||||
|  | ||||
|         @SuppressWarnings("unchecked") | ||||
|         Converter<Object> conv = (Converter<Object>) converter.get(type); | ||||
|  | ||||
|         InteractionIdentifier result = conv.convert(context); | ||||
|         logger.trace("Converted {} to {} using {}", context, result, conv); | ||||
|         return result; | ||||
|     } | ||||
|  | ||||
|     @FunctionalInterface | ||||
|     public interface Converter<T extends Object> { | ||||
|         InteractionIdentifier convert(T context); | ||||
|     } | ||||
|  | ||||
|     public static class LambdaWrapper<T extends Object> implements Converter<T> { | ||||
|  | ||||
|         private Converter<T> converter; | ||||
|  | ||||
|         LambdaWrapper(Converter<T> converter) { | ||||
|             this.converter = converter; | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public InteractionIdentifier convert(T context) { | ||||
|             return this.converter.convert(context); | ||||
|         } | ||||
|  | ||||
|     } | ||||
| } | ||||
| @@ -3,21 +3,16 @@ 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<Consumer<Object>> interactionSubscriber; | ||||
|  | ||||
|     protected LibraryWrapper() { | ||||
|         interactionSubscriber = new ArrayList<>(); | ||||
|         this.interactionSubscriber = new ArrayList<>(); | ||||
|     } | ||||
|  | ||||
|     public void handleInteraction(Object context) { | ||||
|         interactionSubscriber.forEach((o) -> o.accept(context)); | ||||
|         interactionSubscriber.forEach(o -> o.accept(context)); | ||||
|     } | ||||
|  | ||||
|     public void subscribeInteractions(Consumer<Object> consumer) { | ||||
| @@ -27,13 +22,9 @@ 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(); | ||||
|     public abstract void respondAutocomplete(Object context, List<Object> options); | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,16 @@ | ||||
| package net.tomatentum.marinara.wrapper; | ||||
|  | ||||
| import java.util.HashSet; | ||||
|  | ||||
| public class ServerCommandList<A> extends HashSet<A>{ | ||||
|  | ||||
|     private long serverId; | ||||
|      | ||||
|     public ServerCommandList(long serverId) { | ||||
|         this.serverId = serverId; | ||||
|     } | ||||
|  | ||||
|     public long serverId() { | ||||
|         return serverId; | ||||
|     } | ||||
| } | ||||
| @@ -10,17 +10,16 @@ plugins { | ||||
|     `java-library` | ||||
| } | ||||
|  | ||||
| repositories { | ||||
|     // Use Maven Central for resolving dependencies. | ||||
|     mavenCentral() | ||||
| } | ||||
|  | ||||
| dependencies { | ||||
|     // Use JUnit Jupiter for testing. | ||||
|     testImplementation(libs.junit.jupiter) | ||||
|     testImplementation(libs.mockito) | ||||
|     testImplementation(libs.discord4j) | ||||
|  | ||||
|     testRuntimeOnly("org.junit.platform:junit-platform-launcher") | ||||
|     implementation(libs.log4j) | ||||
|     testImplementation(libs.slf4j.simple) | ||||
|  | ||||
|     implementation(libs.slf4j) | ||||
|     implementation(libs.discord4j) { | ||||
|         exclude(module="discord4j-voice") | ||||
|     } | ||||
|   | ||||
| @@ -5,7 +5,9 @@ import java.util.List; | ||||
| import discord4j.core.event.domain.interaction.ChatInputAutoCompleteEvent; | ||||
| import discord4j.core.event.domain.interaction.ChatInputInteractionEvent; | ||||
| import discord4j.core.event.domain.interaction.ComponentInteractionEvent; | ||||
| import discord4j.core.event.domain.interaction.InteractionCreateEvent; | ||||
| import discord4j.core.object.command.ApplicationCommandInteractionOption; | ||||
| import net.tomatentum.marinara.interaction.commands.option.AutocompleteOptionData; | ||||
| import net.tomatentum.marinara.interaction.commands.option.SlashCommandOptionType; | ||||
| import net.tomatentum.marinara.wrapper.ContextObjectProvider; | ||||
|  | ||||
| @@ -71,7 +73,7 @@ public class Discord4JContextObjectProvider implements ContextObjectProvider { | ||||
|         ComponentInteractionEvent componentInteractionEvent = (ComponentInteractionEvent) context; | ||||
|         switch (type.getName()) { | ||||
|             case "discord4j.core.object.entity.Message": | ||||
|                 return componentInteractionEvent.getMessage(); | ||||
|                 return componentInteractionEvent.getMessage().orElse(null); | ||||
|             default: | ||||
|                 return getInteractionContextObject(context, type); | ||||
|         } | ||||
| @@ -79,24 +81,25 @@ public class Discord4JContextObjectProvider implements ContextObjectProvider { | ||||
|  | ||||
|     @Override | ||||
|     public Object getInteractionContextObject(Object context, Class<?> type) { | ||||
|         ComponentInteractionEvent componentInteractionEvent = (ComponentInteractionEvent) context; | ||||
|         InteractionCreateEvent interactionEvent = (InteractionCreateEvent) context; | ||||
|         switch (type.getName()) { | ||||
|             case "discord4j.core.object.entity.channel.MessageChannel": | ||||
|                 return componentInteractionEvent.getInteraction().getChannel().block(); | ||||
|                 return interactionEvent.getInteraction().getChannel().block(); | ||||
|             case "discord4j.core.object.entity.Guild": | ||||
|                 return componentInteractionEvent.getInteraction().getGuild().block(); | ||||
|                 return interactionEvent.getInteraction().getGuild().block(); | ||||
|             case "discord4j.core.object.entity.Member": | ||||
|                 return componentInteractionEvent.getInteraction().getMember().orElse(null); | ||||
|                 return interactionEvent.getInteraction().getMember().orElse(null); | ||||
|             case "discord4j.core.object.entity.User": | ||||
|                 return componentInteractionEvent.getInteraction().getUser(); | ||||
|         } | ||||
|                 return interactionEvent.getInteraction().getUser(); | ||||
|             default: | ||||
|                 return null; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Object getAutocompleteFocusedOption(Object context) { | ||||
|         ChatInputAutoCompleteEvent interaction = (ChatInputAutoCompleteEvent) context; | ||||
|         return getOptionValue(interaction.getFocusedOption()); | ||||
|     public AutocompleteOptionData getAutocompleteFocusedOption(Object context) { | ||||
|         ApplicationCommandInteractionOption option = ((ChatInputAutoCompleteEvent) context).getFocusedOption(); | ||||
|         return new AutocompleteOptionData(option.getName(), getOptionValue(option)); | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -0,0 +1,79 @@ | ||||
| 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<ApplicationCommandRequest, ApplicationCommandOptionData, ApplicationCommandOptionChoiceData> { | ||||
|  | ||||
| 	@Override | ||||
| 	public ApplicationCommandRequest convertCommand(RootCommandIdentifier rootIdentifier, | ||||
| 			List<ApplicationCommandOptionData> options) { | ||||
|         return ApplicationCommandRequest.builder() | ||||
|             .name(rootIdentifier.name()) | ||||
|             .description(rootIdentifier.description()) | ||||
|             .options(options) | ||||
|             .build(); | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public ApplicationCommandOptionData convertSubCommandGroup(InteractionIdentifier identifier, | ||||
| 			List<ApplicationCommandOptionData> 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<ApplicationCommandOptionData> 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<ApplicationCommandOptionChoiceData> 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.autocompletes().length > 0) | ||||
| 			.minLength((int) option.range().min()) | ||||
| 			.minValue(option.range().min()) | ||||
| 			.maxLength((int)option.range().max()) | ||||
| 			.maxValue(option.range().max()) | ||||
|             .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(); | ||||
| 	} | ||||
| 	 | ||||
| } | ||||
| @@ -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<ApplicationCommandRequest> { | ||||
|  | ||||
|     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<ApplicationCommandRequest> commands) { | ||||
|         appService.bulkOverwriteGuildApplicationCommand(applicationId, commands.serverId(), new ArrayList<>(commands)); | ||||
|  | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void registerGlobal(Set<ApplicationCommandRequest> defs) { | ||||
|         appService.bulkOverwriteGlobalApplicationCommand(applicationId, new ArrayList<>(defs)); | ||||
|     } | ||||
| } | ||||
| @@ -1,196 +1,70 @@ | ||||
| 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 java.util.function.UnaryOperator; | ||||
|  | ||||
| import org.apache.logging.log4j.Logger; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
|  | ||||
| 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 reactor.core.publisher.Mono; | ||||
| 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 { | ||||
|  | ||||
|     public static final Function<List<ApplicationCommandInteractionOption>, List<ApplicationCommandInteractionOption>> SUB_FILTER = (i) -> | ||||
|     public static final UnaryOperator<List<ApplicationCommandInteractionOption>> SUB_FILTER = i -> | ||||
|         i.stream() | ||||
|             .filter(o -> o.getType().equals(Type.SUB_COMMAND) || o.getType().equals(Type.SUB_COMMAND_GROUP)) | ||||
|             .toList(); | ||||
|      | ||||
|     public static final Function<List<ApplicationCommandInteractionOption>, List<ApplicationCommandInteractionOption>> ARG_FILTER = (i) -> | ||||
|     public static final UnaryOperator<List<ApplicationCommandInteractionOption>> ARG_FILTER = i -> | ||||
|             i.stream() | ||||
|                 .filter(o -> !o.getType().equals(Type.SUB_COMMAND) && !o.getType().equals(Type.SUB_COMMAND_GROUP)) | ||||
|                 .toList(); | ||||
|  | ||||
|     private GatewayDiscordClient api; | ||||
|     private Discord4JContextObjectProvider contextObjectProvider; | ||||
|     private CommandRegisterer<ApplicationCommandRequest> commandRegisterer; | ||||
|  | ||||
|     private Logger logger = LoggerUtil.getLogger(getClass()); | ||||
|     private Logger logger = LoggerFactory.getLogger(getClass()); | ||||
|  | ||||
|     public Discord4JWrapper(GatewayDiscordClient api) { | ||||
|         this.api = api; | ||||
|         this.contextObjectProvider = new Discord4JContextObjectProvider(); | ||||
|         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)); | ||||
|         Mono.just("test").subscribe(logger::debug); | ||||
|         }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<Long, List<ApplicationCommandRequest>> serverCommands = new HashMap<>(); | ||||
|         List<ApplicationCommandRequest> 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) { | ||||
|         if (!(context instanceof ChatInputInteractionEvent)) | ||||
|             return null; | ||||
|  | ||||
|         ChatInputInteractionEvent interaction = (ChatInputInteractionEvent) context; | ||||
|         ExecutableSlashCommandDefinition.Builder builder = new ExecutableSlashCommandDefinition.Builder(); | ||||
|         List<ApplicationCommandInteractionOption> options = SUB_FILTER.apply(interaction.getOptions()); | ||||
|  | ||||
|         try { | ||||
|             builder.setApplicationCommand(TypeFactory.annotation(SlashCommand.class, Map.of("name", interaction.getCommandName()))); | ||||
|             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<ApplicationCommandOptionData> 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<ApplicationCommandOptionData> 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<ApplicationCommandOptionData> 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<ApplicationCommandOptionChoiceData> convertChoices(SlashCommandOption option) { | ||||
|         List<ApplicationCommandOptionChoiceData> 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 | ||||
| @@ -198,4 +72,15 @@ public class Discord4JWrapper extends LibraryWrapper { | ||||
|         return this.contextObjectProvider; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void respondAutocomplete(Object context, List<Object> options) { | ||||
|         if (context instanceof ChatInputAutoCompleteEvent event) { | ||||
|             List<ApplicationCommandOptionChoiceData> choices = options.stream() | ||||
|                 .filter(ApplicationCommandOptionChoiceData.class::isInstance) | ||||
|                 .map(o -> (ApplicationCommandOptionChoiceData)o) | ||||
|                 .toList(); | ||||
|             event.respondWithSuggestions(choices); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -0,0 +1,42 @@ | ||||
| package net.tomatentum.marinara.wrapper.discord4j.checks; | ||||
|  | ||||
| import java.lang.annotation.ElementType; | ||||
| import java.lang.annotation.Retention; | ||||
| import java.lang.annotation.RetentionPolicy; | ||||
| import java.lang.annotation.Target; | ||||
| import java.util.Optional; | ||||
|  | ||||
| import discord4j.core.event.domain.interaction.InteractionCreateEvent; | ||||
| import discord4j.core.object.entity.Member; | ||||
| import discord4j.rest.util.Permission; | ||||
| import discord4j.rest.util.PermissionSet; | ||||
| import net.tomatentum.marinara.checks.InteractionCheck; | ||||
|  | ||||
| public class PermissionCheck implements InteractionCheck<PermissionCheck.HasPermission> { | ||||
|  | ||||
|     @Retention(RetentionPolicy.RUNTIME) | ||||
|     @Target(ElementType.METHOD) | ||||
|     public static @interface HasPermission { | ||||
|         public Permission[] value(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public boolean preExec(Object context, HasPermission annotation) { | ||||
|         throw new UnsupportedOperationException("Unimplemented method 'preExec'"); | ||||
|     } | ||||
|  | ||||
|     public boolean preExec(InteractionCreateEvent context, HasPermission annotation) { | ||||
|         Optional<Member> member = context.getInteraction().getMember(); | ||||
|         if (member.isEmpty()) | ||||
|             return false; | ||||
|         PermissionSet ownPerms = PermissionSet.of(annotation.value()); | ||||
|         PermissionSet permSet = member.get().getBasePermissions().block(); | ||||
|         return permSet.containsAll(ownPerms); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void postExec(Object context, HasPermission annotation) { | ||||
|          | ||||
|     } | ||||
|      | ||||
| } | ||||
| @@ -0,0 +1,40 @@ | ||||
| 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<ChatInputAutoCompleteEvent> { | ||||
|  | ||||
|     @Override | ||||
|     public InteractionIdentifier convert(ChatInputAutoCompleteEvent context) { | ||||
|         InteractionIdentifier last = InteractionIdentifier.builder() | ||||
|             .type(InteractionType.AUTOCOMPLETE) | ||||
|             .name(context.getCommandName()) | ||||
|             .build(); | ||||
|  | ||||
|         List<ApplicationCommandInteractionOption> options = Discord4JWrapper.SUB_FILTER.apply(context.getOptions()); | ||||
|         if (!options.isEmpty()) { | ||||
|             last = InteractionIdentifier.builder() | ||||
|                 .type(InteractionType.AUTOCOMPLETE) | ||||
|                 .name(options.getFirst().getName()) | ||||
|                 .parent(last) | ||||
|                 .build(); | ||||
|  | ||||
|             List<ApplicationCommandInteractionOption> subOptions = Discord4JWrapper.SUB_FILTER.apply(options.getFirst().getOptions()); | ||||
|             if (!subOptions.isEmpty()) | ||||
|                 last = InteractionIdentifier.builder() | ||||
|                     .type(InteractionType.AUTOCOMPLETE) | ||||
|                     .name(subOptions.getFirst().getName()) | ||||
|                     .parent(last) | ||||
|                     .build(); | ||||
|         } | ||||
|         return last; | ||||
|     } | ||||
|      | ||||
| } | ||||
| @@ -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<ButtonInteractionEvent> { | ||||
|  | ||||
|     @Override | ||||
|     public InteractionIdentifier convert(ButtonInteractionEvent context) { | ||||
|         return InteractionIdentifier.builder().name(context.getCustomId()).type(InteractionType.BUTTON).build(); | ||||
|     } | ||||
|      | ||||
| } | ||||
| @@ -0,0 +1,40 @@ | ||||
| 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<ChatInputInteractionEvent> { | ||||
|  | ||||
|     @Override | ||||
|     public InteractionIdentifier convert(ChatInputInteractionEvent context) { | ||||
|         InteractionIdentifier last = InteractionIdentifier.builder() | ||||
|             .type(InteractionType.COMMAND) | ||||
|             .name(context.getCommandName()) | ||||
|             .build(); | ||||
|  | ||||
|         List<ApplicationCommandInteractionOption> options = Discord4JWrapper.SUB_FILTER.apply(context.getOptions()); | ||||
|         if (!options.isEmpty()) { | ||||
|             last = InteractionIdentifier.builder() | ||||
|                 .type(InteractionType.COMMAND) | ||||
|                 .name(options.getFirst().getName()) | ||||
|                 .parent(last) | ||||
|                 .build(); | ||||
|  | ||||
|             List<ApplicationCommandInteractionOption> subOptions = Discord4JWrapper.SUB_FILTER.apply(options.getFirst().getOptions()); | ||||
|             if (!subOptions.isEmpty()) | ||||
|                 last = InteractionIdentifier.builder() | ||||
|                     .type(InteractionType.COMMAND) | ||||
|                     .name(subOptions.getFirst().getName()) | ||||
|                     .parent(last) | ||||
|                     .build(); | ||||
|         } | ||||
|         return last; | ||||
|     } | ||||
|      | ||||
| } | ||||
| @@ -1,23 +1,45 @@ | ||||
| package net.tomatentum.marinara.test.discord4j; | ||||
|  | ||||
| import static org.junit.jupiter.api.Assertions.assertTrue; | ||||
| import static org.mockito.ArgumentMatchers.any; | ||||
| import static org.mockito.Mockito.mock; | ||||
| import static org.mockito.Mockito.verify; | ||||
| import static org.mockito.Mockito.when; | ||||
|  | ||||
| import java.util.ArrayList; | ||||
| import java.util.Optional; | ||||
|  | ||||
| import org.junit.jupiter.api.Test; | ||||
|  | ||||
| import discord4j.core.event.domain.interaction.ChatInputAutoCompleteEvent; | ||||
| import discord4j.core.object.command.ApplicationCommandInteractionOption; | ||||
| import discord4j.core.object.command.ApplicationCommandInteractionOptionValue; | ||||
| import discord4j.core.object.command.ApplicationCommandOption.Type; | ||||
| import net.tomatentum.marinara.Marinara; | ||||
| import net.tomatentum.marinara.test.discord4j.mocks.AutocompleteInteractionMock; | ||||
| import net.tomatentum.marinara.test.discord4j.mocks.DiscordApiMock; | ||||
| import net.tomatentum.marinara.wrapper.LibraryWrapper; | ||||
| import net.tomatentum.marinara.wrapper.javacord.JavacordWrapper; | ||||
| import net.tomatentum.marinara.wrapper.discord4j.Discord4JWrapper; | ||||
|  | ||||
| public class AutoCompleteTest { | ||||
| class AutoCompleteTest { | ||||
|      | ||||
|     @Test | ||||
|     public void testAutocomplete() { | ||||
|         LibraryWrapper wrapper = new JavacordWrapper(new DiscordApiMock()); //null okay as we don't use the discord API in this test. | ||||
|     void testAutocomplete() { | ||||
|         ApplicationCommandInteractionOption optionMock = mock(); | ||||
|         ChatInputAutoCompleteEvent autoCompleteEventMock = mock(); | ||||
|  | ||||
|         when(optionMock.getName()).thenReturn("foo"); | ||||
|         when(optionMock.getType()).thenReturn(Type.STRING); | ||||
|         when(optionMock.getValue()).thenReturn( | ||||
|             Optional.of( | ||||
|                 new ApplicationCommandInteractionOptionValue(null, null, Type.STRING.getValue(), "test", null) | ||||
|                 )); | ||||
|  | ||||
|         when(autoCompleteEventMock.getCommandName()).thenReturn("test"); | ||||
|         when(autoCompleteEventMock.getOptions()).thenReturn(new ArrayList<>()); | ||||
|         when(autoCompleteEventMock.getFocusedOption()).thenReturn(optionMock); | ||||
|  | ||||
|         LibraryWrapper wrapper = new Discord4JWrapper(null); //null okay as we don't use the discord API in this test. | ||||
|         Marinara marinara = Marinara.load(wrapper); | ||||
|         marinara.getRegistry().addInteractions(new TestAutocomplete()); | ||||
|         wrapper.handleInteraction(new AutocompleteInteractionMock()); | ||||
|         assertTrue(AutocompleteInteractionMock.didAutocompleteRun); | ||||
|         marinara.getInteractionContainer().addAllMethods(new TestAutocomplete()); | ||||
|         wrapper.handleInteraction(autoCompleteEventMock); | ||||
|         verify(autoCompleteEventMock).respondWithSuggestions(any()); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -6,21 +6,23 @@ import org.junit.jupiter.api.Test; | ||||
| import org.junit.jupiter.api.TestInstance; | ||||
| import org.junit.jupiter.api.TestInstance.Lifecycle; | ||||
|  | ||||
| import discord4j.core.event.domain.interaction.ButtonInteractionEvent; | ||||
| import net.tomatentum.marinara.Marinara; | ||||
| import net.tomatentum.marinara.test.discord4j.mocks.ButtonInteractionMock; | ||||
| import net.tomatentum.marinara.test.discord4j.mocks.DiscordApiMock; | ||||
| import net.tomatentum.marinara.test.discord4j.mocks.CommonMocks; | ||||
| import net.tomatentum.marinara.wrapper.LibraryWrapper; | ||||
| import net.tomatentum.marinara.wrapper.javacord.JavacordWrapper; | ||||
| import net.tomatentum.marinara.wrapper.discord4j.Discord4JWrapper; | ||||
|  | ||||
| @TestInstance(Lifecycle.PER_CLASS) | ||||
| public class ButtonTest { | ||||
| class ButtonTest { | ||||
|  | ||||
|     @Test | ||||
|     public void testButtonExecution() { | ||||
|         LibraryWrapper wrapper = new JavacordWrapper(new DiscordApiMock()); //null okay as we don't use the discord API in this test. | ||||
|     void testButtonExecution() { | ||||
|         ButtonInteractionEvent buttonEventMock = CommonMocks.getButtonEventMock("test"); | ||||
|  | ||||
|         LibraryWrapper wrapper = new Discord4JWrapper(null); //null okay as we don't use the discord API in this test. | ||||
|         Marinara marinara = Marinara.load(wrapper); | ||||
|         marinara.getRegistry().addInteractions(new TestButton()); | ||||
|         wrapper.handleInteraction(new ButtonInteractionMock("test")); | ||||
|         marinara.getInteractionContainer().addAllMethods(new TestButton()); | ||||
|         wrapper.handleInteraction(buttonEventMock); | ||||
|         assertTrue(TestButton.didRun); | ||||
|     } | ||||
|      | ||||
|   | ||||
| @@ -2,46 +2,69 @@ package net.tomatentum.marinara.test.discord4j; | ||||
|  | ||||
| import static org.junit.jupiter.api.Assertions.assertFalse; | ||||
| import static org.junit.jupiter.api.Assertions.assertTrue; | ||||
| import static org.mockito.Mockito.mock; | ||||
| import static org.mockito.Mockito.when; | ||||
|  | ||||
| import java.util.Optional; | ||||
|  | ||||
| import org.javacord.api.entity.permission.PermissionType; | ||||
| import org.junit.jupiter.api.Test; | ||||
| import org.junit.jupiter.api.TestInstance; | ||||
| import org.junit.jupiter.api.TestInstance.Lifecycle; | ||||
|  | ||||
| import discord4j.core.event.domain.interaction.ButtonInteractionEvent; | ||||
| import discord4j.core.object.command.Interaction; | ||||
| import discord4j.core.object.entity.Member; | ||||
| import discord4j.rest.util.Permission; | ||||
| import discord4j.rest.util.PermissionSet; | ||||
| import net.tomatentum.marinara.Marinara; | ||||
| import net.tomatentum.marinara.test.discord4j.mocks.ButtonInteractionMock; | ||||
| import net.tomatentum.marinara.test.discord4j.mocks.DiscordApiMock; | ||||
| import net.tomatentum.marinara.test.discord4j.mocks.ServerMock; | ||||
| import net.tomatentum.marinara.test.discord4j.mocks.CommonMocks; | ||||
| import net.tomatentum.marinara.wrapper.LibraryWrapper; | ||||
| import net.tomatentum.marinara.wrapper.javacord.JavacordWrapper; | ||||
| import net.tomatentum.marinara.wrapper.javacord.checks.PermissionCheck; | ||||
| import net.tomatentum.marinara.wrapper.discord4j.Discord4JWrapper; | ||||
| import net.tomatentum.marinara.wrapper.discord4j.checks.PermissionCheck; | ||||
| import reactor.core.publisher.Mono; | ||||
|  | ||||
| @TestInstance(Lifecycle.PER_CLASS) | ||||
| public class InteractionCheckTest { | ||||
| class InteractionCheckTest { | ||||
|      | ||||
|     @Test | ||||
|     public void testInteractionCheck() { | ||||
|         LibraryWrapper wrapper = new JavacordWrapper(new DiscordApiMock()); | ||||
|     void testInteractionCheck() { | ||||
|         ButtonInteractionEvent buttonEventMock = CommonMocks.getButtonEventMock("test"); | ||||
|  | ||||
|         LibraryWrapper wrapper = new Discord4JWrapper(null); | ||||
|         Marinara marinara = Marinara.load(wrapper); | ||||
|         marinara.getCheckRegistry().addCheck(new TestInteractionCheck()); | ||||
|         marinara.getRegistry().addInteractions(new TestButton()); | ||||
|         wrapper.handleInteraction(new ButtonInteractionMock("test")); | ||||
|         marinara.getCheckContainer().addAllMethods(new TestInteractionCheck()); | ||||
|         marinara.getInteractionContainer().addAllMethods(new TestButton()); | ||||
|         wrapper.handleInteraction(buttonEventMock); | ||||
|  | ||||
|         assertTrue(TestInteractionCheck.preExecuted); | ||||
|         assertTrue(TestInteractionCheck.postExecuted); | ||||
|         assertTrue(TestButton.didRun); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     public void testPermissionCheck() { | ||||
|         LibraryWrapper wrapper = new JavacordWrapper(new DiscordApiMock()); | ||||
|     void testPermissionCheck() { | ||||
|         Member memberMock = mock(); | ||||
|         Interaction interactionMock = mock(); | ||||
|  | ||||
|         when(memberMock.getBasePermissions()).thenReturn(Mono.just(PermissionSet.none())); | ||||
|  | ||||
|         when(interactionMock.getMember()).thenReturn(Optional.of(memberMock)); | ||||
|  | ||||
|         ButtonInteractionEvent buttonEventMock = CommonMocks.getButtonEventMock("permissionCheck", interactionMock); | ||||
|  | ||||
|         LibraryWrapper wrapper = new Discord4JWrapper(null); | ||||
|         Marinara marinara = Marinara.load(wrapper); | ||||
|         marinara.getCheckRegistry().addCheck(new PermissionCheck()); | ||||
|         marinara.getRegistry().addInteractions(new TestButton()); | ||||
|         wrapper.handleInteraction(new ButtonInteractionMock("permissionCheck")); | ||||
|         assertTrue(TestButton.didPermRun); | ||||
|         TestButton.didPermRun = false; | ||||
|         ServerMock.TESTPERMISSION = PermissionType.ATTACH_FILE; | ||||
|         wrapper.handleInteraction(new ButtonInteractionMock("permissionCheck")); | ||||
|         marinara.getCheckContainer().addAllMethods(new PermissionCheck()); | ||||
|         marinara.getInteractionContainer().addAllMethods(new TestButton()); | ||||
|  | ||||
|         wrapper.handleInteraction(buttonEventMock); | ||||
|         assertFalse(TestButton.didPermRun); | ||||
|         TestButton.didPermRun = false; | ||||
|          | ||||
|         when(memberMock.getBasePermissions()).thenReturn(Mono.just(PermissionSet.of(Permission.ATTACH_FILES))); | ||||
|  | ||||
|         wrapper.handleInteraction(buttonEventMock); | ||||
|         assertTrue(TestButton.didPermRun); | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -1,51 +1,73 @@ | ||||
| package net.tomatentum.marinara.test.discord4j; | ||||
|  | ||||
| import org.javacord.api.DiscordApi; | ||||
| import org.javacord.api.DiscordApiBuilder; | ||||
| import static org.mockito.Mockito.mock; | ||||
| import static org.mockito.Mockito.when; | ||||
|  | ||||
| import java.util.Arrays; | ||||
| import java.util.Optional; | ||||
|  | ||||
| import org.junit.jupiter.api.AfterAll; | ||||
| import org.junit.jupiter.api.BeforeAll; | ||||
| import org.junit.jupiter.api.Test; | ||||
| import org.junit.jupiter.api.TestInstance; | ||||
| import org.junit.jupiter.api.TestInstance.Lifecycle; | ||||
|  | ||||
| import discord4j.core.DiscordClient; | ||||
| import discord4j.core.GatewayDiscordClient; | ||||
| import discord4j.core.event.domain.interaction.ChatInputInteractionEvent; | ||||
| import discord4j.core.object.command.ApplicationCommandInteractionOption; | ||||
| import discord4j.core.object.command.ApplicationCommandInteractionOptionValue; | ||||
| import discord4j.core.object.command.ApplicationCommandOption.Type; | ||||
| import net.tomatentum.marinara.Marinara; | ||||
| import net.tomatentum.marinara.test.discord4j.mocks.SlashCommandInteractionMock; | ||||
| import net.tomatentum.marinara.wrapper.LibraryWrapper; | ||||
| import net.tomatentum.marinara.wrapper.javacord.JavacordWrapper; | ||||
| @TestInstance(Lifecycle.PER_CLASS) | ||||
| public class SlashCommandTest { | ||||
| import net.tomatentum.marinara.wrapper.discord4j.Discord4JWrapper; | ||||
|  | ||||
|     String DISCORD_TOKEN = System.getenv("DISCORD_TEST_TOKEN"); | ||||
|     DiscordApi api; | ||||
| @TestInstance(Lifecycle.PER_CLASS) | ||||
| class SlashCommandTest { | ||||
|  | ||||
|     private static String DISCORD_TOKEN = System.getenv("DISCORD_TEST_TOKEN"); | ||||
|     GatewayDiscordClient client; | ||||
|  | ||||
|     @BeforeAll | ||||
|     void setUp() { | ||||
|         api = new DiscordApiBuilder() | ||||
|         .setToken(DISCORD_TOKEN) | ||||
|         .login().join(); | ||||
|         client = DiscordClient.create(DISCORD_TOKEN).login().block(); | ||||
|     } | ||||
|  | ||||
|     @AfterAll | ||||
|     void tearDown() { | ||||
|         api.disconnect(); | ||||
|         api = null; | ||||
|         client.logout().block(); | ||||
|         client = null; | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     void testSlashCommand() { | ||||
|         Marinara marinara = Marinara.load(new JavacordWrapper(api)); | ||||
|         marinara.getRegistry().addInteractions(new TestCommand()); | ||||
|         marinara.getRegistry().registerCommands(); | ||||
|         Marinara marinara = Marinara.load(new Discord4JWrapper(client)); | ||||
|         marinara.getInteractionContainer().addAllMethods(new TestCommand()); | ||||
|         marinara.registerCommands(); | ||||
|         System.out.println("Success!"); | ||||
|     } | ||||
|      | ||||
|     @Test | ||||
|     void testSlashCommandExecution() { | ||||
|         LibraryWrapper wrapper = new JavacordWrapper(api); | ||||
|         ApplicationCommandInteractionOption optionMock = mock(); | ||||
|         ChatInputInteractionEvent eventMock = mock(); | ||||
|  | ||||
|         when(optionMock.getName()).thenReturn("foo"); | ||||
|         when(optionMock.getType()).thenReturn(Type.STRING); | ||||
|         when(optionMock.getValue()).thenReturn( | ||||
|             Optional.of( | ||||
|                 new ApplicationCommandInteractionOptionValue(null, null, Type.STRING.getValue(), "test", null) | ||||
|                 )); | ||||
|  | ||||
|         when(eventMock.getCommandName()).thenReturn("test"); | ||||
|         when(eventMock.getOptions()).thenReturn(Arrays.asList(optionMock)); | ||||
|         when(eventMock.getOption("foo")).thenReturn(Optional.of(optionMock)); | ||||
|          | ||||
|         LibraryWrapper wrapper = new Discord4JWrapper(client); | ||||
|         Marinara marinara = Marinara.load(wrapper); | ||||
|         marinara.getRegistry().addInteractions(new TestCommand()); | ||||
|         marinara.getInteractionContainer().addAllMethods(new TestCommand()); | ||||
|  | ||||
|         wrapper.handleInteraction(new SlashCommandInteractionMock()); | ||||
|         wrapper.handleInteraction(eventMock); | ||||
|     } | ||||
|  | ||||
|      | ||||
|   | ||||
| @@ -2,22 +2,36 @@ package net.tomatentum.marinara.test.discord4j; | ||||
|  | ||||
| import static org.junit.jupiter.api.Assertions.assertEquals; | ||||
|  | ||||
| import java.util.Collections; | ||||
|  | ||||
| import org.javacord.api.interaction.AutocompleteInteraction; | ||||
|  | ||||
| import discord4j.core.event.domain.interaction.ChatInputAutoCompleteEvent; | ||||
| import discord4j.core.event.domain.interaction.ChatInputInteractionEvent; | ||||
| import discord4j.discordjson.json.ApplicationCommandOptionChoiceData; | ||||
| import net.tomatentum.marinara.interaction.InteractionHandler; | ||||
| import net.tomatentum.marinara.interaction.annotation.AutoComplete; | ||||
| import net.tomatentum.marinara.interaction.commands.annotation.SlashCommand; | ||||
| import net.tomatentum.marinara.interaction.commands.annotation.SlashCommandOption; | ||||
| import net.tomatentum.marinara.interaction.commands.option.SlashCommandOptionType; | ||||
|  | ||||
| public class TestAutocomplete implements InteractionHandler { | ||||
|  | ||||
|     @SlashCommand(name = "test") | ||||
|     @AutoComplete | ||||
|     public void autocomplete(AutocompleteInteraction context, String value) { | ||||
|     @SlashCommand( | ||||
|         name = "test", | ||||
|         options = @SlashCommandOption( | ||||
|                 name = "foo", | ||||
|                 type = SlashCommandOptionType.STRING, | ||||
|                 autocompletes = @AutoComplete("testAuto") | ||||
|             ) | ||||
|         ) | ||||
|     public void exec(ChatInputInteractionEvent context) { | ||||
|         // Not executed just there for autocomplete to work | ||||
|     } | ||||
|      | ||||
|     @AutoComplete("testAuto") | ||||
|     public ApplicationCommandOptionChoiceData[] autocomplete(ChatInputAutoCompleteEvent context, String value) { | ||||
|         System.out.println("Success!"); | ||||
|         assertEquals(value, "test"); | ||||
|         context.respondWithChoices(Collections.emptyList()); | ||||
|         assertEquals("test", value); | ||||
|         return new ApplicationCommandOptionChoiceData[]{ | ||||
|             ApplicationCommandOptionChoiceData.builder().name("TestValue").value("test").build() | ||||
|         }; | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -2,17 +2,17 @@ package net.tomatentum.marinara.test.discord4j; | ||||
|  | ||||
| import static org.junit.jupiter.api.Assertions.assertNotNull; | ||||
|  | ||||
| import org.javacord.api.entity.channel.TextChannel; | ||||
| import org.javacord.api.entity.message.Message; | ||||
| import org.javacord.api.entity.permission.PermissionType; | ||||
| import org.javacord.api.entity.server.Server; | ||||
| import org.javacord.api.entity.user.User; | ||||
| import org.javacord.api.interaction.ButtonInteraction; | ||||
|  | ||||
| import discord4j.core.event.domain.interaction.ButtonInteractionEvent; | ||||
| import discord4j.core.object.entity.Guild; | ||||
| import discord4j.core.object.entity.Member; | ||||
| import discord4j.core.object.entity.Message; | ||||
| import discord4j.core.object.entity.User; | ||||
| import discord4j.core.object.entity.channel.MessageChannel; | ||||
| import discord4j.rest.util.Permission; | ||||
| import net.tomatentum.marinara.interaction.InteractionHandler; | ||||
| import net.tomatentum.marinara.interaction.annotation.Button; | ||||
| import net.tomatentum.marinara.test.discord4j.TestInteractionCheck.TestCheck; | ||||
| import net.tomatentum.marinara.wrapper.javacord.checks.PermissionCheck.HasPermission; | ||||
| import net.tomatentum.marinara.wrapper.discord4j.checks.PermissionCheck.HasPermission; | ||||
|  | ||||
| public class TestButton implements InteractionHandler { | ||||
|  | ||||
| @@ -20,11 +20,12 @@ public class TestButton implements InteractionHandler { | ||||
|     public static boolean didRun = false; | ||||
|     @Button("test") | ||||
|     @TestCheck | ||||
|     public void exec(ButtonInteraction interaction, TextChannel channel, Message message, User member, Server server) { | ||||
|     public void exec(ButtonInteractionEvent interaction, MessageChannel channel, Message message, Member member, User user, Guild server) { | ||||
|         assertNotNull(interaction); | ||||
|         assertNotNull(channel); | ||||
|         assertNotNull(message); | ||||
|         assertNotNull(member); | ||||
|         assertNotNull(user); | ||||
|         assertNotNull(server); | ||||
|         didRun = true; | ||||
|         System.out.println("Success!"); | ||||
| @@ -33,8 +34,8 @@ public class TestButton implements InteractionHandler { | ||||
|     public static boolean didPermRun = false; | ||||
|  | ||||
|     @Button("permissionCheck") | ||||
|     @HasPermission({PermissionType.ADMINISTRATOR}) | ||||
|     public void exec(ButtonInteraction interaction) { | ||||
|     @HasPermission({Permission.ATTACH_FILES}) | ||||
|     public void exec(ButtonInteractionEvent interaction) { | ||||
|         didPermRun = true; | ||||
|         System.out.println("It worked!"); | ||||
|     } | ||||
|   | ||||
| @@ -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<String> { | ||||
|     TestValue("testValue"), | ||||
|   | ||||
| @@ -2,9 +2,9 @@ package net.tomatentum.marinara.test.discord4j; | ||||
|  | ||||
| import static org.junit.jupiter.api.Assertions.assertEquals; | ||||
|  | ||||
| import org.javacord.api.interaction.SlashCommandInteraction; | ||||
|  | ||||
| import discord4j.core.event.domain.interaction.ChatInputInteractionEvent; | ||||
| import net.tomatentum.marinara.interaction.InteractionHandler; | ||||
| import net.tomatentum.marinara.interaction.commands.annotation.CommandChoices; | ||||
| import net.tomatentum.marinara.interaction.commands.annotation.SlashCommand; | ||||
| import net.tomatentum.marinara.interaction.commands.annotation.SlashCommandOption; | ||||
| import net.tomatentum.marinara.interaction.commands.option.SlashCommandOptionType; | ||||
| @@ -21,12 +21,12 @@ public class TestCommand implements InteractionHandler { | ||||
|                 name = "foo", | ||||
|                 description = "foo bar is very fooby", | ||||
|                 type = SlashCommandOptionType.STRING, | ||||
|                 choiceEnum = TestChoiceEnum.class | ||||
|                 choices = @CommandChoices(cenum = TestChoiceEnum.class) | ||||
|             ) | ||||
|         } | ||||
|         ) | ||||
|     public void exec(SlashCommandInteraction interaction, String test) { | ||||
|         assertEquals(test, "test"); | ||||
|     public void exec(ChatInputInteractionEvent event, String test) { | ||||
|         assertEquals("test", test); | ||||
|         System.out.println("Success!"); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,17 +0,0 @@ | ||||
| package net.tomatentum.marinara.test.discord4j.mocks; | ||||
|  | ||||
| import discord4j.core.GatewayDiscordClient; | ||||
| import discord4j.core.event.domain.interaction.ChatInputAutoCompleteEvent; | ||||
| import discord4j.core.object.command.Interaction; | ||||
| import discord4j.gateway.ShardInfo; | ||||
|  | ||||
| public class AutocompleteInteractionMock extends ChatInputAutoCompleteEvent { | ||||
|  | ||||
|     public AutocompleteInteractionMock(GatewayDiscordClient gateway, ShardInfo shardInfo, Interaction interaction) { | ||||
|         super(gateway, shardInfo, interaction); | ||||
|         //TODO Auto-generated constructor stub | ||||
|     } | ||||
|  | ||||
|     public static boolean didAutocompleteRun = false; | ||||
|      | ||||
| } | ||||
| @@ -1,25 +0,0 @@ | ||||
| package net.tomatentum.marinara.test.discord4j.mocks; | ||||
|  | ||||
| import discord4j.core.event.domain.interaction.ButtonInteractionEvent; | ||||
| import discord4j.core.object.command.Interaction; | ||||
|  | ||||
| public class ButtonInteractionMock extends ButtonInteractionEvent { | ||||
|  | ||||
|     private String customId; | ||||
|      | ||||
|     public ButtonInteractionMock(String customId) { | ||||
|         super(null, null, null); | ||||
|         this.customId = customId; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public String getCustomId() { | ||||
|         return customId; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Interaction getInteraction() { | ||||
|         return new InteractionMock(); | ||||
|     } | ||||
|       | ||||
| } | ||||
| @@ -1,130 +0,0 @@ | ||||
| package net.tomatentum.marinara.test.discord4j.mocks; | ||||
|  | ||||
| import java.time.Instant; | ||||
| import java.util.Optional; | ||||
| import java.util.function.Consumer; | ||||
|  | ||||
| import org.reactivestreams.Publisher; | ||||
|  | ||||
| import discord4j.common.util.Snowflake; | ||||
| import discord4j.core.GatewayDiscordClient; | ||||
| import discord4j.core.object.entity.Message; | ||||
| import discord4j.core.object.entity.channel.MessageChannel; | ||||
| import discord4j.core.retriever.EntityRetrievalStrategy; | ||||
| import discord4j.core.spec.MessageCreateSpec; | ||||
| import discord4j.core.spec.legacy.LegacyMessageCreateSpec; | ||||
| import discord4j.rest.entity.RestChannel; | ||||
| import reactor.core.publisher.Flux; | ||||
| import reactor.core.publisher.Mono; | ||||
|  | ||||
| public class ChannelMock implements MessageChannel { | ||||
|  | ||||
|     @Override | ||||
|     public Type getType() { | ||||
|         // TODO Auto-generated method stub | ||||
|         throw new UnsupportedOperationException("Unimplemented method 'getType'"); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Mono<Void> delete(String reason) { | ||||
|         // TODO Auto-generated method stub | ||||
|         throw new UnsupportedOperationException("Unimplemented method 'delete'"); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public RestChannel getRestChannel() { | ||||
|         // TODO Auto-generated method stub | ||||
|         throw new UnsupportedOperationException("Unimplemented method 'getRestChannel'"); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Snowflake getId() { | ||||
|         // TODO Auto-generated method stub | ||||
|         throw new UnsupportedOperationException("Unimplemented method 'getId'"); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public GatewayDiscordClient getClient() { | ||||
|         // TODO Auto-generated method stub | ||||
|         throw new UnsupportedOperationException("Unimplemented method 'getClient'"); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Optional<Snowflake> getLastMessageId() { | ||||
|         // TODO Auto-generated method stub | ||||
|         throw new UnsupportedOperationException("Unimplemented method 'getLastMessageId'"); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Mono<Message> getLastMessage() { | ||||
|         // TODO Auto-generated method stub | ||||
|         throw new UnsupportedOperationException("Unimplemented method 'getLastMessage'"); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Mono<Message> getLastMessage(EntityRetrievalStrategy retrievalStrategy) { | ||||
|         // TODO Auto-generated method stub | ||||
|         throw new UnsupportedOperationException("Unimplemented method 'getLastMessage'"); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Optional<Instant> getLastPinTimestamp() { | ||||
|         // TODO Auto-generated method stub | ||||
|         throw new UnsupportedOperationException("Unimplemented method 'getLastPinTimestamp'"); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Mono<Message> createMessage(Consumer<? super LegacyMessageCreateSpec> spec) { | ||||
|         // TODO Auto-generated method stub | ||||
|         throw new UnsupportedOperationException("Unimplemented method 'createMessage'"); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Mono<Message> createMessage(MessageCreateSpec spec) { | ||||
|         // TODO Auto-generated method stub | ||||
|         throw new UnsupportedOperationException("Unimplemented method 'createMessage'"); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Mono<Void> type() { | ||||
|         // TODO Auto-generated method stub | ||||
|         throw new UnsupportedOperationException("Unimplemented method 'type'"); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Flux<Long> typeUntil(Publisher<?> until) { | ||||
|         // TODO Auto-generated method stub | ||||
|         throw new UnsupportedOperationException("Unimplemented method 'typeUntil'"); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Flux<Message> getMessagesBefore(Snowflake messageId) { | ||||
|         // TODO Auto-generated method stub | ||||
|         throw new UnsupportedOperationException("Unimplemented method 'getMessagesBefore'"); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Flux<Message> getMessagesAfter(Snowflake messageId) { | ||||
|         // TODO Auto-generated method stub | ||||
|         throw new UnsupportedOperationException("Unimplemented method 'getMessagesAfter'"); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Mono<Message> getMessageById(Snowflake id) { | ||||
|         // TODO Auto-generated method stub | ||||
|         throw new UnsupportedOperationException("Unimplemented method 'getMessageById'"); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Mono<Message> getMessageById(Snowflake id, EntityRetrievalStrategy retrievalStrategy) { | ||||
|         // TODO Auto-generated method stub | ||||
|         throw new UnsupportedOperationException("Unimplemented method 'getMessageById'"); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Flux<Message> getPinnedMessages() { | ||||
|         // TODO Auto-generated method stub | ||||
|         throw new UnsupportedOperationException("Unimplemented method 'getPinnedMessages'"); | ||||
|     } | ||||
|      | ||||
| } | ||||
| @@ -0,0 +1,55 @@ | ||||
| package net.tomatentum.marinara.test.discord4j.mocks; | ||||
|  | ||||
| import static org.mockito.Mockito.mock; | ||||
| import static org.mockito.Mockito.when; | ||||
|  | ||||
| import java.util.Optional; | ||||
|  | ||||
| import discord4j.core.event.domain.interaction.ButtonInteractionEvent; | ||||
| import discord4j.core.object.command.Interaction; | ||||
| import discord4j.core.object.entity.Guild; | ||||
| import discord4j.core.object.entity.Member; | ||||
| import discord4j.core.object.entity.Message; | ||||
| import discord4j.core.object.entity.User; | ||||
| import discord4j.core.object.entity.channel.MessageChannel; | ||||
| import reactor.core.publisher.Mono; | ||||
|  | ||||
| public class CommonMocks { | ||||
|     public static Interaction getInteractionMock() { | ||||
|         Interaction interaction = mock(Interaction.class); | ||||
|         Message message = mock(Message.class); | ||||
|         MessageChannel channel = mock(MessageChannel.class); | ||||
|         Guild guild = mock(Guild.class); | ||||
|         User user = mock(User.class); | ||||
|         Member member = mock(Member.class); | ||||
|  | ||||
|  | ||||
|         when(interaction.getMessage()).thenReturn(Optional.of(message)); | ||||
|         when(interaction.getChannel()).thenReturn(Mono.just(channel)); | ||||
|         when(interaction.getGuild()).thenReturn(Mono.just(guild)); | ||||
|         when(interaction.getUser()).thenReturn(user); | ||||
|         when(interaction.getMember()).thenReturn(Optional.of(member)); | ||||
|  | ||||
|  | ||||
|         return interaction; | ||||
|     } | ||||
|  | ||||
|     public static ButtonInteractionEvent getButtonEventMock(String customId) { | ||||
|         ButtonInteractionEvent buttonEventMock = mock(ButtonInteractionEvent.class); | ||||
|  | ||||
|         when(buttonEventMock.getCustomId()).thenReturn(customId); | ||||
|         Interaction interactionMock = getInteractionMock(); | ||||
|         when(buttonEventMock.getInteraction()).thenReturn(interactionMock); | ||||
|         Optional<Message> message = interactionMock.getMessage(); | ||||
|         when (buttonEventMock.getMessage()).thenReturn(message); | ||||
|         return buttonEventMock; | ||||
|     } | ||||
|  | ||||
|     public static ButtonInteractionEvent getButtonEventMock(String customId, Interaction interaction) { | ||||
|         ButtonInteractionEvent buttonEventMock = mock(ButtonInteractionEvent.class); | ||||
|  | ||||
|         when(buttonEventMock.getCustomId()).thenReturn(customId); | ||||
|         when(buttonEventMock.getInteraction()).thenReturn(interaction); | ||||
|         return buttonEventMock; | ||||
|     } | ||||
| } | ||||
| @@ -1,41 +0,0 @@ | ||||
| package net.tomatentum.marinara.test.discord4j.mocks; | ||||
|  | ||||
| import java.util.Optional; | ||||
|  | ||||
| import discord4j.core.object.command.Interaction; | ||||
| import discord4j.core.object.entity.Guild; | ||||
| import discord4j.core.object.entity.Message; | ||||
| import discord4j.core.object.entity.User; | ||||
| import discord4j.core.object.entity.channel.MessageChannel; | ||||
| import reactor.core.publisher.Mono; | ||||
|  | ||||
| public class InteractionMock extends Interaction | ||||
|  { | ||||
|  | ||||
|     public InteractionMock() { | ||||
|         super(null, null); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Optional<Message> getMessage() { | ||||
|         //return Optional.of(new MessageMock()); | ||||
|         return Optional.empty(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Mono<MessageChannel> getChannel() { | ||||
|         return Mono.just(new ChannelMock()); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Mono<Guild> getGuild() { | ||||
|         //return Mono.just(new ServerMock()); | ||||
|         return Mono.empty(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public User getUser() { | ||||
|         return new UserMock(); | ||||
|     } | ||||
|      | ||||
| } | ||||
| @@ -1,16 +0,0 @@ | ||||
| package net.tomatentum.marinara.test.discord4j.mocks; | ||||
|  | ||||
| import discord4j.core.event.domain.interaction.ChatInputInteractionEvent; | ||||
|  | ||||
| public class SlashCommandInteractionMock extends ChatInputInteractionEvent { | ||||
|  | ||||
|     public SlashCommandInteractionMock() { | ||||
|         super(null, null, null); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public String getCommandName() { | ||||
|         return "test"; | ||||
|     } | ||||
|      | ||||
| } | ||||
| @@ -1,25 +0,0 @@ | ||||
| package net.tomatentum.marinara.test.discord4j.mocks; | ||||
|  | ||||
| import java.util.Optional; | ||||
|  | ||||
| import discord4j.core.object.command.ApplicationCommandInteractionOption; | ||||
| import discord4j.core.object.command.ApplicationCommandInteractionOptionValue; | ||||
| import discord4j.core.object.command.ApplicationCommandOption.Type; | ||||
|  | ||||
| public class SlashCommandInteractionOptionMock extends ApplicationCommandInteractionOption { | ||||
|  | ||||
|     public SlashCommandInteractionOptionMock() { | ||||
|         super(null, null, null, null); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public String getName() { | ||||
|         return "foo"; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Optional<ApplicationCommandInteractionOptionValue> getValue() { | ||||
|         return Optional.of(new ApplicationCommandInteractionOptionValue(null, null, Type.STRING.getValue(), "test", null)); | ||||
|     } | ||||
|      | ||||
| } | ||||
| @@ -1,11 +0,0 @@ | ||||
| package net.tomatentum.marinara.test.discord4j.mocks; | ||||
|  | ||||
| import discord4j.core.object.entity.User; | ||||
|  | ||||
| public class UserMock extends User { | ||||
|  | ||||
|     public UserMock() { | ||||
|         super(null, null); | ||||
|     } | ||||
|      | ||||
| } | ||||
| @@ -0,0 +1,7 @@ | ||||
| # SLF4J's SimpleLogger configuration file | ||||
| # Simple implementation of Logger that sends all enabled log messages, for all defined loggers, to System.err. | ||||
|  | ||||
| # Default logging detail level for all instances of SimpleLogger. | ||||
| # Must be one of ("trace", "debug", "info", "warn", or "error"). | ||||
| # If not specified, defaults to "info". | ||||
| org.slf4j.simpleLogger.defaultLogLevel=trace | ||||
| @@ -10,17 +10,15 @@ plugins { | ||||
|     `java-library` | ||||
| } | ||||
|  | ||||
| repositories { | ||||
|     // Use Maven Central for resolving dependencies. | ||||
|     mavenCentral() | ||||
| } | ||||
|  | ||||
| dependencies { | ||||
|     // Use JUnit Jupiter for testing. | ||||
|     testImplementation(libs.junit.jupiter) | ||||
|     testImplementation(libs.mockito) | ||||
|  | ||||
|     testRuntimeOnly("org.junit.platform:junit-platform-launcher") | ||||
|     implementation(libs.log4j) | ||||
|     testImplementation(libs.slf4j.simple) | ||||
|  | ||||
|     implementation(libs.slf4j) | ||||
|     implementation(libs.javacord) | ||||
|     implementation(libs.geantyref) | ||||
|     implementation(project(":lib")) | ||||
|   | ||||
| @@ -2,10 +2,12 @@ package net.tomatentum.marinara.wrapper.javacord; | ||||
|  | ||||
| import org.javacord.api.interaction.AutocompleteInteraction; | ||||
| import org.javacord.api.interaction.ButtonInteraction; | ||||
| import org.javacord.api.interaction.InteractionBase; | ||||
| import org.javacord.api.interaction.SlashCommandInteraction; | ||||
| import org.javacord.api.interaction.SlashCommandInteractionOption; | ||||
| import org.javacord.api.interaction.SlashCommandOptionType; | ||||
|  | ||||
| import net.tomatentum.marinara.interaction.commands.option.AutocompleteOptionData; | ||||
| import net.tomatentum.marinara.wrapper.ContextObjectProvider; | ||||
|  | ||||
| public class JavacordContextObjectProvider implements ContextObjectProvider { | ||||
| @@ -16,14 +18,14 @@ public class JavacordContextObjectProvider implements ContextObjectProvider { | ||||
|             return null; | ||||
|         SlashCommandInteraction interaction = (SlashCommandInteraction) context; | ||||
|         if (!interaction.getArguments().isEmpty()) | ||||
|             return getOptionValue(interaction.getOptionByName(optionName).get()); | ||||
|             return getOptionValue(interaction.getOptionByName(optionName).orElse(null)); | ||||
|  | ||||
|         SlashCommandInteractionOption subCommandOption = interaction.getOptions().getFirst(); | ||||
|  | ||||
|         if (!subCommandOption.getOptions().isEmpty()) | ||||
|             subCommandOption = subCommandOption.getOptions().getFirst(); | ||||
|  | ||||
|         return getOptionValue(subCommandOption.getOptionByName(optionName).get()); | ||||
|         return getOptionValue(subCommandOption.getOptionByName(optionName).orElse(null)); | ||||
|     } | ||||
|  | ||||
|     private Object getOptionValue(SlashCommandInteractionOption option) { | ||||
| @@ -89,7 +91,7 @@ public class JavacordContextObjectProvider implements ContextObjectProvider { | ||||
|  | ||||
|     @Override | ||||
|     public Object getInteractionContextObject(Object context, Class<?> type) { | ||||
|         ButtonInteraction button = (ButtonInteraction) context; | ||||
|         InteractionBase button = (InteractionBase) context; | ||||
|         switch (type.getName()) { | ||||
|             case "org.javacord.api.entity.channel.TextChannel": | ||||
|                 return button.getChannel().orElse(null); | ||||
| @@ -102,9 +104,9 @@ public class JavacordContextObjectProvider implements ContextObjectProvider { | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Object getAutocompleteFocusedOption(Object context) { | ||||
|         AutocompleteInteraction interaction = (AutocompleteInteraction) context; | ||||
|         return getOptionValue(interaction.getFocusedOption()); | ||||
|     public AutocompleteOptionData getAutocompleteFocusedOption(Object context) { | ||||
|         SlashCommandInteractionOption option = ((AutocompleteInteraction) context).getFocusedOption(); | ||||
|         return new AutocompleteOptionData(option.getName(), getOptionValue(option)); | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -0,0 +1,77 @@ | ||||
| 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<SlashCommandBuilder, SlashCommandOption, SlashCommandOptionChoice> { | ||||
|  | ||||
| 	@Override | ||||
| 	public SlashCommandBuilder convertCommand(RootCommandIdentifier rootIdentifier, List<SlashCommandOption> options) { | ||||
| 		return SlashCommand.with(rootIdentifier.name(), rootIdentifier.description(), options); | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public SlashCommandOption convertSubCommandGroup(InteractionIdentifier identifier, | ||||
| 			List<SlashCommandOption> subCommands) { | ||||
| 		return SlashCommandOption.createWithOptions( | ||||
| 			SlashCommandOptionType.SUB_COMMAND_GROUP,  | ||||
| 			identifier.name(),  | ||||
| 			identifier.description(),  | ||||
| 			subCommands); | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public SlashCommandOption convertSubCommand(InteractionIdentifier identifier, List<SlashCommandOption> 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<SlashCommandOptionChoice> choices) { | ||||
| 		SlashCommandOptionType type = SlashCommandOptionType.fromValue(option.type().getValue()); | ||||
| 		return new SlashCommandOptionBuilder() | ||||
|             .setType(type) | ||||
|             .setName(option.name()) | ||||
|             .setDescription(option.description()) | ||||
|             .setRequired(option.required()) | ||||
|             .setAutocompletable(option.autocompletes().length > 0) | ||||
| 			.setMinLength((long) option.range().min()) | ||||
| 			.setDecimalMinValue(option.range().min()) | ||||
| 			.setMaxLength((long) option.range().max()) | ||||
| 			.setDecimalMaxValue(option.range().max()) | ||||
|             .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(); | ||||
| 	} | ||||
| } | ||||
| @@ -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<SlashCommandBuilder> { | ||||
|  | ||||
|     private DiscordApi api; | ||||
|  | ||||
|     public JavacordRegistererStrategy(DiscordApi api) { | ||||
|         this.api = api; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void registerServer(ServerCommandList<SlashCommandBuilder> commands) { | ||||
|         api.bulkOverwriteServerApplicationCommands(commands.serverId(), commands); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void registerGlobal(Set<SlashCommandBuilder> defs) { | ||||
|         api.bulkOverwriteGlobalApplicationCommands(defs); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -1,177 +1,54 @@ | ||||
| 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 org.javacord.api.interaction.SlashCommandOptionChoice; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
|  | ||||
| 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.util.LoggerUtil; | ||||
| import net.tomatentum.marinara.wrapper.IdentifierProvider; | ||||
| 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<SlashCommandBuilder> commandRegisterer; | ||||
|      | ||||
|     private Logger logger = LoggerUtil.getLogger(getClass()); | ||||
|     private Logger logger = LoggerFactory.getLogger(getClass()); | ||||
|  | ||||
|     public JavacordWrapper(DiscordApi api) { | ||||
|         this.api = api; | ||||
|         this.contextObjectProvider = new JavacordContextObjectProvider(); | ||||
|         api.addInteractionCreateListener((e) -> handleInteraction(e.getInteraction())); | ||||
|         var converter = CommandConverter.of(new JavacordConverterSpec()); | ||||
|  | ||||
|         if (api != null) { | ||||
|             this.commandRegisterer = CommandRegisterer.of(new JavacordRegistererStrategy(api), converter); | ||||
|             api.addInteractionCreateListener(e -> handleInteraction(e.getInteraction())); | ||||
|         }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<Long, Set<SlashCommandBuilder>> serverCommands = new HashMap<>(); | ||||
|         Set<SlashCommandBuilder> 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<SlashCommandInteractionOption> 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<org.javacord.api.interaction.SlashCommandOption> 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<org.javacord.api.interaction.SlashCommandOption> 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<org.javacord.api.interaction.SlashCommandOption> 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<org.javacord.api.interaction.SlashCommandOptionChoice> convertChoices(SlashCommandOption option) { | ||||
|         List<org.javacord.api.interaction.SlashCommandOptionChoice> 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 | ||||
| @@ -179,4 +56,15 @@ public class JavacordWrapper extends LibraryWrapper { | ||||
|         return contextObjectProvider; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void respondAutocomplete(Object context, List<Object> options) { | ||||
|         if (context instanceof AutocompleteInteraction interaction) { | ||||
|             List<SlashCommandOptionChoice> choices = options.stream() | ||||
|                 .filter(SlashCommandOptionChoice.class::isInstance) | ||||
|                 .map(o -> (SlashCommandOptionChoice)o) | ||||
|                 .toList(); | ||||
|             interaction.respondWithChoices(choices); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -0,0 +1,40 @@ | ||||
| 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<AutocompleteInteraction> { | ||||
|  | ||||
|     @Override | ||||
|     public InteractionIdentifier convert(AutocompleteInteraction context) { | ||||
|         InteractionIdentifier last = InteractionIdentifier.builder() | ||||
|             .type(InteractionType.AUTOCOMPLETE) | ||||
|             .name(context.getCommandName()) | ||||
|             .build(); | ||||
|  | ||||
|         List<SlashCommandInteractionOption> options = context.getOptions(); | ||||
|         if (!options.isEmpty()) { | ||||
|             last = InteractionIdentifier.builder() | ||||
|                 .type(InteractionType.AUTOCOMPLETE) | ||||
|                 .name(options.getFirst().getName()) | ||||
|                 .parent(last) | ||||
|                 .build(); | ||||
|  | ||||
|             List<SlashCommandInteractionOption> subOptions = context.getOptions().getFirst().getOptions(); | ||||
|             if (!subOptions.isEmpty()) | ||||
|                 last = InteractionIdentifier.builder() | ||||
|                     .type(InteractionType.AUTOCOMPLETE) | ||||
|                     .name(subOptions.getFirst().getName()) | ||||
|                     .parent(last) | ||||
|                     .build(); | ||||
|         } | ||||
|         return last; | ||||
|     } | ||||
|      | ||||
| } | ||||
| @@ -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<ButtonInteraction> { | ||||
|  | ||||
|     @Override | ||||
|     public InteractionIdentifier convert(ButtonInteraction context) { | ||||
|         return InteractionIdentifier.builder().name(context.getCustomId()).type(InteractionType.BUTTON).build(); | ||||
|     } | ||||
|      | ||||
| } | ||||
| @@ -0,0 +1,40 @@ | ||||
| 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<SlashCommandInteraction> { | ||||
|  | ||||
|     @Override | ||||
|     public InteractionIdentifier convert(SlashCommandInteraction context) { | ||||
|         InteractionIdentifier last = InteractionIdentifier.builder() | ||||
|             .type(InteractionType.COMMAND) | ||||
|             .name(context.getCommandName()) | ||||
|             .build(); | ||||
|  | ||||
|         List<SlashCommandInteractionOption> options = context.getOptions(); | ||||
|         if (!options.isEmpty()) { | ||||
|             last = InteractionIdentifier.builder() | ||||
|                 .type(InteractionType.COMMAND) | ||||
|                 .name(options.getFirst().getName()) | ||||
|                 .parent(last) | ||||
|                 .build(); | ||||
|                  | ||||
|             List<SlashCommandInteractionOption> subOptions = context.getOptions().getFirst().getOptions(); | ||||
|             if (!subOptions.isEmpty()) | ||||
|                 last = InteractionIdentifier.builder() | ||||
|                     .type(InteractionType.COMMAND) | ||||
|                     .name(subOptions.getFirst().getName()) | ||||
|                     .parent(last) | ||||
|                     .build(); | ||||
|         } | ||||
|         return last; | ||||
|     } | ||||
|      | ||||
| } | ||||
| @@ -1,23 +1,40 @@ | ||||
| package net.tomatentum.marinara.test.javacord; | ||||
|  | ||||
| import static org.junit.jupiter.api.Assertions.assertTrue; | ||||
| import static org.mockito.ArgumentMatchers.any; | ||||
| import static org.mockito.Mockito.mock; | ||||
| import static org.mockito.Mockito.verify; | ||||
| import static org.mockito.Mockito.when; | ||||
|  | ||||
| import java.util.Collections; | ||||
| import java.util.Optional; | ||||
|  | ||||
| import org.javacord.api.interaction.AutocompleteInteraction; | ||||
| import org.javacord.api.interaction.SlashCommandInteractionOption; | ||||
| import org.junit.jupiter.api.Test; | ||||
|  | ||||
| import net.tomatentum.marinara.Marinara; | ||||
| import net.tomatentum.marinara.test.javacord.mocks.AutocompleteInteractionMock; | ||||
| import net.tomatentum.marinara.test.javacord.mocks.DiscordApiMock; | ||||
| import net.tomatentum.marinara.wrapper.LibraryWrapper; | ||||
| import net.tomatentum.marinara.wrapper.javacord.JavacordWrapper; | ||||
|  | ||||
| public class AutoCompleteTest { | ||||
| class AutoCompleteTest { | ||||
|      | ||||
|     @Test | ||||
|     public void testAutocomplete() { | ||||
|         LibraryWrapper wrapper = new JavacordWrapper(new DiscordApiMock()); //null okay as we don't use the discord API in this test. | ||||
|     void testAutocomplete() { | ||||
|  | ||||
|         SlashCommandInteractionOption optionMock = mock(); | ||||
|         AutocompleteInteraction autocompleteInteractionMock = mock(); | ||||
|  | ||||
|         when(optionMock.getName()).thenReturn("foo"); | ||||
|         when(optionMock.getStringValue()).thenReturn(Optional.of("test")); | ||||
|  | ||||
|         when(autocompleteInteractionMock.getCommandName()).thenReturn("test"); | ||||
|         when(autocompleteInteractionMock.getFocusedOption()).thenReturn(optionMock); | ||||
|         when(autocompleteInteractionMock.getOptions()).thenReturn(Collections.emptyList()); | ||||
|  | ||||
|         LibraryWrapper wrapper = new JavacordWrapper(null); //null okay as we don't use the discord API in this test. | ||||
|         Marinara marinara = Marinara.load(wrapper); | ||||
|         marinara.getRegistry().addInteractions(new TestAutocomplete()); | ||||
|         wrapper.handleInteraction(new AutocompleteInteractionMock()); | ||||
|         assertTrue(AutocompleteInteractionMock.didAutocompleteRun); | ||||
|         marinara.getInteractionContainer().addAllMethods(new TestAutocomplete()); | ||||
|         wrapper.handleInteraction(autocompleteInteractionMock); | ||||
|         verify(autocompleteInteractionMock).respondWithChoices(any()); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -7,20 +7,19 @@ import org.junit.jupiter.api.TestInstance; | ||||
| import org.junit.jupiter.api.TestInstance.Lifecycle; | ||||
|  | ||||
| import net.tomatentum.marinara.Marinara; | ||||
| import net.tomatentum.marinara.test.javacord.mocks.ButtonInteractionMock; | ||||
| import net.tomatentum.marinara.test.javacord.mocks.DiscordApiMock; | ||||
| import net.tomatentum.marinara.test.javacord.mocks.CommonMocks; | ||||
| import net.tomatentum.marinara.wrapper.LibraryWrapper; | ||||
| import net.tomatentum.marinara.wrapper.javacord.JavacordWrapper; | ||||
|  | ||||
| @TestInstance(Lifecycle.PER_CLASS) | ||||
| public class ButtonTest { | ||||
| class ButtonTest { | ||||
|  | ||||
|     @Test | ||||
|     public void testButtonExecution() { | ||||
|         LibraryWrapper wrapper = new JavacordWrapper(new DiscordApiMock()); //null okay as we don't use the discord API in this test. | ||||
|     void testButtonExecution() { | ||||
|         LibraryWrapper wrapper = new JavacordWrapper(null); //null okay as we don't use the discord API in this test. | ||||
|         Marinara marinara = Marinara.load(wrapper); | ||||
|         marinara.getRegistry().addInteractions(new TestButton()); | ||||
|         wrapper.handleInteraction(new ButtonInteractionMock("test")); | ||||
|         marinara.getInteractionContainer().addAllMethods(new TestButton()); | ||||
|         wrapper.handleInteraction(CommonMocks.getButtonInteractionMock("test")); | ||||
|         assertTrue(TestButton.didRun); | ||||
|     } | ||||
|      | ||||
|   | ||||
| @@ -2,46 +2,52 @@ package net.tomatentum.marinara.test.javacord; | ||||
|  | ||||
| import static org.junit.jupiter.api.Assertions.assertFalse; | ||||
| import static org.junit.jupiter.api.Assertions.assertTrue; | ||||
| import static org.mockito.ArgumentMatchers.any; | ||||
| import static org.mockito.Mockito.mock; | ||||
| import static org.mockito.Mockito.when; | ||||
|  | ||||
| import org.javacord.api.entity.permission.PermissionType; | ||||
| import org.javacord.api.entity.server.Server; | ||||
| import org.javacord.api.interaction.ButtonInteraction; | ||||
| import org.junit.jupiter.api.Test; | ||||
| import org.junit.jupiter.api.TestInstance; | ||||
| import org.junit.jupiter.api.TestInstance.Lifecycle; | ||||
|  | ||||
| import net.tomatentum.marinara.Marinara; | ||||
| import net.tomatentum.marinara.test.javacord.mocks.ButtonInteractionMock; | ||||
| import net.tomatentum.marinara.test.javacord.mocks.DiscordApiMock; | ||||
| import net.tomatentum.marinara.test.javacord.mocks.ServerMock; | ||||
| import net.tomatentum.marinara.test.javacord.mocks.CommonMocks; | ||||
| import net.tomatentum.marinara.wrapper.LibraryWrapper; | ||||
| import net.tomatentum.marinara.wrapper.javacord.JavacordWrapper; | ||||
| import net.tomatentum.marinara.wrapper.javacord.checks.PermissionCheck; | ||||
|  | ||||
| @TestInstance(Lifecycle.PER_CLASS) | ||||
| public class InteractionCheckTest { | ||||
| class InteractionCheckTest { | ||||
|      | ||||
|     @Test | ||||
|     public void testInteractionCheck() { | ||||
|         LibraryWrapper wrapper = new JavacordWrapper(new DiscordApiMock()); | ||||
|     void testInteractionCheck() { | ||||
|         LibraryWrapper wrapper = new JavacordWrapper(null); | ||||
|         Marinara marinara = Marinara.load(wrapper); | ||||
|         marinara.getCheckRegistry().addCheck(new TestInteractionCheck()); | ||||
|         marinara.getRegistry().addInteractions(new TestButton()); | ||||
|         wrapper.handleInteraction(new ButtonInteractionMock("test")); | ||||
|         marinara.getCheckContainer().addAllMethods(new TestInteractionCheck()); | ||||
|         marinara.getInteractionContainer().addAllMethods(new TestButton()); | ||||
|         wrapper.handleInteraction(CommonMocks.getButtonInteractionMock("test")); | ||||
|         assertTrue(TestInteractionCheck.preExecuted); | ||||
|         assertTrue(TestInteractionCheck.postExecuted); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     public void testPermissionCheck() { | ||||
|         LibraryWrapper wrapper = new JavacordWrapper(new DiscordApiMock()); | ||||
|     void testPermissionCheck() { | ||||
|         LibraryWrapper wrapper = new JavacordWrapper(null); | ||||
|         Marinara marinara = Marinara.load(wrapper); | ||||
|         marinara.getCheckRegistry().addCheck(new PermissionCheck()); | ||||
|         marinara.getRegistry().addInteractions(new TestButton()); | ||||
|         wrapper.handleInteraction(new ButtonInteractionMock("permissionCheck")); | ||||
|         assertTrue(TestButton.didPermRun); | ||||
|         TestButton.didPermRun = false; | ||||
|         ServerMock.TESTPERMISSION = PermissionType.ATTACH_FILE; | ||||
|         wrapper.handleInteraction(new ButtonInteractionMock("permissionCheck")); | ||||
|         marinara.getCheckContainer().addAllMethods(new PermissionCheck()); | ||||
|         marinara.getInteractionContainer().addAllMethods(new TestButton()); | ||||
|  | ||||
|         Server serverMock = mock(); | ||||
|         ButtonInteraction buttonInteractionMock = CommonMocks.getButtonInteractionMock("permissionCheck", serverMock); | ||||
|         when(serverMock.hasPermissions(any(), any())).thenReturn(false); | ||||
|         wrapper.handleInteraction(buttonInteractionMock); | ||||
|         assertFalse(TestButton.didPermRun); | ||||
|  | ||||
|         when(serverMock.hasPermissions(any(), any())).thenReturn(true); | ||||
|         wrapper.handleInteraction(buttonInteractionMock); | ||||
|         assertTrue(TestButton.didPermRun); | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -1,7 +1,15 @@ | ||||
| package net.tomatentum.marinara.test.javacord; | ||||
|  | ||||
| import static org.mockito.Mockito.mock; | ||||
| import static org.mockito.Mockito.when; | ||||
|  | ||||
| import java.util.Arrays; | ||||
| import java.util.Optional; | ||||
|  | ||||
| import org.javacord.api.DiscordApi; | ||||
| import org.javacord.api.DiscordApiBuilder; | ||||
| import org.javacord.api.interaction.SlashCommandInteraction; | ||||
| import org.javacord.api.interaction.SlashCommandInteractionOption; | ||||
| import org.junit.jupiter.api.AfterAll; | ||||
| import org.junit.jupiter.api.BeforeAll; | ||||
| import org.junit.jupiter.api.Test; | ||||
| @@ -9,11 +17,10 @@ import org.junit.jupiter.api.TestInstance; | ||||
| import org.junit.jupiter.api.TestInstance.Lifecycle; | ||||
|  | ||||
| import net.tomatentum.marinara.Marinara; | ||||
| import net.tomatentum.marinara.test.javacord.mocks.SlashCommandInteractionMock; | ||||
| import net.tomatentum.marinara.wrapper.LibraryWrapper; | ||||
| import net.tomatentum.marinara.wrapper.javacord.JavacordWrapper; | ||||
| @TestInstance(Lifecycle.PER_CLASS) | ||||
| public class SlashCommandTest { | ||||
| class SlashCommandTest { | ||||
|  | ||||
|     String DISCORD_TOKEN = System.getenv("DISCORD_TEST_TOKEN"); | ||||
|     DiscordApi api; | ||||
| @@ -34,8 +41,8 @@ public class SlashCommandTest { | ||||
|     @Test | ||||
|     void testSlashCommand() { | ||||
|         Marinara marinara = Marinara.load(new JavacordWrapper(api)); | ||||
|         marinara.getRegistry().addInteractions(new TestCommand()); | ||||
|         marinara.getRegistry().registerCommands(); | ||||
|         marinara.getInteractionContainer().addAllMethods(new TestCommand()); | ||||
|         marinara.registerCommands(); | ||||
|         System.out.println("Success!"); | ||||
|     } | ||||
|      | ||||
| @@ -43,9 +50,20 @@ public class SlashCommandTest { | ||||
|     void testSlashCommandExecution() { | ||||
|         LibraryWrapper wrapper = new JavacordWrapper(api); | ||||
|         Marinara marinara = Marinara.load(wrapper); | ||||
|         marinara.getRegistry().addInteractions(new TestCommand()); | ||||
|         marinara.getInteractionContainer().addAllMethods(new TestCommand()); | ||||
|  | ||||
|         wrapper.handleInteraction(new SlashCommandInteractionMock()); | ||||
|         SlashCommandInteractionOption optionMock = mock(); | ||||
|         SlashCommandInteraction interactionMock = mock(); | ||||
|  | ||||
|         when(optionMock.getName()).thenReturn("foo"); | ||||
|         when(optionMock.getStringValue()).thenReturn(Optional.of("test")); | ||||
|  | ||||
|         when(interactionMock.getCommandName()).thenReturn("test"); | ||||
|         when(interactionMock.getOptions()).thenReturn(Arrays.asList(optionMock)); | ||||
|         when(interactionMock.getArguments()).thenReturn(Arrays.asList(optionMock)); | ||||
|         when(interactionMock.getOptionByName("foo")).thenReturn(Optional.of(optionMock)); | ||||
|  | ||||
|         wrapper.handleInteraction(interactionMock); | ||||
|     } | ||||
|  | ||||
|      | ||||
|   | ||||
| @@ -5,18 +5,32 @@ import static org.junit.jupiter.api.Assertions.assertEquals; | ||||
| import java.util.Collections; | ||||
|  | ||||
| import org.javacord.api.interaction.AutocompleteInteraction; | ||||
| import org.javacord.api.interaction.SlashCommandInteraction; | ||||
|  | ||||
| import net.tomatentum.marinara.interaction.InteractionHandler; | ||||
| import net.tomatentum.marinara.interaction.annotation.AutoComplete; | ||||
| import net.tomatentum.marinara.interaction.commands.annotation.SlashCommand; | ||||
| import net.tomatentum.marinara.interaction.commands.annotation.SlashCommandOption; | ||||
| import net.tomatentum.marinara.interaction.commands.option.SlashCommandOptionType; | ||||
|  | ||||
| public class TestAutocomplete implements InteractionHandler { | ||||
|      | ||||
|     @SlashCommand(name = "test") | ||||
|     @AutoComplete | ||||
|     @SlashCommand( | ||||
|         name = "test", | ||||
|         options = @SlashCommandOption( | ||||
|                 name = "foo", | ||||
|                 type = SlashCommandOptionType.STRING, | ||||
|                 autocompletes = @AutoComplete("testAuto") | ||||
|             ) | ||||
|         ) | ||||
|     public void exec(SlashCommandInteraction context) { | ||||
|         //only here for command definition | ||||
|     } | ||||
|  | ||||
|     @AutoComplete("testAuto") | ||||
|     public void autocomplete(AutocompleteInteraction context, String value) { | ||||
|         System.out.println("Success!"); | ||||
|         assertEquals(value, "test"); | ||||
|         assertEquals("test", value); | ||||
|         context.respondWithChoices(Collections.emptyList()); | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -16,8 +16,8 @@ import net.tomatentum.marinara.wrapper.javacord.checks.PermissionCheck.HasPermis | ||||
|  | ||||
| public class TestButton implements InteractionHandler { | ||||
|  | ||||
|  | ||||
|     public static boolean didRun = false; | ||||
|  | ||||
|     @Button("test") | ||||
|     @TestCheck | ||||
|     public void exec(ButtonInteraction interaction, TextChannel channel, Message message, User member, Server server) { | ||||
|   | ||||
| @@ -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<String> { | ||||
|     TestValue("testValue"), | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user