Compare commits
	
		
			61 Commits
		
	
	
		
			master
			...
			ad19ed6ada
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| ad19ed6ada | |||
| ca822909e3 | |||
| caeaec1926 | |||
| f4dbdc302d | |||
| fd87431d51 | |||
| 3b65784770 | |||
| b4af922ac2 | |||
| 1146fa1cc6 | |||
| 9d88ca902d | |||
| e9b5610f3c | |||
| 8e872e7ada | |||
| 43c5946227 | |||
| 71c910ab93 | |||
| c5a7f3665e | |||
| 3baf1eda9a | |||
| 628abb4239 | |||
| 9a89544a9e | |||
| 455fc4955f | |||
| b72c55fc6e | |||
| 3691434f66 | |||
| 74d55d81ca | |||
| 404f221ccf | |||
| 7249c99b69 | |||
| b764972eba | |||
| 83ee4b1efa | |||
| 6b86e9ff87 | |||
| 7a40aebd6d | |||
| 76ab779ab2 | |||
| 3d19fae6b8 | |||
| 1ecbc563a6 | |||
| bf0022775d | |||
| a17f5e826f | |||
| 3de0f32074 | |||
| aaf4f3297a | |||
| 445190db89 | |||
| 9d3a6b8b85 | |||
| 1cb6cd0e05 | |||
| a5e1230fc6 | |||
| 432db43bf5 | |||
| 7a2c15d877 | |||
| f32c7045a1 | |||
| 69b27e4554 | |||
| d8504a7cde | |||
| bef34ee548 | |||
| 29bb7e667e | |||
| 94da2a0e3c | |||
| 7f47130461 | |||
| 83a3efd4b8 | |||
| aefd8a51a0 | |||
| 4332592dfa | |||
| c363ab9744 | |||
| 33392b02fb | |||
| b7333c2e5e | |||
| 239e921e6f | |||
| 6eb7fb723f | |||
| 659218682e | |||
| 019ba8f552 | |||
| f89ae5e425 | |||
| 582e0f0bae | |||
| 0ea330d48b | |||
| c241f6b1fe | 
							
								
								
									
										40
									
								
								.gitea/workflows/gradle-publish-dev.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								.gitea/workflows/gradle-publish-dev.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | |||||||
|  | name: Publish | ||||||
|  |  | ||||||
|  | on: | ||||||
|  |   push: | ||||||
|  |     branches: [dev] | ||||||
|  |  | ||||||
|  | jobs: | ||||||
|  |   Gradle-Publish: | ||||||
|  |     runs-on: ubuntu-latest | ||||||
|  |  | ||||||
|  |     steps: | ||||||
|  |       - name: Checkout code | ||||||
|  |         uses: actions/checkout@v4 | ||||||
|  |  | ||||||
|  |       - name: Set up JDK | ||||||
|  |         uses: actions/setup-java@v4 | ||||||
|  |         with: | ||||||
|  |           java-version: '23' | ||||||
|  |           check-latest: true | ||||||
|  |           distribution: 'zulu' | ||||||
|  |  | ||||||
|  |       - name: Setup Gradle | ||||||
|  |         uses: gradle/actions/setup-gradle@v4 | ||||||
|  |         with: | ||||||
|  |           add-job-summary: always | ||||||
|  |           cache-cleanup: on-success | ||||||
|  |  | ||||||
|  |       - name: Cache Gradle dependencies | ||||||
|  |         uses: actions/cache@v4 | ||||||
|  |         with: | ||||||
|  |           path: |  | ||||||
|  |             ~/.gradle/caches | ||||||
|  |           key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} | ||||||
|  |           restore-keys: | | ||||||
|  |             ${{ runner.os }}-gradle- | ||||||
|  |  | ||||||
|  |       - name: Publish Dev | ||||||
|  |         env: | ||||||
|  |           GITEA_TOKEN: ${{ secrets.PUBLISH_PACKAGE_TOKEN }} | ||||||
|  |         run: chmod +x gradlew; ./gradlew publishAllPublicationsToGiteaRepository  | ||||||
| @@ -1,11 +1,11 @@ | |||||||
| name: Test | name: Publish | ||||||
|  |  | ||||||
| on: | on: | ||||||
|   push: |   push: | ||||||
|     branches: [master] |     branches: [master] | ||||||
|  |  | ||||||
| jobs: | jobs: | ||||||
|   Gradle-Test: |   Gradle-Publish: | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|  |  | ||||||
|     steps: |     steps: | ||||||
| @@ -34,7 +34,7 @@ jobs: | |||||||
|           restore-keys: | |           restore-keys: | | ||||||
|             ${{ runner.os }}-gradle- |             ${{ runner.os }}-gradle- | ||||||
|  |  | ||||||
|       - name: Publish |       - name: Publish Release | ||||||
|         env: |         env: | ||||||
|           GITEA_TOKEN: ${{ secrets.PUBLISH_PACKAGE_TOKEN }} |           GITEA_TOKEN: ${{ secrets.PUBLISH_PACKAGE_TOKEN }} | ||||||
|         run: chmod +x gradlew; ./gradlew publishAllPublicationsToGiteaRepository |         run: chmod +x gradlew; ./gradlew publishAllPublicationsToGiteaRepository -Prelease | ||||||
| @@ -8,6 +8,7 @@ allprojects { | |||||||
|     group = "net.tomatentum.Marinara" |     group = "net.tomatentum.Marinara" | ||||||
|     version = "1.0.0-RC1" + (if (!project.hasProperty("release")) ("-" + getGitHash()) else "") |     version = "1.0.0-RC1" + (if (!project.hasProperty("release")) ("-" + getGitHash()) else "") | ||||||
|     description = "A simple but powerful, library-agnostic Discord Interaction Wrapper." |     description = "A simple but powerful, library-agnostic Discord Interaction Wrapper." | ||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
| subprojects { | subprojects { | ||||||
| @@ -18,6 +19,8 @@ subprojects { | |||||||
|         publishing { |         publishing { | ||||||
|             publications { |             publications { | ||||||
|                 create<MavenPublication>("maven") { |                 create<MavenPublication>("maven") { | ||||||
|  |                     if (!project.hasProperty("release")) | ||||||
|  |                         artifactId = project.getName() + "-dev" | ||||||
|                     from(components["java"]) |                     from(components["java"]) | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|   | |||||||
| @@ -5,10 +5,12 @@ | |||||||
| junit-jupiter = "5.10.2" | junit-jupiter = "5.10.2" | ||||||
| log4j = "2.24.1" | log4j = "2.24.1" | ||||||
| javacord = "3.8.0" | javacord = "3.8.0" | ||||||
|  | discord4j = "3.2.7" | ||||||
| geantyref = "2.0.0" | geantyref = "2.0.0" | ||||||
|  |  | ||||||
| [libraries] | [libraries] | ||||||
| junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit-jupiter" } | junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit-jupiter" } | ||||||
| log4j = { module = "org.apache.logging.log4j:log4j-core", version.ref = "log4j"} | log4j = { module = "org.apache.logging.log4j:log4j-api", version.ref = "log4j"} | ||||||
| javacord = { module = "org.javacord:javacord", version.ref = "javacord"} | 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"} | geantyref = { module = "io.leangen.geantyref:geantyref", version.ref = "geantyref"} | ||||||
|   | |||||||
| @@ -21,13 +21,13 @@ dependencies { | |||||||
|  |  | ||||||
|     testRuntimeOnly("org.junit.platform:junit-platform-launcher") |     testRuntimeOnly("org.junit.platform:junit-platform-launcher") | ||||||
|     implementation(libs.log4j) |     implementation(libs.log4j) | ||||||
|  |     implementation(libs.geantyref) | ||||||
| } | } | ||||||
|  |  | ||||||
| // Apply a specific Java toolchain to ease working on different environments. | // Apply a specific Java toolchain to ease working on different environments. | ||||||
| java { | java { | ||||||
|     toolchain { |     toolchain { | ||||||
|         languageVersion = JavaLanguageVersion.of(21) |         languageVersion = JavaLanguageVersion.of(23) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,22 +1,40 @@ | |||||||
| package net.tomatentum.marinara; | package net.tomatentum.marinara; | ||||||
|  |  | ||||||
|  | import org.apache.logging.log4j.Logger; | ||||||
|  |  | ||||||
|  | import net.tomatentum.marinara.registry.InteractionCheckRegistry; | ||||||
| import net.tomatentum.marinara.registry.InteractionRegistry; | import net.tomatentum.marinara.registry.InteractionRegistry; | ||||||
|  | import net.tomatentum.marinara.util.LoggerUtil; | ||||||
| import net.tomatentum.marinara.wrapper.LibraryWrapper; | import net.tomatentum.marinara.wrapper.LibraryWrapper; | ||||||
|  |  | ||||||
| public class Marinara { | public class Marinara { | ||||||
|  |  | ||||||
|  |     private Logger logger = LoggerUtil.getLogger(getClass()); | ||||||
|  |      | ||||||
|     public static <T extends LibraryWrapper> Marinara load(LibraryWrapper wrapper) { |     public static <T extends LibraryWrapper> Marinara load(LibraryWrapper wrapper) { | ||||||
|         InteractionRegistry registry = new InteractionRegistry(wrapper); |         return new Marinara(wrapper); | ||||||
|         return new Marinara(registry); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private InteractionRegistry registry; |     private InteractionRegistry registry; | ||||||
|  |     private InteractionCheckRegistry checkRegistry; | ||||||
|  |     private LibraryWrapper wrapper; | ||||||
|  |  | ||||||
|     private Marinara(InteractionRegistry registry) { |     private Marinara(LibraryWrapper wrapper) { | ||||||
|         this.registry = registry; |         this.wrapper = wrapper; | ||||||
|  |         this.registry = new InteractionRegistry(this); | ||||||
|  |         this.checkRegistry = new InteractionCheckRegistry(); | ||||||
|  |         logger.info("Marinara loaded successfully!"); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public InteractionRegistry getRegistry() { |     public InteractionRegistry getRegistry() { | ||||||
|         return registry; |         return registry; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     public InteractionCheckRegistry getCheckRegistry() { | ||||||
|  |         return checkRegistry; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public LibraryWrapper getWrapper() { | ||||||
|  |         return wrapper; | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -0,0 +1,50 @@ | |||||||
|  | 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 net.tomatentum.marinara.util.LoggerUtil; | ||||||
|  | import net.tomatentum.marinara.util.ReflectionUtil; | ||||||
|  |  | ||||||
|  | public record AppliedCheck(InteractionCheck<?> check, Annotation annotation) { | ||||||
|  |  | ||||||
|  |     private static Logger logger = LoggerUtil.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; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     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); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,10 @@ | |||||||
|  | package net.tomatentum.marinara.checks; | ||||||
|  |  | ||||||
|  | import java.lang.annotation.Annotation; | ||||||
|  |  | ||||||
|  | public interface InteractionCheck<A extends Annotation> { | ||||||
|  |  | ||||||
|  |     public boolean preExec(Object context, A annotation); | ||||||
|  |     public void postExec(Object context, A annotation); | ||||||
|  |      | ||||||
|  | } | ||||||
| @@ -0,0 +1,12 @@ | |||||||
|  | package net.tomatentum.marinara.interaction.annotation; | ||||||
|  |  | ||||||
|  | import java.lang.annotation.ElementType; | ||||||
|  | import java.lang.annotation.Retention; | ||||||
|  | import java.lang.annotation.RetentionPolicy; | ||||||
|  | import java.lang.annotation.Target; | ||||||
|  |  | ||||||
|  | @Target({ElementType.METHOD}) | ||||||
|  | @Retention(RetentionPolicy.RUNTIME) | ||||||
|  | public @interface AutoComplete { | ||||||
|  |      | ||||||
|  | } | ||||||
| @@ -0,0 +1,5 @@ | |||||||
|  | package net.tomatentum.marinara.interaction.commands; | ||||||
|  |  | ||||||
|  | public interface ChoiceValueProvider<T> { | ||||||
|  |     T getChoiceValue(); | ||||||
|  | } | ||||||
| @@ -0,0 +1,78 @@ | |||||||
|  | package net.tomatentum.marinara.interaction.commands; | ||||||
|  |  | ||||||
|  | import java.lang.reflect.InvocationTargetException; | ||||||
|  | import java.lang.reflect.Method; | ||||||
|  | import java.lang.reflect.ParameterizedType; | ||||||
|  | import java.lang.reflect.Type; | ||||||
|  | import java.util.ArrayList; | ||||||
|  | import java.util.List; | ||||||
|  | import java.util.Map; | ||||||
|  |  | ||||||
|  | import io.leangen.geantyref.AnnotationFormatException; | ||||||
|  | import io.leangen.geantyref.GenericTypeReflector; | ||||||
|  | import io.leangen.geantyref.TypeFactory; | ||||||
|  | import net.tomatentum.marinara.interaction.commands.annotation.SlashCommandOptionChoice; | ||||||
|  |  | ||||||
|  | public record EnumChoices(Class<? extends Enum<?>> enumClass, ChoiceType type, SlashCommandOptionChoice[] choices) { | ||||||
|  |  | ||||||
|  |     private static Method method; | ||||||
|  |  | ||||||
|  |     static { | ||||||
|  |         try { | ||||||
|  |             method = ChoiceValueProvider.class.getMethod("getChoiceValue"); | ||||||
|  |         } catch (NoSuchMethodException | SecurityException e) { | ||||||
|  |             e.printStackTrace(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public static EnumChoices of(Class<? extends Enum<?>> enumClass) { | ||||||
|  |         if (!ChoiceValueProvider.class.isAssignableFrom(enumClass)) | ||||||
|  |             throw new IllegalArgumentException("Provided class needs to implement the ChoiceValueProvider interface."); | ||||||
|  |         ChoiceType type = parseChoiceType(enumClass); | ||||||
|  |         SlashCommandOptionChoice[] choices = parseChoices(enumClass, type); | ||||||
|  |         return new EnumChoices(enumClass, type, choices); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private static ChoiceType parseChoiceType(Class<? extends Enum<?>> enumClass) { | ||||||
|  |         ParameterizedType type = (ParameterizedType) GenericTypeReflector.getExactSuperType(enumClass, ChoiceValueProvider.class); | ||||||
|  |         Type typeParam = type.getActualTypeArguments()[0]; | ||||||
|  |  | ||||||
|  |         if (!(typeParam instanceof Class<?>)) | ||||||
|  |             throw new IllegalArgumentException("ChoiceValueProvider need either a String or Number type parameter."); | ||||||
|  |  | ||||||
|  |         if (Long.class.isAssignableFrom((Class<?>) typeParam)) | ||||||
|  |             return ChoiceType.INTEGER; | ||||||
|  |         if (Double.class.isAssignableFrom((Class<?>) typeParam)) | ||||||
|  |             return ChoiceType.DOUBLE; | ||||||
|  |         if (String.class.isAssignableFrom((Class<?>) typeParam)) | ||||||
|  |             return ChoiceType.String; | ||||||
|  |         throw new IllegalArgumentException("ChoiceValueProvider need either a String, Number or Decimal type parameter."); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private static SlashCommandOptionChoice[] parseChoices(Class<? extends Enum<?>> enumClass, ChoiceType type) { | ||||||
|  |         Enum<? extends Enum<?>>[] constants = enumClass.getEnumConstants(); | ||||||
|  |         List<SlashCommandOptionChoice> choices = new ArrayList<>(); | ||||||
|  |         for (Enum<? extends Enum<?>> enumInstance : constants) { | ||||||
|  |             Object value; | ||||||
|  |             try { | ||||||
|  |                 value = method.invoke(enumInstance); | ||||||
|  |                 if (type.equals(ChoiceType.INTEGER)) | ||||||
|  |                     choices.add(TypeFactory.annotation(SlashCommandOptionChoice.class, Map.of("name", enumInstance.name(), "longValue", value))); | ||||||
|  |                 if (type.equals(ChoiceType.DOUBLE)) | ||||||
|  |                     choices.add(TypeFactory.annotation(SlashCommandOptionChoice.class, Map.of("name", enumInstance.name(), "doubleValue", value))); | ||||||
|  |                 if (type.equals(ChoiceType.String)) | ||||||
|  |                     choices.add(TypeFactory.annotation(SlashCommandOptionChoice.class, Map.of("name", enumInstance.name(), "stringValue", value))); | ||||||
|  |             } catch (IllegalAccessException | InvocationTargetException | AnnotationFormatException e) { | ||||||
|  |                 e.printStackTrace(); | ||||||
|  |                 return null; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return choices.toArray(SlashCommandOptionChoice[]::new); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public static enum ChoiceType { | ||||||
|  |         String, | ||||||
|  |         INTEGER, | ||||||
|  |         DOUBLE | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -2,8 +2,10 @@ package net.tomatentum.marinara.interaction.commands; | |||||||
|  |  | ||||||
| import net.tomatentum.marinara.interaction.commands.annotation.SlashCommand; | import net.tomatentum.marinara.interaction.commands.annotation.SlashCommand; | ||||||
| import net.tomatentum.marinara.interaction.commands.annotation.SlashCommandOption; | import net.tomatentum.marinara.interaction.commands.annotation.SlashCommandOption; | ||||||
|  | import net.tomatentum.marinara.interaction.commands.annotation.SlashCommandOptionChoice; | ||||||
| import net.tomatentum.marinara.interaction.commands.annotation.SubCommand; | import net.tomatentum.marinara.interaction.commands.annotation.SubCommand; | ||||||
| import net.tomatentum.marinara.interaction.commands.annotation.SubCommandGroup; | import net.tomatentum.marinara.interaction.commands.annotation.SubCommandGroup; | ||||||
|  | import net.tomatentum.marinara.interaction.commands.annotation.SlashCommandOption.PlaceHolderEnum; | ||||||
|  |  | ||||||
| public record ExecutableSlashCommandDefinition( | public record ExecutableSlashCommandDefinition( | ||||||
|     SlashCommand applicationCommand, |     SlashCommand applicationCommand, | ||||||
| @@ -11,19 +13,41 @@ public record ExecutableSlashCommandDefinition( | |||||||
|     SubCommandGroup subCommandGroup,  |     SubCommandGroup subCommandGroup,  | ||||||
|     SlashCommandOption[] options) { |     SlashCommandOption[] options) { | ||||||
|  |  | ||||||
|  |     public static SlashCommandOptionChoice[] getActualChoices(SlashCommandOption option) { | ||||||
|  |         SlashCommandOptionChoice[] choices = option.choices(); | ||||||
|  |         if (choices.length <= 0 && !option.choiceEnum().equals(PlaceHolderEnum.class)) | ||||||
|  |             choices = EnumChoices.of(option.choiceEnum()).choices(); | ||||||
|  |         return choices; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public final boolean equals(Object o) { |     public final boolean equals(Object o) { | ||||||
|         if (!(o instanceof ExecutableSlashCommandDefinition)) |         if (!(o instanceof ExecutableSlashCommandDefinition)) | ||||||
|             return false; |             return false; | ||||||
|         ExecutableSlashCommandDefinition other = (ExecutableSlashCommandDefinition) o; |         ExecutableSlashCommandDefinition other = (ExecutableSlashCommandDefinition) o; | ||||||
|         return other.applicationCommand.name().equals(this.applicationCommand.name()) &&  |         boolean equals = false; | ||||||
|             other.subCommandGroup.name().equals(this.subCommandGroup.name()) && |  | ||||||
|             other.subCommand.name().equals(this.subCommand.name()); |         if (this.applicationCommand() != null && other.applicationCommand() != null) | ||||||
|  |             equals = this.applicationCommand().name().equals(other.applicationCommand().name()); | ||||||
|  |  | ||||||
|  |         if (this.subCommandGroup() != null && other.subCommandGroup() != null) | ||||||
|  |             equals = this.subCommandGroup().name().equals(other.subCommandGroup().name()); | ||||||
|  |          | ||||||
|  |         if (this.subCommand() != null && other.subCommand() != null) | ||||||
|  |             equals = this.subCommand().name().equals(other.subCommand().name()); | ||||||
|  |  | ||||||
|  |         return equals; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public final String toString() { |     public final String toString() { | ||||||
|         return applicationCommand.name() + subCommand.name() != null ? "::" + subCommand.name() : ""; |         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() { |     public boolean isRootCommand() { | ||||||
|   | |||||||
| @@ -14,4 +14,11 @@ public @interface SlashCommandOption { | |||||||
|     public String description() default ""; |     public String description() default ""; | ||||||
|     public SlashCommandOptionType type() default SlashCommandOptionType.STRING; |     public SlashCommandOptionType type() default SlashCommandOptionType.STRING; | ||||||
|     public boolean required() default false; |     public boolean required() default false; | ||||||
|  |     public boolean autocomplete() default false; | ||||||
|  |     public SlashCommandOptionChoice[] choices() default {}; | ||||||
|  |     public Class<? extends Enum<?>> choiceEnum() default PlaceHolderEnum.class; | ||||||
|  |  | ||||||
|  |     public static enum PlaceHolderEnum { | ||||||
|  |  | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -0,0 +1,8 @@ | |||||||
|  | package net.tomatentum.marinara.interaction.commands.annotation; | ||||||
|  |  | ||||||
|  | public @interface SlashCommandOptionChoice { | ||||||
|  |     public String name(); | ||||||
|  |     public long longValue() default Long.MAX_VALUE; | ||||||
|  |     public double doubleValue() default Double.MAX_VALUE; | ||||||
|  |     public String stringValue() default ""; | ||||||
|  | } | ||||||
| @@ -1,16 +1,35 @@ | |||||||
| package net.tomatentum.marinara.interaction.commands.option; | package net.tomatentum.marinara.interaction.commands.option; | ||||||
|  |  | ||||||
| public enum SlashCommandOptionType { | public enum SlashCommandOptionType { | ||||||
|     ATTACHMENT, |     SUB_COMMAND(1), | ||||||
|     BOOLEAN, |     SUB_COMMAND_GROUP(2), | ||||||
|     CHANNEL, |     STRING(3), | ||||||
|     DECIMAL, |     INTEGER(4), | ||||||
|     LONG, |     BOOLEAN(5), | ||||||
|     MENTIONABLE, |     USER(6), | ||||||
|     ROLE, |     CHANNEL(7), | ||||||
|     STRING, |     ROLE(8), | ||||||
|     SUB_COMMAND, |     MENTIONABLE(9), | ||||||
|     SUB_COMMAND_GROUP, |     DOUBLE(10), | ||||||
|     UNKNOW, |     ATTACHMENT(11), | ||||||
|     USER |     UNKNOWN(-1); | ||||||
|  |  | ||||||
|  |     private final int value; | ||||||
|  |  | ||||||
|  |     private SlashCommandOptionType(int value) { | ||||||
|  |         this.value = value; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public int getValue() { | ||||||
|  |         return value; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public static SlashCommandOptionType fromValue(int value) { | ||||||
|  |         for (SlashCommandOptionType type : values()) { | ||||||
|  |             if (type.getValue() == value) { | ||||||
|  |                 return type; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return UNKNOWN; | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -0,0 +1,51 @@ | |||||||
|  | package net.tomatentum.marinara.interaction.methods; | ||||||
|  |  | ||||||
|  | import java.lang.reflect.Method; | ||||||
|  |  | ||||||
|  | import net.tomatentum.marinara.Marinara; | ||||||
|  | import net.tomatentum.marinara.interaction.InteractionHandler; | ||||||
|  | import net.tomatentum.marinara.interaction.InteractionType; | ||||||
|  | import net.tomatentum.marinara.interaction.commands.ExecutableSlashCommandDefinition; | ||||||
|  | import net.tomatentum.marinara.parser.AnnotationParser; | ||||||
|  | import net.tomatentum.marinara.parser.SlashCommandParser; | ||||||
|  |  | ||||||
|  | public class AutoCompleteInteractionMethod extends InteractionMethod { | ||||||
|  |  | ||||||
|  |     private ExecutableSlashCommandDefinition commandDefinition; | ||||||
|  |  | ||||||
|  |     public AutoCompleteInteractionMethod(Method method,  | ||||||
|  |         InteractionHandler handler,  | ||||||
|  |         Marinara marinara | ||||||
|  |         ) { | ||||||
|  |         super(method, handler, marinara); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public AnnotationParser[] getParsers() { | ||||||
|  |         return new AnnotationParser[] {  | ||||||
|  |             new SlashCommandParser(method, (x) -> { this.commandDefinition = x; } )  | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public Object getParameter(Object context, int index) { | ||||||
|  |         Class<?> type = getMethod().getParameterTypes()[index+1]; | ||||||
|  |         Object autocompleteOptionValue = marinara.getWrapper().getContextObjectProvider().getAutocompleteFocusedOption(context); | ||||||
|  |         if (autocompleteOptionValue != null) | ||||||
|  |             return autocompleteOptionValue; | ||||||
|  |  | ||||||
|  |         return marinara.getWrapper().getContextObjectProvider().getComponentContextObject(context, type); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public boolean canRun(Object context) { | ||||||
|  |         ExecutableSlashCommandDefinition other = marinara.getWrapper().getCommandDefinition(context); | ||||||
|  |         return commandDefinition.equals(other); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public InteractionType getType() { | ||||||
|  |         return InteractionType.AUTOCOMPLETE; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  | } | ||||||
| @@ -2,29 +2,36 @@ package net.tomatentum.marinara.interaction.methods; | |||||||
|  |  | ||||||
| import java.lang.reflect.Method; | import java.lang.reflect.Method; | ||||||
|  |  | ||||||
|  | import net.tomatentum.marinara.Marinara; | ||||||
| import net.tomatentum.marinara.interaction.InteractionHandler; | import net.tomatentum.marinara.interaction.InteractionHandler; | ||||||
| import net.tomatentum.marinara.interaction.InteractionType; | import net.tomatentum.marinara.interaction.InteractionType; | ||||||
| import net.tomatentum.marinara.interaction.annotation.Button; | import net.tomatentum.marinara.parser.AnnotationParser; | ||||||
| import net.tomatentum.marinara.wrapper.LibraryWrapper; | import net.tomatentum.marinara.parser.ButtonParser; | ||||||
|  |  | ||||||
| public class ButtonInteractionMethod extends InteractionMethod { | public class ButtonInteractionMethod extends InteractionMethod { | ||||||
|  |  | ||||||
|     private String customId; |     private String customId; | ||||||
|  |  | ||||||
|     ButtonInteractionMethod(Method method, InteractionHandler handler, LibraryWrapper wrapper) { |     ButtonInteractionMethod(Method method, InteractionHandler handler, Marinara marinara) { | ||||||
|         super(method, handler, wrapper); |         super(method, handler, marinara); | ||||||
|         parseMethod(); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public Object getParameter(Object parameter, int index) { |     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]; |         Class<?> type = getMethod().getParameterTypes()[index+1]; | ||||||
|         return wrapper.getComponentContextObject(parameter, type); |         return marinara.getWrapper().getContextObjectProvider().getComponentContextObject(context, type); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public boolean canRun(Object context) { |     public boolean canRun(Object context) { | ||||||
|         return wrapper.getButtonId(context).equals(customId); |         return marinara.getWrapper().getButtonId(context).equals(customId); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
| @@ -32,9 +39,4 @@ public class ButtonInteractionMethod extends InteractionMethod { | |||||||
|         return InteractionType.BUTTON; |         return InteractionType.BUTTON; | ||||||
|     } |     } | ||||||
|      |      | ||||||
|     private void parseMethod() { |  | ||||||
|         Button button = getMethod().getAnnotation(Button.class); |  | ||||||
|         this.customId = button.value(); |  | ||||||
|     } |  | ||||||
|      |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -7,62 +7,100 @@ import java.util.ArrayList; | |||||||
| import java.util.Arrays; | import java.util.Arrays; | ||||||
| import java.util.List; | import java.util.List; | ||||||
|  |  | ||||||
|  | import org.apache.logging.log4j.Logger; | ||||||
|  |  | ||||||
|  | import net.tomatentum.marinara.Marinara; | ||||||
|  | import net.tomatentum.marinara.checks.AppliedCheck; | ||||||
| import net.tomatentum.marinara.interaction.InteractionHandler; | import net.tomatentum.marinara.interaction.InteractionHandler; | ||||||
| import net.tomatentum.marinara.interaction.InteractionType; | 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.annotation.Button; | ||||||
| import net.tomatentum.marinara.interaction.commands.annotation.SlashCommand; | import net.tomatentum.marinara.interaction.commands.annotation.SlashCommand; | ||||||
| import net.tomatentum.marinara.interaction.commands.annotation.SubCommand; | import net.tomatentum.marinara.interaction.commands.annotation.SubCommand; | ||||||
| import net.tomatentum.marinara.wrapper.LibraryWrapper; | import net.tomatentum.marinara.parser.AnnotationParser; | ||||||
|  | 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 { | ||||||
|  |  | ||||||
|     public static InteractionMethod create(Method method, InteractionHandler handler, LibraryWrapper wrapper) { |     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)) |         if (method.isAnnotationPresent(SlashCommand.class) || method.isAnnotationPresent(SubCommand.class)) | ||||||
|             return new SlashCommandInteractionMethod(method, handler, wrapper); |             return new SlashCommandInteractionMethod(method, handler, marinara); | ||||||
|         if (method.isAnnotationPresent(Button.class)) |         if (method.isAnnotationPresent(Button.class)) | ||||||
|             return new ButtonInteractionMethod(method, handler, wrapper); |             return new ButtonInteractionMethod(method, handler, marinara); | ||||||
|         return null; |         return null; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     protected Method method; |     protected Method method; | ||||||
|     protected InteractionHandler handler; |     protected InteractionHandler handler; | ||||||
|     protected LibraryWrapper wrapper; |     protected Marinara marinara; | ||||||
|  |     protected List<AnnotationParser> parsers; | ||||||
|  |     protected List<AppliedCheck> appliedChecks; | ||||||
|  |  | ||||||
|     protected InteractionMethod(Method method, InteractionHandler handler, LibraryWrapper wrapper) { |     private Logger logger = LoggerUtil.getLogger(getClass()); | ||||||
|  |  | ||||||
|  |     protected InteractionMethod(Method method,  | ||||||
|  |         InteractionHandler handler,  | ||||||
|  |         Marinara marinara | ||||||
|  |         ) { | ||||||
|         if (!Arrays.asList(handler.getClass().getMethods()).contains(method)) |         if (!Arrays.asList(handler.getClass().getMethods()).contains(method)) | ||||||
|             throw new InvalidParameterException("Method does not apply to specified handler"); |             throw new InvalidParameterException("Method does not apply to specified handler"); | ||||||
|  |  | ||||||
|         this.method = method; |         this.method = method; | ||||||
|         this.handler = handler; |         this.handler = handler; | ||||||
|         this.wrapper = wrapper; |         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 Object getParameter(Object parameter, int index); |     public abstract AnnotationParser[] getParsers(); | ||||||
|  |  | ||||||
|  |     public abstract Object getParameter(Object context, int index); | ||||||
|  |  | ||||||
|     public abstract boolean canRun(Object context); |     public abstract boolean canRun(Object context); | ||||||
|  |  | ||||||
|     public abstract InteractionType getType(); |     public abstract InteractionType getType(); | ||||||
|  |  | ||||||
|     public void run(Object context) { |     public void run(Object context) { | ||||||
|         int parameterCount = method.getParameterCount(); |         if (this.appliedChecks.stream().filter(x -> !x.pre(context)).count() > 0) | ||||||
|         List<Object> parameters = new ArrayList<>(); |             return; | ||||||
|  |  | ||||||
|         for (int i = 0; i < parameterCount; i++) { |  | ||||||
|             if (i == 0) { |  | ||||||
|                 parameters.add(context); |  | ||||||
|                 continue; |  | ||||||
|             } |  | ||||||
|             parameters.add(getParameter(context, i-1)); |  | ||||||
|         } |  | ||||||
|         method.setAccessible(true); |         method.setAccessible(true); | ||||||
|         try { |         try { | ||||||
|             method.invoke(handler, parameters.toArray()); |             method.invoke(handler, getParameters(context)); | ||||||
|         }catch (IllegalAccessException | InvocationTargetException ex) { |         }catch (IllegalAccessException | InvocationTargetException ex) { | ||||||
|             throw new RuntimeException(ex); |             logger.fatal(ex); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         this.appliedChecks.forEach(x -> x.post(context)); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public Method getMethod() { |     public Method getMethod() { | ||||||
|         return method; |         return method; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     private Object[] getParameters(Object context) { | ||||||
|  |         int parameterCount = method.getParameterCount(); | ||||||
|  |         List<Object> parameters = new ArrayList<>(); | ||||||
|  |          | ||||||
|  |         for (int i = 0; i < parameterCount; i++) { | ||||||
|  |             Object parameter; | ||||||
|  |             if (i == 0) { | ||||||
|  |                 parameter = context; | ||||||
|  |             }else | ||||||
|  |                 parameter = getParameter(context, i-1); | ||||||
|  |  | ||||||
|  |             logger.trace("Found parameter {}={} for method {}", parameter.getClass().toString(), parameter, ReflectionUtil.getFullMethodName(method)); | ||||||
|  |             parameters.add(parameter);    | ||||||
|  |         } | ||||||
|  |         return parameters.toArray(); | ||||||
|  |     } | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -2,32 +2,36 @@ package net.tomatentum.marinara.interaction.methods; | |||||||
|  |  | ||||||
| import java.lang.reflect.Method; | import java.lang.reflect.Method; | ||||||
|  |  | ||||||
|  | import net.tomatentum.marinara.Marinara; | ||||||
| import net.tomatentum.marinara.interaction.InteractionHandler; | import net.tomatentum.marinara.interaction.InteractionHandler; | ||||||
| import net.tomatentum.marinara.interaction.InteractionType; | import net.tomatentum.marinara.interaction.InteractionType; | ||||||
| import net.tomatentum.marinara.interaction.commands.ExecutableSlashCommandDefinition; | import net.tomatentum.marinara.interaction.commands.ExecutableSlashCommandDefinition; | ||||||
| import net.tomatentum.marinara.interaction.commands.annotation.SlashCommand; | import net.tomatentum.marinara.parser.AnnotationParser; | ||||||
| import net.tomatentum.marinara.interaction.commands.annotation.SubCommand; | import net.tomatentum.marinara.parser.SlashCommandParser; | ||||||
| import net.tomatentum.marinara.interaction.commands.annotation.SubCommandGroup; |  | ||||||
| import net.tomatentum.marinara.util.ReflectionUtil; |  | ||||||
| import net.tomatentum.marinara.wrapper.LibraryWrapper; |  | ||||||
|  |  | ||||||
| public class SlashCommandInteractionMethod extends InteractionMethod { | public class SlashCommandInteractionMethod extends InteractionMethod { | ||||||
|  |  | ||||||
|     private ExecutableSlashCommandDefinition commandDefinition; |     private ExecutableSlashCommandDefinition commandDefinition; | ||||||
|  |  | ||||||
|     SlashCommandInteractionMethod(Method method, InteractionHandler handler, LibraryWrapper wrapper) { |     SlashCommandInteractionMethod(Method method, InteractionHandler handler, Marinara marinara) { | ||||||
|         super(method, handler, wrapper); |         super(method, handler, marinara); | ||||||
|         parseMethod(); |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public AnnotationParser[] getParsers() { | ||||||
|  |         return new AnnotationParser[] {  | ||||||
|  |             new SlashCommandParser(method, (x) -> { this.commandDefinition = x; } )  | ||||||
|  |         }; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public Object getParameter(Object context, int index) { |     public Object getParameter(Object context, int index) { | ||||||
|         return wrapper.convertCommandOption(context, commandDefinition.options()[index].type(), commandDefinition.options()[index].name()); |         return marinara.getWrapper().getContextObjectProvider().convertCommandOption(context, commandDefinition.options()[index].name()); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public boolean canRun(Object context) { |     public boolean canRun(Object context) { | ||||||
|         ExecutableSlashCommandDefinition other = wrapper.getCommandDefinition(context); |         ExecutableSlashCommandDefinition other = marinara.getWrapper().getCommandDefinition(context); | ||||||
|         return commandDefinition.equals(other); |         return commandDefinition.equals(other); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -40,24 +44,8 @@ public class SlashCommandInteractionMethod extends InteractionMethod { | |||||||
|         return commandDefinition; |         return commandDefinition; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private void parseMethod() { |     public void setCommandDefinition(ExecutableSlashCommandDefinition commandDefinition) { | ||||||
|         ReflectionUtil.checkValidCommandMethod(method); |         this.commandDefinition = commandDefinition; | ||||||
|  |  | ||||||
|         SlashCommand cmd = ReflectionUtil.getAnnotation(method, SlashCommand.class); |  | ||||||
|         ExecutableSlashCommandDefinition.Builder builder = new ExecutableSlashCommandDefinition.Builder(); |  | ||||||
|         builder.setApplicationCommand(cmd); |  | ||||||
|  |  | ||||||
|         if (ReflectionUtil.isAnnotationPresent(method, SubCommandGroup.class)) { |  | ||||||
|             SubCommandGroup cmdGroup = ReflectionUtil.getAnnotation(method, SubCommandGroup.class); |  | ||||||
|             builder.setSubCommandGroup(cmdGroup); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if (ReflectionUtil.isAnnotationPresent(method, SubCommand.class)) { |  | ||||||
|             SubCommand subCmd = ReflectionUtil.getAnnotation(method, SubCommand.class); |  | ||||||
|             builder.setSubCommand(subCmd); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         this.commandDefinition = builder.build(); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -0,0 +1,8 @@ | |||||||
|  | package net.tomatentum.marinara.parser; | ||||||
|  |  | ||||||
|  | import java.lang.reflect.Method; | ||||||
|  |  | ||||||
|  | public interface AnnotationParser { | ||||||
|  |     void parse(); | ||||||
|  |     Method getMethod(); | ||||||
|  | } | ||||||
| @@ -0,0 +1,36 @@ | |||||||
|  | package net.tomatentum.marinara.parser; | ||||||
|  |  | ||||||
|  | import java.lang.reflect.Method; | ||||||
|  | import java.util.function.Consumer; | ||||||
|  |  | ||||||
|  | import org.apache.logging.log4j.Logger; | ||||||
|  |  | ||||||
|  | import net.tomatentum.marinara.interaction.annotation.Button; | ||||||
|  | import net.tomatentum.marinara.util.LoggerUtil; | ||||||
|  | import net.tomatentum.marinara.util.ReflectionUtil; | ||||||
|  |  | ||||||
|  | public class ButtonParser implements AnnotationParser { | ||||||
|  |      | ||||||
|  |     private Method method; | ||||||
|  |     private Consumer<String> consumer; | ||||||
|  |  | ||||||
|  |     private Logger logger = LoggerUtil.getLogger(getClass()); | ||||||
|  |  | ||||||
|  |     public ButtonParser(Method method, Consumer<String> consumer) { | ||||||
|  |         this.method = method; | ||||||
|  |         this.consumer = consumer; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @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; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  | } | ||||||
| @@ -0,0 +1,51 @@ | |||||||
|  | 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 org.apache.logging.log4j.Logger; | ||||||
|  |  | ||||||
|  | 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; | ||||||
|  |  | ||||||
|  | public class InteractionCheckParser implements AnnotationParser { | ||||||
|  |  | ||||||
|  |     private InteractionCheckRegistry checkRegistry; | ||||||
|  |     private Method method; | ||||||
|  |     private Consumer<AppliedCheck> consumer; | ||||||
|  |  | ||||||
|  |     private Logger logger = LoggerUtil.getLogger(getClass()); | ||||||
|  |  | ||||||
|  |     public InteractionCheckParser(Method method, Consumer<AppliedCheck> consumer, InteractionCheckRegistry checkRegistry) { | ||||||
|  |         this.checkRegistry = checkRegistry; | ||||||
|  |         this.method = method; | ||||||
|  |         this.consumer = consumer; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public void parse() { | ||||||
|  |         Annotation[] annotations = method.getAnnotations(); | ||||||
|  |         Arrays.stream(annotations).forEach(this::convertAnnotation); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     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); | ||||||
|  |             } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public Method getMethod() { | ||||||
|  |         return this.method; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  | } | ||||||
| @@ -0,0 +1,71 @@ | |||||||
|  | package net.tomatentum.marinara.parser; | ||||||
|  |  | ||||||
|  | import java.lang.reflect.Method; | ||||||
|  | import java.util.function.Consumer; | ||||||
|  |  | ||||||
|  | import org.apache.logging.log4j.Logger; | ||||||
|  |  | ||||||
|  | import net.tomatentum.marinara.interaction.commands.ExecutableSlashCommandDefinition; | ||||||
|  | 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; | ||||||
|  |  | ||||||
|  | public class SlashCommandParser implements AnnotationParser { | ||||||
|  |  | ||||||
|  |     private Method method; | ||||||
|  |     private Consumer<ExecutableSlashCommandDefinition> consumer; | ||||||
|  |  | ||||||
|  |     private Logger logger = LoggerUtil.getLogger(getClass()); | ||||||
|  |  | ||||||
|  |     public SlashCommandParser(Method method, Consumer<ExecutableSlashCommandDefinition> consumer) { | ||||||
|  |         this.method = method; | ||||||
|  |         this.consumer = consumer; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public void parse() { | ||||||
|  |         this.checkValidCommandMethod(method); | ||||||
|  |  | ||||||
|  |         SlashCommand cmd = ReflectionUtil.getAnnotation(method, SlashCommand.class); | ||||||
|  |         ExecutableSlashCommandDefinition.Builder builder = new ExecutableSlashCommandDefinition.Builder(); | ||||||
|  |         builder.setApplicationCommand(cmd); | ||||||
|  |  | ||||||
|  |         if (ReflectionUtil.isAnnotationPresent(method, SubCommandGroup.class)) { | ||||||
|  |             SubCommandGroup cmdGroup = ReflectionUtil.getAnnotation(method, SubCommandGroup.class); | ||||||
|  |             builder.setSubCommandGroup(cmdGroup); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (ReflectionUtil.isAnnotationPresent(method, SubCommand.class)) { | ||||||
|  |             SubCommand subCmd = ReflectionUtil.getAnnotation(method, SubCommand.class); | ||||||
|  |             builder.setSubCommand(subCmd); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         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; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     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"); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (!ReflectionUtil.isAnnotationPresent(method, SlashCommand.class)) | ||||||
|  |             throw new RuntimeException(method.getName() + ": Missing ApplicationCommand 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"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |      | ||||||
|  | } | ||||||
| @@ -0,0 +1,40 @@ | |||||||
|  | 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(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -5,31 +5,38 @@ import java.util.ArrayList; | |||||||
| import java.util.List; | import java.util.List; | ||||||
| import java.util.Optional; | 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.InteractionHandler; | ||||||
| import net.tomatentum.marinara.interaction.InteractionType; | import net.tomatentum.marinara.interaction.InteractionType; | ||||||
| import net.tomatentum.marinara.interaction.commands.SlashCommandDefinition; | import net.tomatentum.marinara.interaction.commands.SlashCommandDefinition; | ||||||
| import net.tomatentum.marinara.interaction.commands.ExecutableSlashCommandDefinition; | import net.tomatentum.marinara.interaction.commands.ExecutableSlashCommandDefinition; | ||||||
| import net.tomatentum.marinara.interaction.methods.SlashCommandInteractionMethod; | import net.tomatentum.marinara.interaction.methods.SlashCommandInteractionMethod; | ||||||
|  | import net.tomatentum.marinara.util.LoggerUtil; | ||||||
| import net.tomatentum.marinara.interaction.methods.InteractionMethod; | import net.tomatentum.marinara.interaction.methods.InteractionMethod; | ||||||
| import net.tomatentum.marinara.wrapper.LibraryWrapper; |  | ||||||
|  |  | ||||||
| public class InteractionRegistry { | public class InteractionRegistry { | ||||||
|  |     private Logger logger = LoggerUtil.getLogger(getClass()); | ||||||
|     private List<InteractionMethod> interactionMethods; |     private List<InteractionMethod> interactionMethods; | ||||||
|     private LibraryWrapper wrapper; |     private Marinara marinara; | ||||||
|  |  | ||||||
|     public InteractionRegistry(LibraryWrapper wrapper) { |     public InteractionRegistry(Marinara marinara) { | ||||||
|         this.interactionMethods = new ArrayList<>(); |         this.interactionMethods = new ArrayList<>(); | ||||||
|         this.wrapper = wrapper; |         this.marinara = marinara; | ||||||
|         wrapper.subscribeInteractions(this::handle); |         marinara.getWrapper().subscribeInteractions(this::handle); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public void addInteractions(InteractionHandler interactionHandler) { |     public void addInteractions(InteractionHandler interactionHandler) { | ||||||
|         for (Method method : interactionHandler.getClass().getMethods()) { |         for (Method method : interactionHandler.getClass().getMethods()) { | ||||||
|             InteractionMethod iMethod = InteractionMethod.create(method, interactionHandler, wrapper); |             InteractionMethod iMethod = InteractionMethod.create(method, interactionHandler, marinara); | ||||||
|             if (iMethod != null) |             if (iMethod != null) { | ||||||
|                 this.interactionMethods.add(iMethod); |                 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() { |     public void registerCommands() { | ||||||
|         List<SlashCommandDefinition> defs = new ArrayList<>(); |         List<SlashCommandDefinition> defs = new ArrayList<>(); | ||||||
| @@ -46,16 +53,26 @@ public class InteractionRegistry { | |||||||
|                 appDef.get().addExecutableCommand(def); |                 appDef.get().addExecutableCommand(def); | ||||||
|             else |             else | ||||||
|                 defs.add(new SlashCommandDefinition(def.applicationCommand()).addExecutableCommand(def)); |                 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() | ||||||
|  |                 ); | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         wrapper.registerSlashCommands(defs.toArray(new SlashCommandDefinition[0])); |         marinara.getWrapper().registerSlashCommands(defs.toArray(SlashCommandDefinition[]::new)); | ||||||
|  |         logger.info("Registered all SlashCommands"); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public void handle(Object context) { |     public void handle(Object context) { | ||||||
|  |         InteractionType type = marinara.getWrapper().getInteractionType(context); | ||||||
|  |         logger.debug("Received {} interaction ", context); | ||||||
|         interactionMethods.forEach((m) -> { |         interactionMethods.forEach((m) -> { | ||||||
|             InteractionType type = wrapper.getInteractionType(context.getClass()); |             if (m.getType().equals(type) && m.canRun(context)) { | ||||||
|             if (m.getType().equals(type)) |                 logger.info("Running {} interaction using {}\ncontext: {}", type, m.getMethod().toString(), context.toString()); | ||||||
|                 m.run(context); |                 m.run(context); | ||||||
|  |             } | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -0,0 +1,24 @@ | |||||||
|  | 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()); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -2,9 +2,10 @@ package net.tomatentum.marinara.util; | |||||||
|  |  | ||||||
| import java.lang.annotation.Annotation; | import java.lang.annotation.Annotation; | ||||||
| import java.lang.reflect.Method; | import java.lang.reflect.Method; | ||||||
|  | import java.util.ArrayList; | ||||||
| import net.tomatentum.marinara.interaction.commands.annotation.SlashCommand; | import java.util.Arrays; | ||||||
| import net.tomatentum.marinara.interaction.commands.annotation.SubCommand; | import java.util.List; | ||||||
|  | import java.util.Objects; | ||||||
|  |  | ||||||
| public final class ReflectionUtil { | public final class ReflectionUtil { | ||||||
|  |  | ||||||
| @@ -21,19 +22,86 @@ public final class ReflectionUtil { | |||||||
|             method.getDeclaringClass().getAnnotation(annotationClass); |             method.getDeclaringClass().getAnnotation(annotationClass); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public static void checkValidCommandMethod(Method method) { |     public static int getCastDepth(Class<?> child, Class<?> parent) { | ||||||
|         if (method.isAnnotationPresent(SlashCommand.class) &&  |  | ||||||
|             method.getDeclaringClass().isAnnotationPresent(SlashCommand.class)) { |         if (parent.equals(Object.class)) | ||||||
|             throw new RuntimeException(method.getName() + ": Can't have ApplicationCommand Annotation on Class and Method"); |             return Integer.MAX_VALUE; | ||||||
|  |  | ||||||
|  |         if (!parent.isAssignableFrom(child)) { | ||||||
|  |             throw new IllegalArgumentException("The specified class is not a child class of the specified parent."); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         if (!isAnnotationPresent(method, SlashCommand.class)) |         int depth = 0; | ||||||
|             throw new RuntimeException(method.getName() + ": Missing ApplicationCommand Annotation on either Class or Method"); |         Class<?> curr = child; | ||||||
|  |         List<Class<?>> parents = new ArrayList<>(); | ||||||
|  |  | ||||||
|         if ((method.isAnnotationPresent(SubCommand.class) &&  |         while (!curr.equals(parent)) { | ||||||
|             !isAnnotationPresent(method, SlashCommand.class))) { |             depth++; | ||||||
|             throw new RuntimeException(method.getName() + ": Missing ApplicationCommand Annotation on either Method or Class"); |             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,11 @@ | |||||||
|  | package net.tomatentum.marinara.wrapper; | ||||||
|  |  | ||||||
|  | public interface ContextObjectProvider { | ||||||
|  |  | ||||||
|  |     public Object convertCommandOption(Object context, String optionName); | ||||||
|  |  | ||||||
|  |     public Object getComponentContextObject(Object context, Class<?> type); | ||||||
|  |     public Object getInteractionContextObject(Object context, Class<?> type); | ||||||
|  |  | ||||||
|  |     public Object getAutocompleteFocusedOption(Object context); | ||||||
|  | } | ||||||
| @@ -6,7 +6,6 @@ import java.util.function.Consumer; | |||||||
|  |  | ||||||
| import net.tomatentum.marinara.interaction.commands.SlashCommandDefinition; | import net.tomatentum.marinara.interaction.commands.SlashCommandDefinition; | ||||||
| import net.tomatentum.marinara.interaction.commands.ExecutableSlashCommandDefinition; | import net.tomatentum.marinara.interaction.commands.ExecutableSlashCommandDefinition; | ||||||
| import net.tomatentum.marinara.interaction.commands.option.SlashCommandOptionType; |  | ||||||
| import net.tomatentum.marinara.interaction.InteractionType; | import net.tomatentum.marinara.interaction.InteractionType; | ||||||
|  |  | ||||||
| public abstract class LibraryWrapper { | public abstract class LibraryWrapper { | ||||||
| @@ -17,7 +16,6 @@ public abstract class LibraryWrapper { | |||||||
|         interactionSubscriber = new ArrayList<>(); |         interactionSubscriber = new ArrayList<>(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|     public void handleInteraction(Object context) { |     public void handleInteraction(Object context) { | ||||||
|         interactionSubscriber.forEach((o) -> o.accept(context)); |         interactionSubscriber.forEach((o) -> o.accept(context)); | ||||||
|     } |     } | ||||||
| @@ -29,12 +27,13 @@ public abstract class LibraryWrapper { | |||||||
|         interactionSubscriber.remove(consumer); |         interactionSubscriber.remove(consumer); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public abstract InteractionType getInteractionType(Class<?> clazz); |     public abstract InteractionType getInteractionType(Object context); | ||||||
|  |  | ||||||
|     public abstract void registerSlashCommands(SlashCommandDefinition[] defs);  |     public abstract void registerSlashCommands(SlashCommandDefinition[] defs);  | ||||||
|     public abstract Object convertCommandOption(Object context, SlashCommandOptionType type, String optionName); |  | ||||||
|     public abstract ExecutableSlashCommandDefinition getCommandDefinition(Object context); |     public abstract ExecutableSlashCommandDefinition getCommandDefinition(Object context); | ||||||
|  |  | ||||||
|     public abstract String getButtonId(Object context); |     public abstract String getButtonId(Object context); | ||||||
|     public abstract Object getComponentContextObject(Object context, Class<?> type); |  | ||||||
|  |     public abstract ContextObjectProvider getContextObjectProvider(); | ||||||
|  |  | ||||||
| } | } | ||||||
| @@ -13,5 +13,9 @@ plugins { | |||||||
| rootProject.name = "Marinara" | rootProject.name = "Marinara" | ||||||
| include(":lib") | include(":lib") | ||||||
| include(":wrapper-javacord") | include(":wrapper-javacord") | ||||||
|  | include(":wrapper-discord4j") | ||||||
|  |  | ||||||
|  |  | ||||||
| project(":wrapper-javacord").projectDir = file("wrapper/javacord") | project(":wrapper-javacord").projectDir = file("wrapper/javacord") | ||||||
|  | project(":wrapper-discord4j").projectDir = file("wrapper/discord4j") | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										41
									
								
								wrapper/discord4j/build.gradle.kts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								wrapper/discord4j/build.gradle.kts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | |||||||
|  | /* | ||||||
|  |  * This file was generated by the Gradle 'init' task. | ||||||
|  |  * | ||||||
|  |  * This generated file contains a sample Java library project to get you started. | ||||||
|  |  * For more details on building Java & JVM projects, please refer to https://docs.gradle.org/8.8/userguide/building_java_projects.html in the Gradle documentation. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | plugins { | ||||||
|  |     // Apply the java-library plugin for API and implementation separation. | ||||||
|  |     `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.discord4j) { | ||||||
|  |         exclude(module="discord4j-voice") | ||||||
|  |     } | ||||||
|  |     implementation(libs.geantyref) | ||||||
|  |     implementation(project(":lib")) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Apply a specific Java toolchain to ease working on different environments. | ||||||
|  | java { | ||||||
|  |     toolchain { | ||||||
|  |         languageVersion = JavaLanguageVersion.of(23) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | tasks.named<Test>("test") { | ||||||
|  |     // Use JUnit Platform for unit tests. | ||||||
|  |     useJUnitPlatform() | ||||||
|  | } | ||||||
| @@ -0,0 +1,102 @@ | |||||||
|  | package net.tomatentum.marinara.wrapper.discord4j; | ||||||
|  |  | ||||||
|  | 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.object.command.ApplicationCommandInteractionOption; | ||||||
|  | import net.tomatentum.marinara.interaction.commands.option.SlashCommandOptionType; | ||||||
|  | import net.tomatentum.marinara.wrapper.ContextObjectProvider; | ||||||
|  |  | ||||||
|  | public class Discord4JContextObjectProvider implements ContextObjectProvider { | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public Object convertCommandOption(Object context, String optionName) { | ||||||
|  |         if (!(context instanceof ChatInputInteractionEvent)) | ||||||
|  |             return null; | ||||||
|  |         ChatInputInteractionEvent interactionEvent = (ChatInputInteractionEvent) context; | ||||||
|  |  | ||||||
|  |         List<ApplicationCommandInteractionOption> subOptions = Discord4JWrapper.SUB_FILTER.apply(interactionEvent.getOptions()); | ||||||
|  |  | ||||||
|  |         if (subOptions.isEmpty()) | ||||||
|  |             return getOptionValue(interactionEvent.getOption(optionName).get()); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |         ApplicationCommandInteractionOption subCommandOption = interactionEvent.getOptions().getFirst(); | ||||||
|  |         subOptions = Discord4JWrapper.SUB_FILTER.apply(subCommandOption.getOptions()); | ||||||
|  |  | ||||||
|  |         if (!subOptions.isEmpty()) | ||||||
|  |             subCommandOption = subOptions.getFirst(); | ||||||
|  |  | ||||||
|  |         return getOptionValue(interactionEvent.getOption(optionName).get()); | ||||||
|  |  | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private Object getOptionValue(ApplicationCommandInteractionOption option) { | ||||||
|  |         if (!option.getValue().isPresent()) | ||||||
|  |             return null; | ||||||
|  |         SlashCommandOptionType type = getOptionType(option); | ||||||
|  |  | ||||||
|  |         switch (type) { | ||||||
|  |             case ATTACHMENT: | ||||||
|  |                 return option.getValue().get().asAttachment(); | ||||||
|  |             case BOOLEAN: | ||||||
|  |                 return option.getValue().get().asBoolean(); | ||||||
|  |             case CHANNEL: | ||||||
|  |                 return option.getValue().get().asChannel(); | ||||||
|  |             case DOUBLE: | ||||||
|  |                 return option.getValue().get().asDouble(); | ||||||
|  |             case INTEGER: | ||||||
|  |                 return option.getValue().get().asLong(); | ||||||
|  |             case MENTIONABLE: | ||||||
|  |                 return option.getValue().get().asSnowflake(); | ||||||
|  |             case ROLE: | ||||||
|  |                 return option.getValue().get().asRole(); | ||||||
|  |             case STRING: | ||||||
|  |                 return option.getValue().get().asString(); | ||||||
|  |             case USER: | ||||||
|  |                 return option.getValue().get().asUser(); | ||||||
|  |             default: | ||||||
|  |                 return null; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private SlashCommandOptionType getOptionType(ApplicationCommandInteractionOption option) { | ||||||
|  |         return SlashCommandOptionType.fromValue(option.getType().getValue()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public Object getComponentContextObject(Object context, Class<?> type) { | ||||||
|  |         ComponentInteractionEvent componentInteractionEvent = (ComponentInteractionEvent) context; | ||||||
|  |         switch (type.getName()) { | ||||||
|  |             case "discord4j.core.object.entity.Message": | ||||||
|  |                 return componentInteractionEvent.getMessage(); | ||||||
|  |             default: | ||||||
|  |                 return getInteractionContextObject(context, type); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public Object getInteractionContextObject(Object context, Class<?> type) { | ||||||
|  |         ComponentInteractionEvent componentInteractionEvent = (ComponentInteractionEvent) context; | ||||||
|  |         switch (type.getName()) { | ||||||
|  |             case "discord4j.core.object.entity.channel.MessageChannel": | ||||||
|  |                 return componentInteractionEvent.getInteraction().getChannel().block(); | ||||||
|  |             case "discord4j.core.object.entity.Guild": | ||||||
|  |                 return componentInteractionEvent.getInteraction().getGuild().block(); | ||||||
|  |             case "discord4j.core.object.entity.Member": | ||||||
|  |                 return componentInteractionEvent.getInteraction().getMember().orElse(null); | ||||||
|  |             case "discord4j.core.object.entity.User": | ||||||
|  |                 return componentInteractionEvent.getInteraction().getUser(); | ||||||
|  |         } | ||||||
|  |         return null; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public Object getAutocompleteFocusedOption(Object context) { | ||||||
|  |         ChatInputAutoCompleteEvent interaction = (ChatInputAutoCompleteEvent) context; | ||||||
|  |         return getOptionValue(interaction.getFocusedOption()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,201 @@ | |||||||
|  | package net.tomatentum.marinara.wrapper.discord4j; | ||||||
|  |  | ||||||
|  | import java.util.ArrayList; | ||||||
|  | import java.util.Arrays; | ||||||
|  | import java.util.HashMap; | ||||||
|  | import java.util.List; | ||||||
|  | import java.util.Map; | ||||||
|  | import java.util.function.Function; | ||||||
|  |  | ||||||
|  | import org.apache.logging.log4j.Logger; | ||||||
|  |  | ||||||
|  | import discord4j.core.GatewayDiscordClient; | ||||||
|  | import discord4j.core.event.domain.interaction.ButtonInteractionEvent; | ||||||
|  | import discord4j.core.event.domain.interaction.ChatInputAutoCompleteEvent; | ||||||
|  | import discord4j.core.event.domain.interaction.ChatInputInteractionEvent; | ||||||
|  | import discord4j.core.event.domain.interaction.InteractionCreateEvent; | ||||||
|  | import discord4j.core.object.command.ApplicationCommandInteractionOption; | ||||||
|  | import discord4j.core.object.command.ApplicationCommandOption.Type; | ||||||
|  | import discord4j.discordjson.json.ApplicationCommandOptionChoiceData; | ||||||
|  | import discord4j.discordjson.json.ApplicationCommandOptionData; | ||||||
|  | import discord4j.discordjson.json.ApplicationCommandRequest; | ||||||
|  |  | ||||||
|  | import io.leangen.geantyref.AnnotationFormatException; | ||||||
|  | import io.leangen.geantyref.TypeFactory; | ||||||
|  | import net.tomatentum.marinara.interaction.InteractionType; | ||||||
|  | import net.tomatentum.marinara.interaction.commands.ExecutableSlashCommandDefinition; | ||||||
|  | import net.tomatentum.marinara.interaction.commands.SlashCommandDefinition; | ||||||
|  | import net.tomatentum.marinara.interaction.commands.annotation.SlashCommand; | ||||||
|  | import net.tomatentum.marinara.interaction.commands.annotation.SlashCommandOption; | ||||||
|  | import net.tomatentum.marinara.interaction.commands.annotation.SlashCommandOptionChoice; | ||||||
|  | import net.tomatentum.marinara.interaction.commands.annotation.SubCommand; | ||||||
|  | import net.tomatentum.marinara.interaction.commands.annotation.SubCommandGroup; | ||||||
|  | import net.tomatentum.marinara.util.LoggerUtil; | ||||||
|  | import net.tomatentum.marinara.wrapper.ContextObjectProvider; | ||||||
|  | import net.tomatentum.marinara.wrapper.LibraryWrapper; | ||||||
|  | import reactor.core.publisher.Mono; | ||||||
|  |  | ||||||
|  | public class Discord4JWrapper extends LibraryWrapper { | ||||||
|  |  | ||||||
|  |     public static final Function<List<ApplicationCommandInteractionOption>, 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) -> | ||||||
|  |             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 Logger logger = LoggerUtil.getLogger(getClass()); | ||||||
|  |  | ||||||
|  |     public Discord4JWrapper(GatewayDiscordClient api) { | ||||||
|  |         this.api = api; | ||||||
|  |         this.contextObjectProvider = new Discord4JContextObjectProvider(); | ||||||
|  |         api.on(InteractionCreateEvent.class) | ||||||
|  |             .subscribe(event -> handleInteraction(event)); | ||||||
|  |         Mono.just("test").subscribe(logger::debug); | ||||||
|  |              | ||||||
|  |         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; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @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(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public ContextObjectProvider getContextObjectProvider() { | ||||||
|  |         return this.contextObjectProvider; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  | } | ||||||
| @@ -29,7 +29,7 @@ dependencies { | |||||||
| // Apply a specific Java toolchain to ease working on different environments. | // Apply a specific Java toolchain to ease working on different environments. | ||||||
| java { | java { | ||||||
|     toolchain { |     toolchain { | ||||||
|         languageVersion = JavaLanguageVersion.of(21) |         languageVersion = JavaLanguageVersion.of(23) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -0,0 +1,110 @@ | |||||||
|  | package net.tomatentum.marinara.wrapper.javacord; | ||||||
|  |  | ||||||
|  | import org.javacord.api.interaction.AutocompleteInteraction; | ||||||
|  | import org.javacord.api.interaction.ButtonInteraction; | ||||||
|  | import org.javacord.api.interaction.SlashCommandInteraction; | ||||||
|  | import org.javacord.api.interaction.SlashCommandInteractionOption; | ||||||
|  | import org.javacord.api.interaction.SlashCommandOptionType; | ||||||
|  |  | ||||||
|  | import net.tomatentum.marinara.wrapper.ContextObjectProvider; | ||||||
|  |  | ||||||
|  | public class JavacordContextObjectProvider implements ContextObjectProvider { | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public Object convertCommandOption(Object context, String optionName) { | ||||||
|  |         if (!(context instanceof SlashCommandInteraction)) | ||||||
|  |             return null; | ||||||
|  |         SlashCommandInteraction interaction = (SlashCommandInteraction) context; | ||||||
|  |         if (!interaction.getArguments().isEmpty()) | ||||||
|  |             return getOptionValue(interaction.getOptionByName(optionName).get()); | ||||||
|  |  | ||||||
|  |         SlashCommandInteractionOption subCommandOption = interaction.getOptions().getFirst(); | ||||||
|  |  | ||||||
|  |         if (!subCommandOption.getOptions().isEmpty()) | ||||||
|  |             subCommandOption = subCommandOption.getOptions().getFirst(); | ||||||
|  |  | ||||||
|  |         return getOptionValue(subCommandOption.getOptionByName(optionName).get()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private Object getOptionValue(SlashCommandInteractionOption option) { | ||||||
|  |         SlashCommandOptionType type = getOptionType(option); | ||||||
|  |         switch (type) { | ||||||
|  |             case ATTACHMENT: | ||||||
|  |                 return option.getAttachmentValue().get(); | ||||||
|  |             case BOOLEAN: | ||||||
|  |                 return option.getBooleanValue().get(); | ||||||
|  |             case CHANNEL: | ||||||
|  |                 return option.getChannelValue().get(); | ||||||
|  |             case DECIMAL: | ||||||
|  |                 return option.getDecimalValue().get(); | ||||||
|  |             case LONG: | ||||||
|  |                 return option.getLongValue().get(); | ||||||
|  |             case MENTIONABLE: | ||||||
|  |                 return option.requestMentionableValue().get(); | ||||||
|  |             case ROLE: | ||||||
|  |                 return option.getRoleValue().get(); | ||||||
|  |             case STRING: | ||||||
|  |                 return option.getStringValue().get(); | ||||||
|  |             case USER: | ||||||
|  |                 return option.requestUserValue().get(); | ||||||
|  |             default: | ||||||
|  |                 return null; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     private SlashCommandOptionType getOptionType(SlashCommandInteractionOption option) { | ||||||
|  |         if (option.getAttachmentValue().isPresent()) | ||||||
|  |             return SlashCommandOptionType.ATTACHMENT; | ||||||
|  |         if (option.getBooleanValue().isPresent()) | ||||||
|  |             return SlashCommandOptionType.BOOLEAN; | ||||||
|  |         if (option.getChannelValue().isPresent()) | ||||||
|  |             return SlashCommandOptionType.CHANNEL; | ||||||
|  |         if (option.getDecimalValue().isPresent()) | ||||||
|  |             return SlashCommandOptionType.DECIMAL; | ||||||
|  |         if (option.getLongValue().isPresent()) | ||||||
|  |             return SlashCommandOptionType.LONG; | ||||||
|  |         if (option.requestMentionableValue().isPresent()) | ||||||
|  |             return SlashCommandOptionType.MENTIONABLE; | ||||||
|  |         if (option.getRoleValue().isPresent()) | ||||||
|  |             return SlashCommandOptionType.ROLE; | ||||||
|  |         if (option.getStringValue().isPresent()) | ||||||
|  |             return SlashCommandOptionType.STRING; | ||||||
|  |         if (option.requestUserValue().isPresent()) | ||||||
|  |             return SlashCommandOptionType.USER; | ||||||
|  |  | ||||||
|  |         return SlashCommandOptionType.UNKNOWN; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public Object getComponentContextObject(Object context, Class<?> type) { | ||||||
|  |         ButtonInteraction button = (ButtonInteraction) context; | ||||||
|  |         switch (type.getName()) { | ||||||
|  |             case "org.javacord.api.entity.message.Message": | ||||||
|  |                 return button.getMessage(); | ||||||
|  |             default: | ||||||
|  |                 return getInteractionContextObject(context, type); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public Object getInteractionContextObject(Object context, Class<?> type) { | ||||||
|  |         ButtonInteraction button = (ButtonInteraction) context; | ||||||
|  |         switch (type.getName()) { | ||||||
|  |             case "org.javacord.api.entity.channel.TextChannel": | ||||||
|  |                 return button.getChannel().orElse(null); | ||||||
|  |             case "org.javacord.api.entity.server.Server": | ||||||
|  |                 return button.getServer().orElse(null); | ||||||
|  |             case "org.javacord.api.entity.user.User": | ||||||
|  |                 return button.getUser(); | ||||||
|  |         } | ||||||
|  |         return null; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public Object getAutocompleteFocusedOption(Object context) { | ||||||
|  |         AutocompleteInteraction interaction = (AutocompleteInteraction) context; | ||||||
|  |         return getOptionValue(interaction.getFocusedOption()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -8,12 +8,17 @@ import java.util.List; | |||||||
| import java.util.Map; | import java.util.Map; | ||||||
| import java.util.Set; | import java.util.Set; | ||||||
|  |  | ||||||
|  | import org.apache.logging.log4j.Logger; | ||||||
| import org.javacord.api.DiscordApi; | import org.javacord.api.DiscordApi; | ||||||
| import org.javacord.api.interaction.ApplicationCommandInteraction; | import org.javacord.api.interaction.ApplicationCommandInteraction; | ||||||
|  | import org.javacord.api.interaction.AutocompleteInteraction; | ||||||
| import org.javacord.api.interaction.ButtonInteraction; | import org.javacord.api.interaction.ButtonInteraction; | ||||||
| import org.javacord.api.interaction.SlashCommandBuilder; | import org.javacord.api.interaction.SlashCommandBuilder; | ||||||
| import org.javacord.api.interaction.SlashCommandInteraction; | import org.javacord.api.interaction.SlashCommandInteraction; | ||||||
| import org.javacord.api.interaction.SlashCommandInteractionOption; | import org.javacord.api.interaction.SlashCommandInteractionOption; | ||||||
|  | import org.javacord.api.interaction.SlashCommandOptionBuilder; | ||||||
|  | import org.javacord.api.interaction.SlashCommandOptionChoiceBuilder; | ||||||
|  | import org.javacord.api.interaction.SlashCommandOptionType; | ||||||
|  |  | ||||||
| import io.leangen.geantyref.AnnotationFormatException; | import io.leangen.geantyref.AnnotationFormatException; | ||||||
| import io.leangen.geantyref.TypeFactory; | import io.leangen.geantyref.TypeFactory; | ||||||
| @@ -22,27 +27,35 @@ import net.tomatentum.marinara.interaction.commands.ExecutableSlashCommandDefini | |||||||
| import net.tomatentum.marinara.interaction.commands.SlashCommandDefinition; | import net.tomatentum.marinara.interaction.commands.SlashCommandDefinition; | ||||||
| import net.tomatentum.marinara.interaction.commands.annotation.SlashCommand; | import net.tomatentum.marinara.interaction.commands.annotation.SlashCommand; | ||||||
| import net.tomatentum.marinara.interaction.commands.annotation.SlashCommandOption; | import net.tomatentum.marinara.interaction.commands.annotation.SlashCommandOption; | ||||||
|  | import net.tomatentum.marinara.interaction.commands.annotation.SlashCommandOptionChoice; | ||||||
| import net.tomatentum.marinara.interaction.commands.annotation.SubCommand; | import net.tomatentum.marinara.interaction.commands.annotation.SubCommand; | ||||||
| import net.tomatentum.marinara.interaction.commands.annotation.SubCommandGroup; | import net.tomatentum.marinara.interaction.commands.annotation.SubCommandGroup; | ||||||
| import net.tomatentum.marinara.interaction.commands.option.SlashCommandOptionType; | import net.tomatentum.marinara.wrapper.ContextObjectProvider; | ||||||
|  | import net.tomatentum.marinara.util.LoggerUtil; | ||||||
| import net.tomatentum.marinara.wrapper.LibraryWrapper; | import net.tomatentum.marinara.wrapper.LibraryWrapper; | ||||||
|  |  | ||||||
| public class JavacordWrapper extends LibraryWrapper { | public class JavacordWrapper extends LibraryWrapper { | ||||||
|  |  | ||||||
|     private DiscordApi api; |     private DiscordApi api; | ||||||
|  |     private JavacordContextObjectProvider contextObjectProvider; | ||||||
|  |      | ||||||
|  |     private Logger logger = LoggerUtil.getLogger(getClass()); | ||||||
|  |  | ||||||
|     public JavacordWrapper(DiscordApi api) { |     public JavacordWrapper(DiscordApi api) { | ||||||
|         this.api = api; |         this.api = api; | ||||||
|  |         this.contextObjectProvider = new JavacordContextObjectProvider(); | ||||||
|         api.addInteractionCreateListener((e) -> handleInteraction(e.getInteraction())); |         api.addInteractionCreateListener((e) -> handleInteraction(e.getInteraction())); | ||||||
|  |         logger.info("Javacord wrapper loaded!"); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public InteractionType getInteractionType(Class<?> clazz) { |     public InteractionType getInteractionType(Object context) { | ||||||
|         if (ApplicationCommandInteraction.class.isAssignableFrom(clazz)) |         if (AutocompleteInteraction.class.isAssignableFrom(context.getClass())) | ||||||
|  |             return InteractionType.AUTOCOMPLETE; | ||||||
|  |         if (ApplicationCommandInteraction.class.isAssignableFrom(context.getClass())) | ||||||
|             return InteractionType.COMMAND; |             return InteractionType.COMMAND; | ||||||
|         if (ButtonInteraction.class.isAssignableFrom(clazz)) |         if (ButtonInteraction.class.isAssignableFrom(context.getClass())) | ||||||
|             return InteractionType.BUTTON; |             return InteractionType.BUTTON; | ||||||
|  |  | ||||||
|         return null; |         return null; | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -67,22 +80,6 @@ public class JavacordWrapper extends LibraryWrapper { | |||||||
|         api.bulkOverwriteGlobalApplicationCommands(globalCommands); |         api.bulkOverwriteGlobalApplicationCommands(globalCommands); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public Object convertCommandOption(Object context, SlashCommandOptionType type, String optionName) { |  | ||||||
|         if (!(context instanceof SlashCommandInteraction)) |  | ||||||
|             return null; |  | ||||||
|         SlashCommandInteraction interaction = (SlashCommandInteraction) context; |  | ||||||
|         if (!interaction.getArguments().isEmpty()) |  | ||||||
|             return getOptionValue(interaction.getOptionByName(optionName).get(), type); |  | ||||||
|  |  | ||||||
|         SlashCommandInteractionOption subCommandOption = interaction.getOptions().getFirst(); |  | ||||||
|  |  | ||||||
|         if (!subCommandOption.getOptions().isEmpty()) |  | ||||||
|             subCommandOption = subCommandOption.getOptions().getFirst(); |  | ||||||
|  |  | ||||||
|         return getOptionValue(subCommandOption.getOptionByName(optionName).get(), type); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public ExecutableSlashCommandDefinition getCommandDefinition(Object context) { |     public ExecutableSlashCommandDefinition getCommandDefinition(Object context) { | ||||||
|         if (!(context instanceof SlashCommandInteraction)) |         if (!(context instanceof SlashCommandInteraction)) | ||||||
| @@ -93,13 +90,15 @@ public class JavacordWrapper extends LibraryWrapper { | |||||||
|         List<SlashCommandInteractionOption> options = interaction.getOptions(); |         List<SlashCommandInteractionOption> options = interaction.getOptions(); | ||||||
|         try { |         try { | ||||||
|             builder.setApplicationCommand(TypeFactory.annotation(SlashCommand.class, Map.of("name", interaction.getCommandName()))); |             builder.setApplicationCommand(TypeFactory.annotation(SlashCommand.class, Map.of("name", interaction.getCommandName()))); | ||||||
|  |             if (!options.isEmpty()) { | ||||||
|                 if (!options.getFirst().getArguments().isEmpty()) { |                 if (!options.getFirst().getArguments().isEmpty()) { | ||||||
|                     builder.setSubCommandGroup(TypeFactory.annotation(SubCommandGroup.class, Map.of("name", options.getFirst().getName()))); |                     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()))); |                     builder.setSubCommand(TypeFactory.annotation(SubCommand.class, Map.of("name", options.getFirst().getOptions().getFirst().getName()))); | ||||||
|                 }else |                 }else | ||||||
|                     builder.setSubCommand(TypeFactory.annotation(SubCommand.class, Map.of("name", options.getFirst().getName()))); |                     builder.setSubCommand(TypeFactory.annotation(SubCommand.class, Map.of("name", options.getFirst().getName()))); | ||||||
|  |             } | ||||||
|         } catch (AnnotationFormatException e) { |         } catch (AnnotationFormatException e) { | ||||||
|             e.printStackTrace(); |             logger.fatal(e); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         return builder.build(); |         return builder.build(); | ||||||
| @@ -120,44 +119,53 @@ public class JavacordWrapper extends LibraryWrapper { | |||||||
|  |  | ||||||
|     private org.javacord.api.interaction.SlashCommandOption convertSubCommandGroupDef(SlashCommandDefinition def, SubCommandGroup subGroup) { |     private org.javacord.api.interaction.SlashCommandOption convertSubCommandGroupDef(SlashCommandDefinition def, SubCommandGroup subGroup) { | ||||||
|         SubCommand[] subCommands = def.getSubCommands(subGroup.name()); |         SubCommand[] subCommands = def.getSubCommands(subGroup.name()); | ||||||
|         org.javacord.api.interaction.SlashCommandOption[] convertedSubCommands = (org.javacord.api.interaction.SlashCommandOption[]) Arrays.stream(subCommands).map(this::convertSubCommandDef).toArray(); |         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(), Arrays.asList(convertedSubCommands)); |         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) { |     private org.javacord.api.interaction.SlashCommandOption convertSubCommandDef(SubCommand sub) { | ||||||
|         List<org.javacord.api.interaction.SlashCommandOption> convertedOptions = new ArrayList<>(); |         List<org.javacord.api.interaction.SlashCommandOption> convertedOptions = Arrays.stream(sub.options()).map(this::convertOptionDef).toList(); | ||||||
|         Arrays.stream(sub.options()).map(this::convertOptionDef).forEach(convertedOptions::add); |         return org.javacord.api.interaction.SlashCommandOption.createWithOptions( | ||||||
|         return org.javacord.api.interaction.SlashCommandOption.createWithOptions(org.javacord.api.interaction.SlashCommandOptionType.SUB_COMMAND, sub.name(), sub.description(), convertedOptions); |             org.javacord.api.interaction.SlashCommandOptionType.SUB_COMMAND,  | ||||||
|  |             sub.name(),  | ||||||
|  |             sub.description(),  | ||||||
|  |             convertedOptions); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private org.javacord.api.interaction.SlashCommandOption convertOptionDef(SlashCommandOption option) { |     private org.javacord.api.interaction.SlashCommandOption convertOptionDef(SlashCommandOption option) { | ||||||
|         org.javacord.api.interaction.SlashCommandOptionType type = Enum.valueOf(org.javacord.api.interaction.SlashCommandOptionType.class, option.type().toString()); |         SlashCommandOptionType type = SlashCommandOptionType.fromValue(option.type().getValue()); | ||||||
|         return org.javacord.api.interaction.SlashCommandOption.create(type, option.name(), option.description(), option.required()); |         SlashCommandOptionBuilder builder = new SlashCommandOptionBuilder(); | ||||||
|  |         builder | ||||||
|  |             .setType(type) | ||||||
|  |             .setName(option.name()) | ||||||
|  |             .setDescription(option.description()) | ||||||
|  |             .setRequired(option.required()) | ||||||
|  |             .setAutocompletable(option.autocomplete()) | ||||||
|  |             .setChoices(convertChoices(option)); | ||||||
|  |          | ||||||
|  |         return builder.build(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private Object getOptionValue(SlashCommandInteractionOption option, SlashCommandOptionType type) { |     private List<org.javacord.api.interaction.SlashCommandOptionChoice> convertChoices(SlashCommandOption option) { | ||||||
|         switch (type) { |         List<org.javacord.api.interaction.SlashCommandOptionChoice> convertedChoices = new ArrayList<>(); | ||||||
|             case ATTACHMENT: |         for (SlashCommandOptionChoice choice : ExecutableSlashCommandDefinition.getActualChoices(option)) { | ||||||
|                 return option.getAttachmentValue().get(); |             SlashCommandOptionChoiceBuilder builder = new SlashCommandOptionChoiceBuilder(); | ||||||
|             case BOOLEAN: |             builder.setName(choice.name()); | ||||||
|                 return option.getBooleanValue().get(); |             if (choice.longValue() != Long.MAX_VALUE) | ||||||
|             case CHANNEL: |                 builder.setValue(choice.longValue()); | ||||||
|                 return option.getChannelValue().get(); |             /* | ||||||
|             case DECIMAL: |             not yet available | ||||||
|                 return option.getDecimalValue().get(); |             if (choice.doubleValue() != Double.MAX_VALUE) | ||||||
|             case LONG: |                 builder.setValue(choice.doubleValue()); | ||||||
|                 return option.getLongValue().get(); |             */ | ||||||
|             case MENTIONABLE: |             if (!choice.stringValue().isEmpty()) | ||||||
|                 return option.getMentionableValue().get(); |                 builder.setValue(choice.stringValue()); | ||||||
|             case ROLE: |  | ||||||
|                 return option.getRoleValue().get(); |  | ||||||
|             case STRING: |  | ||||||
|                 return option.getStringValue().get(); |  | ||||||
|             case USER: |  | ||||||
|                 return option.getUserValue().get(); |  | ||||||
|             default: |  | ||||||
|                 return null; |  | ||||||
|         } |         } | ||||||
|  |         return convertedChoices; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
| @@ -167,20 +175,8 @@ public class JavacordWrapper extends LibraryWrapper { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public Object getComponentContextObject(Object context, Class<?> type) { |     public ContextObjectProvider getContextObjectProvider() { | ||||||
|         ButtonInteraction button = (ButtonInteraction) context; |         return contextObjectProvider; | ||||||
|         switch (type.getName()) { |  | ||||||
|             case "org.javacord.api.entity.channel.TextChannel": |  | ||||||
|                 return button.getChannel().orElse(null); |  | ||||||
|             case "org.javacord.api.entity.message.Message": |  | ||||||
|                 return button.getMessage(); |  | ||||||
|             case "org.javacord.api.entity.server.Server": |  | ||||||
|                 return button.getServer().orElse(null); |  | ||||||
|             case "org.javacord.api.entity.user.User": |  | ||||||
|                 return button.getUser(); |  | ||||||
|     } |     } | ||||||
|         return null; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -0,0 +1,41 @@ | |||||||
|  | package net.tomatentum.marinara.wrapper.javacord.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 org.javacord.api.entity.permission.PermissionType; | ||||||
|  | import org.javacord.api.entity.server.Server; | ||||||
|  | import org.javacord.api.interaction.InteractionBase; | ||||||
|  |  | ||||||
|  | import net.tomatentum.marinara.checks.InteractionCheck; | ||||||
|  |  | ||||||
|  | public class PermissionCheck implements InteractionCheck<PermissionCheck.HasPermission> { | ||||||
|  |  | ||||||
|  |     @Retention(RetentionPolicy.RUNTIME) | ||||||
|  |     @Target(ElementType.METHOD) | ||||||
|  |     public static @interface HasPermission { | ||||||
|  |         public PermissionType[] value(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public boolean preExec(Object context, HasPermission annotation) { | ||||||
|  |         throw new UnsupportedOperationException("Unimplemented method 'preExec'"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public boolean preExec(InteractionBase context, HasPermission annotation) { | ||||||
|  |         Optional<Server> server = context.getServer(); | ||||||
|  |         if (!server.isPresent()) | ||||||
|  |             return false; | ||||||
|  |  | ||||||
|  |        return server.get().hasPermissions(context.getUser(), annotation.value()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public void postExec(Object context, HasPermission annotation) { | ||||||
|  |          | ||||||
|  |     } | ||||||
|  |      | ||||||
|  | } | ||||||
| @@ -0,0 +1,23 @@ | |||||||
|  | package net.tomatentum.marinara.test; | ||||||
|  |  | ||||||
|  | import static org.junit.jupiter.api.Assertions.assertTrue; | ||||||
|  |  | ||||||
|  | import org.junit.jupiter.api.Test; | ||||||
|  |  | ||||||
|  | import net.tomatentum.marinara.Marinara; | ||||||
|  | import net.tomatentum.marinara.test.mocks.AutocompleteInteractionMock; | ||||||
|  | import net.tomatentum.marinara.test.mocks.DiscordApiMock; | ||||||
|  | import net.tomatentum.marinara.wrapper.LibraryWrapper; | ||||||
|  | import net.tomatentum.marinara.wrapper.javacord.JavacordWrapper; | ||||||
|  |  | ||||||
|  | public class AutoCompleteTest { | ||||||
|  |      | ||||||
|  |     @Test | ||||||
|  |     public void testAutocomplete() { | ||||||
|  |         LibraryWrapper wrapper = new JavacordWrapper(new DiscordApiMock()); //null okay as we don't use the discord API in this test. | ||||||
|  |         Marinara marinara = Marinara.load(wrapper); | ||||||
|  |         marinara.getRegistry().addInteractions(new TestAutocomplete()); | ||||||
|  |         wrapper.handleInteraction(new AutocompleteInteractionMock()); | ||||||
|  |         assertTrue(AutocompleteInteractionMock.didAutocompleteRun); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -20,7 +20,7 @@ public class ButtonTest { | |||||||
|         LibraryWrapper wrapper = new JavacordWrapper(new DiscordApiMock()); //null okay as we don't use the discord API in this test. |         LibraryWrapper wrapper = new JavacordWrapper(new DiscordApiMock()); //null okay as we don't use the discord API in this test. | ||||||
|         Marinara marinara = Marinara.load(wrapper); |         Marinara marinara = Marinara.load(wrapper); | ||||||
|         marinara.getRegistry().addInteractions(new TestButton()); |         marinara.getRegistry().addInteractions(new TestButton()); | ||||||
|         wrapper.handleInteraction(new ButtonInteractionMock()); |         wrapper.handleInteraction(new ButtonInteractionMock("test")); | ||||||
|         assertTrue(TestButton.didRun); |         assertTrue(TestButton.didRun); | ||||||
|     } |     } | ||||||
|      |      | ||||||
|   | |||||||
| @@ -0,0 +1,47 @@ | |||||||
|  | package net.tomatentum.marinara.test; | ||||||
|  |  | ||||||
|  | import static org.junit.jupiter.api.Assertions.assertFalse; | ||||||
|  | import static org.junit.jupiter.api.Assertions.assertTrue; | ||||||
|  |  | ||||||
|  | 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 net.tomatentum.marinara.Marinara; | ||||||
|  | import net.tomatentum.marinara.test.mocks.ButtonInteractionMock; | ||||||
|  | import net.tomatentum.marinara.test.mocks.DiscordApiMock; | ||||||
|  | import net.tomatentum.marinara.test.mocks.ServerMock; | ||||||
|  | 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 { | ||||||
|  |      | ||||||
|  |     @Test | ||||||
|  |     public void testInteractionCheck() { | ||||||
|  |         LibraryWrapper wrapper = new JavacordWrapper(new DiscordApiMock()); | ||||||
|  |         Marinara marinara = Marinara.load(wrapper); | ||||||
|  |         marinara.getCheckRegistry().addCheck(new TestInteractionCheck()); | ||||||
|  |         marinara.getRegistry().addInteractions(new TestButton()); | ||||||
|  |         wrapper.handleInteraction(new ButtonInteractionMock("test")); | ||||||
|  |         assertTrue(TestInteractionCheck.preExecuted); | ||||||
|  |         assertTrue(TestInteractionCheck.postExecuted); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Test | ||||||
|  |     public void testPermissionCheck() { | ||||||
|  |         LibraryWrapper wrapper = new JavacordWrapper(new DiscordApiMock()); | ||||||
|  |         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")); | ||||||
|  |         assertFalse(TestButton.didPermRun); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,23 @@ | |||||||
|  | package net.tomatentum.marinara.test; | ||||||
|  |  | ||||||
|  | import static org.junit.jupiter.api.Assertions.assertEquals; | ||||||
|  |  | ||||||
|  | import java.util.Collections; | ||||||
|  |  | ||||||
|  | import org.javacord.api.interaction.AutocompleteInteraction; | ||||||
|  |  | ||||||
|  | import net.tomatentum.marinara.interaction.InteractionHandler; | ||||||
|  | import net.tomatentum.marinara.interaction.annotation.AutoComplete; | ||||||
|  | import net.tomatentum.marinara.interaction.commands.annotation.SlashCommand; | ||||||
|  |  | ||||||
|  | public class TestAutocomplete implements InteractionHandler { | ||||||
|  |      | ||||||
|  |     @SlashCommand(name = "test") | ||||||
|  |     @AutoComplete | ||||||
|  |     public void autocomplete(AutocompleteInteraction context, String value) { | ||||||
|  |         System.out.println("Success!"); | ||||||
|  |         assertEquals(value, "test"); | ||||||
|  |         context.respondWithChoices(Collections.emptyList()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -4,18 +4,22 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; | |||||||
|  |  | ||||||
| import org.javacord.api.entity.channel.TextChannel; | import org.javacord.api.entity.channel.TextChannel; | ||||||
| import org.javacord.api.entity.message.Message; | 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.server.Server; | ||||||
| import org.javacord.api.entity.user.User; | import org.javacord.api.entity.user.User; | ||||||
| import org.javacord.api.interaction.ButtonInteraction; | import org.javacord.api.interaction.ButtonInteraction; | ||||||
|  |  | ||||||
| import net.tomatentum.marinara.interaction.InteractionHandler; | import net.tomatentum.marinara.interaction.InteractionHandler; | ||||||
| import net.tomatentum.marinara.interaction.annotation.Button; | import net.tomatentum.marinara.interaction.annotation.Button; | ||||||
|  | import net.tomatentum.marinara.test.TestInteractionCheck.TestCheck; | ||||||
|  | import net.tomatentum.marinara.wrapper.javacord.checks.PermissionCheck.HasPermission; | ||||||
|  |  | ||||||
| public class TestButton implements InteractionHandler { | public class TestButton implements InteractionHandler { | ||||||
|  |  | ||||||
|  |  | ||||||
|     public static boolean didRun = false; |     public static boolean didRun = false; | ||||||
|     @Button("test") |     @Button("test") | ||||||
|  |     @TestCheck | ||||||
|     public void exec(ButtonInteraction interaction, TextChannel channel, Message message, User member, Server server) { |     public void exec(ButtonInteraction interaction, TextChannel channel, Message message, User member, Server server) { | ||||||
|         assertNotNull(interaction); |         assertNotNull(interaction); | ||||||
|         assertNotNull(channel); |         assertNotNull(channel); | ||||||
| @@ -26,4 +30,13 @@ public class TestButton implements InteractionHandler { | |||||||
|         System.out.println("Success!"); |         System.out.println("Success!"); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     public static boolean didPermRun = false; | ||||||
|  |  | ||||||
|  |     @Button("permissionCheck") | ||||||
|  |     @HasPermission({PermissionType.ADMINISTRATOR}) | ||||||
|  |     public void exec(ButtonInteraction interaction) { | ||||||
|  |         didPermRun = true; | ||||||
|  |         System.out.println("It worked!"); | ||||||
|  |     } | ||||||
|  |      | ||||||
| } | } | ||||||
|   | |||||||
| @@ -0,0 +1,20 @@ | |||||||
|  | package net.tomatentum.marinara.test; | ||||||
|  |  | ||||||
|  | import net.tomatentum.marinara.interaction.commands.ChoiceValueProvider; | ||||||
|  |  | ||||||
|  | public enum TestChoiceEnum implements ChoiceValueProvider<String> { | ||||||
|  |     TestValue("testValue"), | ||||||
|  |     FooBar("fooBar"), | ||||||
|  |     Spongebob("spongebob"); | ||||||
|  |  | ||||||
|  |     private String value; | ||||||
|  |  | ||||||
|  |     private TestChoiceEnum(String value) { | ||||||
|  |         this.value = value; | ||||||
|  |     } | ||||||
|  |     @Override | ||||||
|  |     public String getChoiceValue() { | ||||||
|  |         return value; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  | } | ||||||
| @@ -20,7 +20,8 @@ public class TestCommand implements InteractionHandler { | |||||||
|             @SlashCommandOption( |             @SlashCommandOption( | ||||||
|                 name = "foo", |                 name = "foo", | ||||||
|                 description = "foo bar is very fooby", |                 description = "foo bar is very fooby", | ||||||
|                 type = SlashCommandOptionType.STRING |                 type = SlashCommandOptionType.STRING, | ||||||
|  |                 choiceEnum = TestChoiceEnum.class | ||||||
|             ) |             ) | ||||||
|         } |         } | ||||||
|         ) |         ) | ||||||
|   | |||||||
| @@ -0,0 +1,37 @@ | |||||||
|  | package net.tomatentum.marinara.test; | ||||||
|  |  | ||||||
|  | import static org.junit.jupiter.api.Assertions.assertNotNull; | ||||||
|  |  | ||||||
|  | import java.lang.annotation.ElementType; | ||||||
|  | import java.lang.annotation.Retention; | ||||||
|  | import java.lang.annotation.RetentionPolicy; | ||||||
|  | import java.lang.annotation.Target; | ||||||
|  |  | ||||||
|  | import net.tomatentum.marinara.checks.InteractionCheck; | ||||||
|  |  | ||||||
|  | public class TestInteractionCheck implements InteractionCheck<TestInteractionCheck.TestCheck> { | ||||||
|  |  | ||||||
|  |     public static boolean preExecuted = false; | ||||||
|  |     public static boolean postExecuted = false; | ||||||
|  |  | ||||||
|  |     @Target({ElementType.METHOD}) | ||||||
|  |     @Retention(RetentionPolicy.RUNTIME) | ||||||
|  |     public static @interface TestCheck { | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public boolean preExec(Object context, TestCheck annotation) { | ||||||
|  |         assertNotNull(annotation); | ||||||
|  |         assertNotNull(context); | ||||||
|  |         preExecuted = true; | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public void postExec(Object context, TestCheck annotation) { | ||||||
|  |         assertNotNull(annotation); | ||||||
|  |         assertNotNull(context); | ||||||
|  |         postExecuted = true; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  | } | ||||||
| @@ -0,0 +1,179 @@ | |||||||
|  | package net.tomatentum.marinara.test.mocks; | ||||||
|  |  | ||||||
|  | import java.util.ArrayList; | ||||||
|  | import java.util.EnumSet; | ||||||
|  | import java.util.List; | ||||||
|  | import java.util.Optional; | ||||||
|  | import java.util.concurrent.CompletableFuture; | ||||||
|  |  | ||||||
|  | import org.javacord.api.DiscordApi; | ||||||
|  | import org.javacord.api.entity.channel.TextChannel; | ||||||
|  | import org.javacord.api.entity.message.component.HighLevelComponent; | ||||||
|  | import org.javacord.api.entity.permission.PermissionType; | ||||||
|  | import org.javacord.api.entity.server.Server; | ||||||
|  | import org.javacord.api.entity.user.User; | ||||||
|  | import org.javacord.api.interaction.AutocompleteInteraction; | ||||||
|  | import org.javacord.api.interaction.DiscordLocale; | ||||||
|  | import org.javacord.api.interaction.InteractionType; | ||||||
|  | import org.javacord.api.interaction.SlashCommandInteractionOption; | ||||||
|  | import org.javacord.api.interaction.SlashCommandOptionChoice; | ||||||
|  | import org.javacord.api.interaction.callback.InteractionFollowupMessageBuilder; | ||||||
|  | import org.javacord.api.interaction.callback.InteractionImmediateResponseBuilder; | ||||||
|  | import org.javacord.api.interaction.callback.InteractionOriginalResponseUpdater; | ||||||
|  |  | ||||||
|  | public class AutocompleteInteractionMock implements AutocompleteInteraction { | ||||||
|  |  | ||||||
|  |     public static boolean didAutocompleteRun = false; | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public String getFullCommandName() { | ||||||
|  |         return "test"; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public long getCommandId() { | ||||||
|  |         // TODO Auto-generated method stub | ||||||
|  |         throw new UnsupportedOperationException("Unimplemented method 'getCommandId'"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public String getCommandIdAsString() { | ||||||
|  |         // TODO Auto-generated method stub | ||||||
|  |         throw new UnsupportedOperationException("Unimplemented method 'getCommandIdAsString'"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public String getCommandName() { | ||||||
|  |         return "test"; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public Optional<Long> getRegisteredCommandServerId() { | ||||||
|  |         // TODO Auto-generated method stub | ||||||
|  |         throw new UnsupportedOperationException("Unimplemented method 'getRegisteredCommandServerId'"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public long getApplicationId() { | ||||||
|  |         // TODO Auto-generated method stub | ||||||
|  |         throw new UnsupportedOperationException("Unimplemented method 'getApplicationId'"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public InteractionType getType() { | ||||||
|  |         return InteractionType.APPLICATION_COMMAND_AUTOCOMPLETE; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public InteractionImmediateResponseBuilder createImmediateResponder() { | ||||||
|  |         // TODO Auto-generated method stub | ||||||
|  |         throw new UnsupportedOperationException("Unimplemented method 'createImmediateResponder'"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public CompletableFuture<InteractionOriginalResponseUpdater> respondLater() { | ||||||
|  |         // TODO Auto-generated method stub | ||||||
|  |         throw new UnsupportedOperationException("Unimplemented method 'respondLater'"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public CompletableFuture<InteractionOriginalResponseUpdater> respondLater(boolean ephemeral) { | ||||||
|  |         // TODO Auto-generated method stub | ||||||
|  |         throw new UnsupportedOperationException("Unimplemented method 'respondLater'"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public CompletableFuture<Void> respondWithModal(String customId, String title, | ||||||
|  |             List<HighLevelComponent> components) { | ||||||
|  |         // TODO Auto-generated method stub | ||||||
|  |         throw new UnsupportedOperationException("Unimplemented method 'respondWithModal'"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public InteractionFollowupMessageBuilder createFollowupMessageBuilder() { | ||||||
|  |         // TODO Auto-generated method stub | ||||||
|  |         throw new UnsupportedOperationException("Unimplemented method 'createFollowupMessageBuilder'"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public Optional<Server> getServer() { | ||||||
|  |         // TODO Auto-generated method stub | ||||||
|  |         throw new UnsupportedOperationException("Unimplemented method 'getServer'"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public Optional<TextChannel> getChannel() { | ||||||
|  |         // TODO Auto-generated method stub | ||||||
|  |         throw new UnsupportedOperationException("Unimplemented method 'getChannel'"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public User getUser() { | ||||||
|  |         // TODO Auto-generated method stub | ||||||
|  |         throw new UnsupportedOperationException("Unimplemented method 'getUser'"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public String getToken() { | ||||||
|  |         // TODO Auto-generated method stub | ||||||
|  |         throw new UnsupportedOperationException("Unimplemented method 'getToken'"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public int getVersion() { | ||||||
|  |         // TODO Auto-generated method stub | ||||||
|  |         throw new UnsupportedOperationException("Unimplemented method 'getVersion'"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public DiscordLocale getLocale() { | ||||||
|  |         // TODO Auto-generated method stub | ||||||
|  |         throw new UnsupportedOperationException("Unimplemented method 'getLocale'"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public Optional<DiscordLocale> getServerLocale() { | ||||||
|  |         // TODO Auto-generated method stub | ||||||
|  |         throw new UnsupportedOperationException("Unimplemented method 'getServerLocale'"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public Optional<EnumSet<PermissionType>> getBotPermissions() { | ||||||
|  |         // TODO Auto-generated method stub | ||||||
|  |         throw new UnsupportedOperationException("Unimplemented method 'getBotPermissions'"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public DiscordApi getApi() { | ||||||
|  |         // TODO Auto-generated method stub | ||||||
|  |         throw new UnsupportedOperationException("Unimplemented method 'getApi'"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public long getId() { | ||||||
|  |         // TODO Auto-generated method stub | ||||||
|  |         throw new UnsupportedOperationException("Unimplemented method 'getId'"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public List<SlashCommandInteractionOption> getOptions() { | ||||||
|  |         return new ArrayList<>(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public List<SlashCommandInteractionOption> getArguments() { | ||||||
|  |         return new ArrayList<>(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public CompletableFuture<Void> respondWithChoices(List<SlashCommandOptionChoice> choices) { | ||||||
|  |         didAutocompleteRun = true; | ||||||
|  |         return CompletableFuture.completedFuture(null); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public SlashCommandInteractionOption getFocusedOption() { | ||||||
|  |         return new SlashCommandInteractionOptionMock(); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  | } | ||||||
| @@ -23,6 +23,12 @@ import org.javacord.api.interaction.callback.InteractionOriginalResponseUpdater; | |||||||
|  |  | ||||||
| public class ButtonInteractionMock implements ButtonInteraction { | public class ButtonInteractionMock implements ButtonInteraction { | ||||||
|  |  | ||||||
|  |     private String customId; | ||||||
|  |      | ||||||
|  |     public ButtonInteractionMock(String customId) { | ||||||
|  |         this.customId = customId; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public Message getMessage() { |     public Message getMessage() { | ||||||
|         return new MessageMock(); |         return new MessageMock(); | ||||||
| @@ -30,7 +36,7 @@ public class ButtonInteractionMock implements ButtonInteraction { | |||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public String getCustomId() { |     public String getCustomId() { | ||||||
|         return "test"; |         return this.customId; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|   | |||||||
| @@ -1,5 +1,7 @@ | |||||||
| package net.tomatentum.marinara.test.mocks; | package net.tomatentum.marinara.test.mocks; | ||||||
|  |  | ||||||
|  | import static org.junit.jupiter.api.Assertions.assertNotNull; | ||||||
|  |  | ||||||
| import java.time.Instant; | import java.time.Instant; | ||||||
| import java.util.Collection; | import java.util.Collection; | ||||||
| import java.util.EnumSet; | import java.util.EnumSet; | ||||||
| @@ -27,6 +29,7 @@ import org.javacord.api.entity.channel.ServerTextChannel; | |||||||
| import org.javacord.api.entity.channel.ServerThreadChannel; | import org.javacord.api.entity.channel.ServerThreadChannel; | ||||||
| import org.javacord.api.entity.channel.ServerVoiceChannel; | import org.javacord.api.entity.channel.ServerVoiceChannel; | ||||||
| import org.javacord.api.entity.emoji.KnownCustomEmoji; | import org.javacord.api.entity.emoji.KnownCustomEmoji; | ||||||
|  | import org.javacord.api.entity.permission.PermissionType; | ||||||
| import org.javacord.api.entity.permission.Role; | import org.javacord.api.entity.permission.Role; | ||||||
| import org.javacord.api.entity.server.ActiveThreads; | import org.javacord.api.entity.server.ActiveThreads; | ||||||
| import org.javacord.api.entity.server.Ban; | import org.javacord.api.entity.server.Ban; | ||||||
| @@ -2259,5 +2262,12 @@ public class ServerMock implements Server { | |||||||
|         // TODO Auto-generated method stub |         // TODO Auto-generated method stub | ||||||
|         throw new UnsupportedOperationException("Unimplemented method 'getSystemChannelFlags'"); |         throw new UnsupportedOperationException("Unimplemented method 'getSystemChannelFlags'"); | ||||||
|     } |     } | ||||||
|  |     public static PermissionType TESTPERMISSION = PermissionType.ADMINISTRATOR; | ||||||
|  |     @Override | ||||||
|  |     public boolean hasPermissions(User user, PermissionType... type) { | ||||||
|  |         assertNotNull(user); | ||||||
|  |         assertNotNull(type); | ||||||
|  |         return TESTPERMISSION.equals(type[0]); | ||||||
|  |     } | ||||||
|      |      | ||||||
| } | } | ||||||
|   | |||||||
| @@ -41,52 +41,52 @@ public class SlashCommandInteractionOptionMock implements SlashCommandInteractio | |||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public Optional<Long> getLongValue() { |     public Optional<Long> getLongValue() { | ||||||
|         throw new UnsupportedOperationException("Unimplemented method 'getLongValue'"); |         return Optional.empty(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public Optional<Boolean> getBooleanValue() { |     public Optional<Boolean> getBooleanValue() { | ||||||
|         throw new UnsupportedOperationException("Unimplemented method 'getBooleanValue'"); |         return Optional.empty(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public Optional<User> getUserValue() { |     public Optional<User> getUserValue() { | ||||||
|         throw new UnsupportedOperationException("Unimplemented method 'getUserValue'"); |         return Optional.empty(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public Optional<CompletableFuture<User>> requestUserValue() { |     public Optional<CompletableFuture<User>> requestUserValue() { | ||||||
|         throw new UnsupportedOperationException("Unimplemented method 'requestUserValue'"); |         return Optional.empty(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public Optional<ServerChannel> getChannelValue() { |     public Optional<ServerChannel> getChannelValue() { | ||||||
|         throw new UnsupportedOperationException("Unimplemented method 'getChannelValue'"); |         return Optional.empty(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public Optional<Attachment> getAttachmentValue() { |     public Optional<Attachment> getAttachmentValue() { | ||||||
|         throw new UnsupportedOperationException("Unimplemented method 'getAttachmentValue'"); |         return Optional.empty(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public Optional<Role> getRoleValue() { |     public Optional<Role> getRoleValue() { | ||||||
|         throw new UnsupportedOperationException("Unimplemented method 'getRoleValue'"); |         return Optional.empty(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public Optional<Mentionable> getMentionableValue() { |     public Optional<Mentionable> getMentionableValue() { | ||||||
|         throw new UnsupportedOperationException("Unimplemented method 'getMentionableValue'"); |         return Optional.empty(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public Optional<Double> getDecimalValue() { |     public Optional<Double> getDecimalValue() { | ||||||
|         throw new UnsupportedOperationException("Unimplemented method 'getDecimalValue'"); |         return Optional.empty(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public Optional<CompletableFuture<Mentionable>> requestMentionableValue() { |     public Optional<CompletableFuture<Mentionable>> requestMentionableValue() { | ||||||
|         throw new UnsupportedOperationException("Unimplemented method 'requestMentionableValue'"); |         return Optional.empty(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user