Compare commits

...

17 Commits

Author SHA1 Message Date
d8504a7cde Merge pull request 'add Checks system' (#6) from feat/checks into dev
All checks were successful
github-mirror / push-github (push) Successful in 4s
Build / Gradle-Build (push) Successful in 11s
Publish / Gradle-Publish (push) Successful in 11s
Test / Gradle-Test (push) Successful in 16s
Reviewed-on: #6
2024-12-05 07:51:36 +00:00
bef34ee548 fix null issue
All checks were successful
github-mirror / push-github (push) Successful in 3s
Build / Gradle-Build (push) Successful in 12s
Test / Gradle-Test (push) Successful in 16s
2024-12-03 20:20:57 +01:00
29bb7e667e
add PermissionCHeck test
Some checks failed
github-mirror / push-github (push) Successful in 5s
Build / Gradle-Build (push) Successful in 11s
Test / Gradle-Test (push) Failing after 16s
2024-12-02 21:21:00 +01:00
94da2a0e3c
change logic to always give objects the highest value 2024-12-02 21:20:49 +01:00
7f47130461
add missing annotation annotations 2024-12-02 21:20:26 +01:00
83a3efd4b8
add canRun check which i forgor 2024-12-02 21:19:58 +01:00
aefd8a51a0
change wrong annotation class usage 2024-12-02 21:19:42 +01:00
4332592dfa
add Javacord PermissionCheck
All checks were successful
github-mirror / push-github (push) Successful in 4s
Build / Gradle-Build (push) Successful in 12s
Test / Gradle-Test (push) Successful in 17s
2024-12-02 13:16:28 +01:00
c363ab9744 add ability to return false to cancel execution in pre Checks and remove return type on post checks
All checks were successful
github-mirror / push-github (push) Successful in 7s
Build / Gradle-Build (push) Successful in 14s
Test / Gradle-Test (push) Successful in 21s
2024-12-01 13:06:42 +01:00
33392b02fb
add InteractionCheck test
All checks were successful
github-mirror / push-github (push) Successful in 4s
Build / Gradle-Build (push) Successful in 13s
Test / Gradle-Test (push) Successful in 15s
2024-11-29 21:41:46 +01:00
b7333c2e5e
fix problem with multiple method overrides in generic types. 2024-11-29 21:41:18 +01:00
239e921e6f
change Annotation#getClass to Annotation#annotationType because it was not working as expected 2024-11-29 21:37:13 +01:00
6eb7fb723f
add geantyref to fix and simplify generic Type parsing.
Also switch to java 23 to avoid conflicts and issues.
2024-11-29 21:36:02 +01:00
659218682e
add context Object to check methods and create the ability to have a specific method for each type of context or one for all by using the superclass and casting yourself
All checks were successful
github-mirror / push-github (push) Successful in 4s
Build / Gradle-Build (push) Successful in 12s
Test / Gradle-Test (push) Successful in 17s
2024-11-29 18:17:33 +01:00
019ba8f552
add getMostSpecificMethod method to simplify Check method parsing.
This Method searches the The method that has the best matching parameters with the fewest inheritance levels as possible. Left sided priority
2024-11-29 18:15:47 +01:00
f89ae5e425
- add prototype Interactioncheck impementation.
All checks were successful
github-mirror / push-github (push) Successful in 4s
Build / Gradle-Build (push) Successful in 12s
Test / Gradle-Test (push) Successful in 16s
- refactor dependency injection to have all widely used dependencies in the Marinara class.
2024-11-28 10:32:48 +01:00
582e0f0bae
implement AnnotationParser system
All checks were successful
github-mirror / push-github (push) Successful in 4s
Build / Gradle-Build (push) Successful in 13s
Test / Gradle-Test (push) Successful in 16s
2024-11-24 00:02:19 +01:00
23 changed files with 572 additions and 91 deletions

@ -21,13 +21,13 @@ dependencies {
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
implementation(libs.log4j)
implementation(libs.geantyref)
}
// Apply a specific Java toolchain to ease working on different environments.
java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
languageVersion = JavaLanguageVersion.of(23)
}
}

@ -1,22 +1,34 @@
package net.tomatentum.marinara;
import net.tomatentum.marinara.registry.InteractionCheckRegistry;
import net.tomatentum.marinara.registry.InteractionRegistry;
import net.tomatentum.marinara.wrapper.LibraryWrapper;
public class Marinara {
public static <T extends LibraryWrapper> Marinara load(LibraryWrapper wrapper) {
InteractionRegistry registry = new InteractionRegistry(wrapper);
return new Marinara(registry);
return new Marinara(wrapper);
}
private InteractionRegistry registry;
private InteractionCheckRegistry checkRegistry;
private LibraryWrapper wrapper;
private Marinara(InteractionRegistry registry) {
this.registry = registry;
private Marinara(LibraryWrapper wrapper) {
this.wrapper = wrapper;
this.registry = new InteractionRegistry(this);
this.checkRegistry = new InteractionCheckRegistry();
}
public InteractionRegistry getRegistry() {
return registry;
}
public InteractionCheckRegistry getCheckRegistry() {
return checkRegistry;
}
public LibraryWrapper getWrapper() {
return wrapper;
}
}

@ -0,0 +1,41 @@
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 net.tomatentum.marinara.util.ReflectionUtil;
public record AppliedCheck(InteractionCheck<?> check, Annotation annotation) {
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 {
return (boolean) method.invoke(check, context, annotation);
} catch (IllegalAccessException | InvocationTargetException | SecurityException e) {
e.printStackTrace();
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 {
method.invoke(check, context, annotation);
} catch (IllegalAccessException | InvocationTargetException | SecurityException e) {
e.printStackTrace();
}
}
}

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

@ -16,9 +16,18 @@ public record ExecutableSlashCommandDefinition(
if (!(o instanceof ExecutableSlashCommandDefinition))
return false;
ExecutableSlashCommandDefinition other = (ExecutableSlashCommandDefinition) o;
return other.applicationCommand.name().equals(this.applicationCommand.name()) &&
other.subCommandGroup.name().equals(this.subCommandGroup.name()) &&
other.subCommand.name().equals(this.subCommand.name());
boolean equals = false;
if (this.applicationCommand() != null && other.subCommandGroup() != 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

@ -2,39 +2,40 @@ 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.annotation.Button;
import net.tomatentum.marinara.wrapper.LibraryWrapper;
import net.tomatentum.marinara.parser.AnnotationParser;
import net.tomatentum.marinara.parser.ButtonParser;
public class ButtonInteractionMethod extends InteractionMethod {
private String customId;
ButtonInteractionMethod(Method method, InteractionHandler handler, LibraryWrapper wrapper) {
super(method, handler, wrapper);
parseMethod();
ButtonInteractionMethod(Method method, InteractionHandler handler, Marinara marinara) {
super(method, handler, marinara);
}
@Override
public AnnotationParser[] getParsers() {
return new AnnotationParser[] {
new ButtonParser(method, (x) -> { this.customId = x; } )
};
}
@Override
public Object getParameter(Object parameter, int index) {
Class<?> type = getMethod().getParameterTypes()[index+1];
return wrapper.getComponentContextObject(parameter, type);
return marinara.getWrapper().getComponentContextObject(parameter, type);
}
@Override
public boolean canRun(Object context) {
return wrapper.getButtonId(context).equals(customId);
return marinara.getWrapper().getButtonId(context).equals(customId);
}
@Override
public InteractionType getType() {
return InteractionType.BUTTON;
}
private void parseMethod() {
Button button = getMethod().getAnnotation(Button.class);
this.customId = button.value();
}
}

@ -7,35 +7,52 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import net.tomatentum.marinara.Marinara;
import net.tomatentum.marinara.checks.AppliedCheck;
import net.tomatentum.marinara.interaction.InteractionHandler;
import net.tomatentum.marinara.interaction.InteractionType;
import net.tomatentum.marinara.interaction.annotation.Button;
import net.tomatentum.marinara.interaction.commands.annotation.SlashCommand;
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;
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(SlashCommand.class) || method.isAnnotationPresent(SubCommand.class))
return new SlashCommandInteractionMethod(method, handler, wrapper);
return new SlashCommandInteractionMethod(method, handler, marinara);
if (method.isAnnotationPresent(Button.class))
return new ButtonInteractionMethod(method, handler, wrapper);
return new ButtonInteractionMethod(method, handler, marinara);
return null;
}
protected Method method;
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) {
protected InteractionMethod(Method method,
InteractionHandler handler,
Marinara marinara
) {
if (!Arrays.asList(handler.getClass().getMethods()).contains(method))
throw new InvalidParameterException("Method does not apply to specified handler");
this.method = method;
this.handler = handler;
this.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 AnnotationParser[] getParsers();
public abstract Object getParameter(Object parameter, int index);
public abstract boolean canRun(Object context);
@ -43,6 +60,24 @@ public abstract class InteractionMethod {
public abstract InteractionType getType();
public void run(Object context) {
if (this.appliedChecks.stream().filter(x -> !x.pre(context)).count() > 0)
return;
method.setAccessible(true);
try {
method.invoke(handler, getParameters(context));
}catch (IllegalAccessException | InvocationTargetException ex) {
throw new RuntimeException(ex);
}
this.appliedChecks.forEach(x -> x.post(context));
}
public Method getMethod() {
return method;
}
private Object[] getParameters(Object context) {
int parameterCount = method.getParameterCount();
List<Object> parameters = new ArrayList<>();
@ -53,16 +88,7 @@ public abstract class InteractionMethod {
}
parameters.add(getParameter(context, i-1));
}
method.setAccessible(true);
try {
method.invoke(handler, parameters.toArray());
}catch (IllegalAccessException | InvocationTargetException ex) {
throw new RuntimeException(ex);
}
}
public Method getMethod() {
return method;
return parameters.toArray();
}
}

@ -2,32 +2,36 @@ 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.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.ReflectionUtil;
import net.tomatentum.marinara.wrapper.LibraryWrapper;
import net.tomatentum.marinara.parser.AnnotationParser;
import net.tomatentum.marinara.parser.SlashCommandParser;
public class SlashCommandInteractionMethod extends InteractionMethod {
private ExecutableSlashCommandDefinition commandDefinition;
SlashCommandInteractionMethod(Method method, InteractionHandler handler, LibraryWrapper wrapper) {
super(method, handler, wrapper);
parseMethod();
SlashCommandInteractionMethod(Method method, InteractionHandler handler, Marinara marinara) {
super(method, handler, marinara);
}
@Override
public AnnotationParser[] getParsers() {
return new AnnotationParser[] {
new SlashCommandParser(method, (x) -> { this.commandDefinition = x; } )
};
}
@Override
public Object getParameter(Object context, int index) {
return wrapper.convertCommandOption(context, commandDefinition.options()[index].type(), commandDefinition.options()[index].name());
return marinara.getWrapper().convertCommandOption(context, commandDefinition.options()[index].type(), commandDefinition.options()[index].name());
}
@Override
public boolean canRun(Object context) {
ExecutableSlashCommandDefinition other = wrapper.getCommandDefinition(context);
ExecutableSlashCommandDefinition other = marinara.getWrapper().getCommandDefinition(context);
return commandDefinition.equals(other);
}
@ -40,24 +44,8 @@ public class SlashCommandInteractionMethod extends InteractionMethod {
return commandDefinition;
}
private void parseMethod() {
ReflectionUtil.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);
}
this.commandDefinition = builder.build();
public void setCommandDefinition(ExecutableSlashCommandDefinition commandDefinition) {
this.commandDefinition = commandDefinition;
}
}

@ -0,0 +1,8 @@
package net.tomatentum.marinara.parser;
import java.lang.reflect.Method;
public interface AnnotationParser {
void parse();
Method getMethod();
}

@ -0,0 +1,29 @@
package net.tomatentum.marinara.parser;
import java.lang.reflect.Method;
import java.util.function.Consumer;
import net.tomatentum.marinara.interaction.annotation.Button;
public class ButtonParser implements AnnotationParser {
private Method method;
private Consumer<String> consumer;
public ButtonParser(Method method, Consumer<String> consumer) {
this.method = method;
this.consumer = consumer;
}
@Override
public void parse() {
Button button = getMethod().getAnnotation(Button.class);
this.consumer.accept(button.value());
}
@Override
public Method getMethod() {
return this.method;
}
}

@ -0,0 +1,42 @@
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 net.tomatentum.marinara.checks.AppliedCheck;
import net.tomatentum.marinara.checks.InteractionCheck;
import net.tomatentum.marinara.registry.InteractionCheckRegistry;
public class InteractionCheckParser implements AnnotationParser {
private InteractionCheckRegistry checkRegistry;
private Method method;
private Consumer<AppliedCheck> consumer;
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())
consumer.accept(new AppliedCheck(check.get(), annotation));
}
@Override
public Method getMethod() {
return this.method;
}
}

@ -0,0 +1,63 @@
package net.tomatentum.marinara.parser;
import java.lang.reflect.Method;
import java.util.function.Consumer;
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.ReflectionUtil;
public class SlashCommandParser implements AnnotationParser {
private Method method;
private Consumer<ExecutableSlashCommandDefinition> consumer;
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);
}
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,34 @@
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 io.leangen.geantyref.GenericTypeReflector;
import net.tomatentum.marinara.checks.InteractionCheck;
public class InteractionCheckRegistry {
private List<InteractionCheck<?>> checks;
public InteractionCheckRegistry() {
this.checks = new ArrayList<>();
}
public void addCheck(InteractionCheck<?> check) {
checks.add(check);
}
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,27 +5,27 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import net.tomatentum.marinara.Marinara;
import net.tomatentum.marinara.interaction.InteractionHandler;
import net.tomatentum.marinara.interaction.InteractionType;
import net.tomatentum.marinara.interaction.commands.SlashCommandDefinition;
import net.tomatentum.marinara.interaction.commands.ExecutableSlashCommandDefinition;
import net.tomatentum.marinara.interaction.methods.SlashCommandInteractionMethod;
import net.tomatentum.marinara.interaction.methods.InteractionMethod;
import net.tomatentum.marinara.wrapper.LibraryWrapper;
public class InteractionRegistry {
private List<InteractionMethod> interactionMethods;
private LibraryWrapper wrapper;
private Marinara marinara;
public InteractionRegistry(LibraryWrapper wrapper) {
public InteractionRegistry(Marinara marinara) {
this.interactionMethods = new ArrayList<>();
this.wrapper = wrapper;
wrapper.subscribeInteractions(this::handle);
this.marinara = marinara;
marinara.getWrapper().subscribeInteractions(this::handle);
}
public void addInteractions(InteractionHandler interactionHandler) {
for (Method method : interactionHandler.getClass().getMethods()) {
InteractionMethod iMethod = InteractionMethod.create(method, interactionHandler, wrapper);
InteractionMethod iMethod = InteractionMethod.create(method, interactionHandler, marinara);
if (iMethod != null)
this.interactionMethods.add(iMethod);
}
@ -48,13 +48,13 @@ public class InteractionRegistry {
defs.add(new SlashCommandDefinition(def.applicationCommand()).addExecutableCommand(def));
});
wrapper.registerSlashCommands(defs.toArray(new SlashCommandDefinition[0]));
marinara.getWrapper().registerSlashCommands(defs.toArray(new SlashCommandDefinition[0]));
}
public void handle(Object context) {
interactionMethods.forEach((m) -> {
InteractionType type = wrapper.getInteractionType(context.getClass());
if (m.getType().equals(type))
InteractionType type = marinara.getWrapper().getInteractionType(context.getClass());
if (m.getType().equals(type) && m.canRun(context))
m.run(context);
});
}

@ -2,9 +2,10 @@ package net.tomatentum.marinara.util;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import net.tomatentum.marinara.interaction.commands.annotation.SlashCommand;
import net.tomatentum.marinara.interaction.commands.annotation.SubCommand;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
public final class ReflectionUtil {
@ -21,19 +22,82 @@ public final class ReflectionUtil {
method.getDeclaringClass().getAnnotation(annotationClass);
}
public static 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");
public static int getCastDepth(Class<?> child, Class<?> parent) {
if (parent.equals(Object.class))
return Integer.MAX_VALUE;
if (!parent.isAssignableFrom(child)) {
throw new IllegalArgumentException("The specified class is not a child class of the specified parent.");
}
if (!isAnnotationPresent(method, SlashCommand.class))
throw new RuntimeException(method.getName() + ": Missing ApplicationCommand Annotation on either Class or Method");
int depth = 0;
Class<?> curr = child;
List<Class<?>> parents = new ArrayList<>();
if ((method.isAnnotationPresent(SubCommand.class) &&
!isAnnotationPresent(method, SlashCommand.class))) {
throw new RuntimeException(method.getName() + ": Missing ApplicationCommand Annotation on either Method or Class");
while (!curr.equals(parent)) {
depth++;
parents.add(curr.getSuperclass());
parents.addAll(Arrays.asList(curr.getInterfaces()));
for (Class<?> currParent : parents) {
if (currParent != null && parent.isAssignableFrom(currParent)) {
curr = currParent;
break;
}
}
parents.clear();
}
return depth;
}
public static Method getMostSpecificMethod(Method[] methods, Class<?>... parameters) {
List<Method> compatibleMethods = Arrays.stream(methods)
.filter(x -> isMethodCallable(x, parameters))
.toList();
if (compatibleMethods.size() == 0)
throw new IllegalArgumentException("There are no compatible Methods provided");
for (int i = 0; i < parameters.length; i++) {
final int currI = i;
Class<?>[] parameterTypes = compatibleMethods.stream()
.map(x -> x.getParameterTypes()[currI])
.toArray(x -> new Class[x]);
Class<?> mostSpecific = getMostSpecificClass(parameterTypes, parameters[i]);
compatibleMethods = compatibleMethods.stream()
.filter(x -> Objects.equals(x.getParameterTypes()[currI], mostSpecific))
.toList();
}
return compatibleMethods.getFirst();
}
public static Class<?> getMostSpecificClass(Class<?>[] classes, Class<?> base) {
int min = Integer.MAX_VALUE;
Class<?> currMostSpecific = null;
for (Class<?> currClass : classes) {
int currCastDepth = getCastDepth(base, currClass);
if (currCastDepth <= min) {
min = currCastDepth;
currMostSpecific = currClass;
}
}
return currMostSpecific;
}
public static boolean isMethodCallable(Method method, Class<?>... parameters) {
if (!Objects.equals(method.getParameterCount(), parameters.length))
return false;
Class<?>[] methodParams = method.getParameterTypes();
for (int i = 0; i < parameters.length; i++) {
if (!methodParams[i].isAssignableFrom(parameters[i]))
return false;
}
return true;
}
}

@ -29,7 +29,7 @@ dependencies {
// Apply a specific Java toolchain to ease working on different environments.
java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
languageVersion = JavaLanguageVersion.of(23)
}
}

@ -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) {
}
}

@ -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.
Marinara marinara = Marinara.load(wrapper);
marinara.getRegistry().addInteractions(new TestButton());
wrapper.handleInteraction(new ButtonInteractionMock());
wrapper.handleInteraction(new ButtonInteractionMock("test"));
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);
}
}

@ -4,18 +4,22 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
import org.javacord.api.entity.channel.TextChannel;
import org.javacord.api.entity.message.Message;
import org.javacord.api.entity.permission.PermissionType;
import org.javacord.api.entity.server.Server;
import org.javacord.api.entity.user.User;
import org.javacord.api.interaction.ButtonInteraction;
import net.tomatentum.marinara.interaction.InteractionHandler;
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 static boolean didRun = false;
@Button("test")
@TestCheck
public void exec(ButtonInteraction interaction, TextChannel channel, Message message, User member, Server server) {
assertNotNull(interaction);
assertNotNull(channel);
@ -25,5 +29,14 @@ public class TestButton implements InteractionHandler {
didRun = true;
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,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;
}
}

@ -23,6 +23,12 @@ import org.javacord.api.interaction.callback.InteractionOriginalResponseUpdater;
public class ButtonInteractionMock implements ButtonInteraction {
private String customId;
public ButtonInteractionMock(String customId) {
this.customId = customId;
}
@Override
public Message getMessage() {
return new MessageMock();
@ -30,7 +36,7 @@ public class ButtonInteractionMock implements ButtonInteraction {
@Override
public String getCustomId() {
return "test";
return this.customId;
}
@Override

@ -1,5 +1,7 @@
package net.tomatentum.marinara.test.mocks;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import java.time.Instant;
import java.util.Collection;
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.ServerVoiceChannel;
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.server.ActiveThreads;
import org.javacord.api.entity.server.Ban;
@ -2259,5 +2262,12 @@ public class ServerMock implements Server {
// TODO Auto-generated method stub
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]);
}
}