Compare commits

28 Commits

Author SHA1 Message Date
aaf4f3297a add autocomplete option toggle, add double value for non javacord wrappers, rename OptionChoices to match with discords naming and general cleanup
All checks were successful
github-mirror / push-github (push) Successful in 4s
Build / Gradle-Build (push) Successful in 31s
Test / Gradle-Test (push) Successful in 39s
2024-12-16 13:03:11 +01:00
445190db89 added Tests for choices 2024-12-16 12:49:51 +01:00
9d3a6b8b85 add Autocomplete Test
All checks were successful
github-mirror / push-github (push) Successful in 4s
Build / Gradle-Build (push) Successful in 33s
Test / Gradle-Test (push) Successful in 39s
2024-12-15 23:15:37 +01:00
1cb6cd0e05 clean up code and switch to request instead of getting from cache 2024-12-15 23:15:29 +01:00
a5e1230fc6 fix issues with ExecutableSlashCommandDefinition equals check 2024-12-15 23:13:57 +01:00
432db43bf5 add remaining parts of AutocompleteInteraction
All checks were successful
github-mirror / push-github (push) Successful in 4s
Build / Gradle-Build (push) Successful in 33s
Test / Gradle-Test (push) Successful in 40s
2024-12-15 15:09:09 +01:00
7a2c15d877 create seperate class for ContextObjectProviders and renamed some context parameters from parameter to context.
First parts of AutocompleteInteraction added
2024-12-15 15:08:34 +01:00
f32c7045a1 remove unnecessary getClass call fixing test
All checks were successful
github-mirror / push-github (push) Successful in 4s
Build / Gradle-Build (push) Successful in 36s
Test / Gradle-Test (push) Successful in 40s
2024-12-14 12:32:03 +01:00
69b27e4554 add prototype choices implementation
Some checks failed
github-mirror / push-github (push) Successful in 5s
Build / Gradle-Build (push) Successful in 50s
Test / Gradle-Test (push) Failing after 41s
2024-12-13 10:50:30 +01:00
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
0ea330d48b move to seperate files because gitea does not have expressions yet
All checks were successful
github-mirror / push-github (push) Successful in 4s
Build / Gradle-Build (push) Successful in 9s
Publish / Gradle-Publish (push) Successful in 10s
Test / Gradle-Test (push) Successful in 13s
2024-11-20 12:01:47 +01:00
c241f6b1fe enable dev branch publishing
All checks were successful
github-mirror / push-github (push) Successful in 4s
Build / Gradle-Build (push) Successful in 17s
Publish / Gradle-Publish (push) Successful in 6s
Test / Gradle-Test (push) Successful in 13s
2024-11-20 11:52:41 +01:00
43 changed files with 1252 additions and 186 deletions

View 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

View File

@@ -1,11 +1,11 @@
name: Test
name: Publish
on:
push:
branches: [master]
jobs:
Gradle-Test:
Gradle-Publish:
runs-on: ubuntu-latest
steps:
@@ -34,7 +34,7 @@ jobs:
restore-keys: |
${{ runner.os }}-gradle-
- name: Publish
- name: Publish Release
env:
GITEA_TOKEN: ${{ secrets.PUBLISH_PACKAGE_TOKEN }}
run: chmod +x gradlew; ./gradlew publishAllPublicationsToGiteaRepository
run: chmod +x gradlew; ./gradlew publishAllPublicationsToGiteaRepository -Prelease

View File

@@ -8,6 +8,7 @@ allprojects {
group = "net.tomatentum.Marinara"
version = "1.0.0-RC1" + (if (!project.hasProperty("release")) ("-" + getGitHash()) else "")
description = "A simple but powerful, library-agnostic Discord Interaction Wrapper."
}
subprojects {
@@ -18,6 +19,8 @@ subprojects {
publishing {
publications {
create<MavenPublication>("maven") {
if (!project.hasProperty("release"))
artifactId = project.getName() + "-dev"
from(components["java"])
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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(new SlashCommandOptionChoice[0]);
}
public static enum ChoiceType {
String,
INTEGER,
DOUBLE
}
}

View File

@@ -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.SlashCommandOption;
import net.tomatentum.marinara.interaction.commands.annotation.SlashCommandOptionChoice;
import net.tomatentum.marinara.interaction.commands.annotation.SubCommand;
import net.tomatentum.marinara.interaction.commands.annotation.SubCommandGroup;
import net.tomatentum.marinara.interaction.commands.annotation.SlashCommandOption.PlaceHolderEnum;
public record ExecutableSlashCommandDefinition(
SlashCommand applicationCommand,
@@ -11,14 +13,30 @@ public record ExecutableSlashCommandDefinition(
SubCommandGroup subCommandGroup,
SlashCommandOption[] options) {
public static SlashCommandOptionChoice[] getActualChoices(SlashCommandOption option) {
SlashCommandOptionChoice[] choices = option.choices();
if (choices.length <= 0 && !option.choiceEnum().equals(PlaceHolderEnum.class))
choices = EnumChoices.of(option.choiceEnum()).choices();
return choices;
}
@Override
public final boolean equals(Object o) {
if (!(o instanceof ExecutableSlashCommandDefinition))
return false;
ExecutableSlashCommandDefinition other = (ExecutableSlashCommandDefinition) o;
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.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

View File

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

View File

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

View File

@@ -1,16 +1,26 @@
package net.tomatentum.marinara.interaction.commands.option;
public enum SlashCommandOptionType {
ATTACHMENT,
BOOLEAN,
CHANNEL,
DECIMAL,
LONG,
MENTIONABLE,
ROLE,
STRING,
SUB_COMMAND,
SUB_COMMAND_GROUP,
UNKNOW,
USER
SUB_COMMAND(1),
SUB_COMMAND_GROUP(2),
STRING(3),
INTEGER(4),
BOOLEAN(5),
USER(6),
CHANNEL(7),
ROLE(8),
MENTIONABLE(9),
DOUBLE(10),
ATTACHMENT(11),
UNKNOWN(-1);
private final int value;
private SlashCommandOptionType(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}

View File

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

View File

@@ -2,29 +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.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 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];
return wrapper.getComponentContextObject(parameter, type);
return marinara.getWrapper().getContextObjectProvider().getComponentContextObject(context, type);
}
@Override
public boolean canRun(Object context) {
return wrapper.getButtonId(context).equals(customId);
return marinara.getWrapper().getButtonId(context).equals(customId);
}
@Override
@@ -32,9 +39,4 @@ public class ButtonInteractionMethod extends InteractionMethod {
return InteractionType.BUTTON;
}
private void parseMethod() {
Button button = getMethod().getAnnotation(Button.class);
this.customId = button.value();
}
}

View File

@@ -7,42 +7,80 @@ 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.AutoComplete;
import net.tomatentum.marinara.interaction.annotation.Button;
import net.tomatentum.marinara.interaction.commands.annotation.SlashCommand;
import net.tomatentum.marinara.interaction.commands.annotation.SubCommand;
import net.tomatentum.marinara.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(AutoComplete.class))
return new AutoCompleteInteractionMethod(method, handler, 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 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 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 +91,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();
}
}

View File

@@ -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().getContextObjectProvider().convertCommandOption(context, 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;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -6,7 +6,6 @@ import java.util.function.Consumer;
import net.tomatentum.marinara.interaction.commands.SlashCommandDefinition;
import net.tomatentum.marinara.interaction.commands.ExecutableSlashCommandDefinition;
import net.tomatentum.marinara.interaction.commands.option.SlashCommandOptionType;
import net.tomatentum.marinara.interaction.InteractionType;
public abstract class LibraryWrapper {
@@ -17,7 +16,6 @@ public abstract class LibraryWrapper {
interactionSubscriber = new ArrayList<>();
}
public void handleInteraction(Object context) {
interactionSubscriber.forEach((o) -> o.accept(context));
}
@@ -32,9 +30,10 @@ public abstract class LibraryWrapper {
public abstract InteractionType getInteractionType(Class<?> clazz);
public abstract void registerSlashCommands(SlashCommandDefinition[] defs);
public abstract Object convertCommandOption(Object context, SlashCommandOptionType type, String optionName);
public abstract ExecutableSlashCommandDefinition getCommandDefinition(Object context);
public abstract String getButtonId(Object context);
public abstract Object getComponentContextObject(Object context, Class<?> type);
public abstract ContextObjectProvider getContextObjectProvider();
}

View File

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

View File

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

View File

@@ -10,10 +10,14 @@ import java.util.Set;
import org.javacord.api.DiscordApi;
import org.javacord.api.interaction.ApplicationCommandInteraction;
import org.javacord.api.interaction.AutocompleteInteraction;
import org.javacord.api.interaction.ButtonInteraction;
import org.javacord.api.interaction.SlashCommandBuilder;
import org.javacord.api.interaction.SlashCommandInteraction;
import org.javacord.api.interaction.SlashCommandInteractionOption;
import org.javacord.api.interaction.SlashCommandOptionBuilder;
import org.javacord.api.interaction.SlashCommandOptionChoiceBuilder;
import org.javacord.api.interaction.SlashCommandOptionType;
import io.leangen.geantyref.AnnotationFormatException;
import io.leangen.geantyref.TypeFactory;
@@ -22,27 +26,31 @@ import net.tomatentum.marinara.interaction.commands.ExecutableSlashCommandDefini
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.interaction.commands.option.SlashCommandOptionType;
import net.tomatentum.marinara.wrapper.ContextObjectProvider;
import net.tomatentum.marinara.wrapper.LibraryWrapper;
public class JavacordWrapper extends LibraryWrapper {
private DiscordApi api;
private JavacordContextObjectProvider contextObjectProvider;
public JavacordWrapper(DiscordApi api) {
this.api = api;
this.contextObjectProvider = new JavacordContextObjectProvider();
api.addInteractionCreateListener((e) -> handleInteraction(e.getInteraction()));
}
@Override
public InteractionType getInteractionType(Class<?> clazz) {
if (AutocompleteInteraction.class.isAssignableFrom(clazz))
return InteractionType.AUTOCOMPLETE;
if (ApplicationCommandInteraction.class.isAssignableFrom(clazz))
return InteractionType.COMMAND;
if (ButtonInteraction.class.isAssignableFrom(clazz))
return InteractionType.BUTTON;
return null;
}
@@ -67,22 +75,6 @@ public class JavacordWrapper extends LibraryWrapper {
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
public ExecutableSlashCommandDefinition getCommandDefinition(Object context) {
if (!(context instanceof SlashCommandInteraction))
@@ -93,11 +85,13 @@ public class JavacordWrapper extends LibraryWrapper {
List<SlashCommandInteractionOption> options = interaction.getOptions();
try {
builder.setApplicationCommand(TypeFactory.annotation(SlashCommand.class, Map.of("name", interaction.getCommandName())));
if (!options.isEmpty()) {
if (!options.getFirst().getArguments().isEmpty()) {
builder.setSubCommandGroup(TypeFactory.annotation(SubCommandGroup.class, Map.of("name", options.getFirst().getName())));
builder.setSubCommand(TypeFactory.annotation(SubCommand.class, Map.of("name", options.getFirst().getOptions().getFirst().getName())));
}else
builder.setSubCommand(TypeFactory.annotation(SubCommand.class, Map.of("name", options.getFirst().getName())));
}
} catch (AnnotationFormatException e) {
e.printStackTrace();
}
@@ -121,43 +115,53 @@ public class JavacordWrapper extends LibraryWrapper {
private org.javacord.api.interaction.SlashCommandOption convertSubCommandGroupDef(SlashCommandDefinition def, SubCommandGroup subGroup) {
SubCommand[] subCommands = def.getSubCommands(subGroup.name());
org.javacord.api.interaction.SlashCommandOption[] convertedSubCommands = (org.javacord.api.interaction.SlashCommandOption[]) Arrays.stream(subCommands).map(this::convertSubCommandDef).toArray();
return org.javacord.api.interaction.SlashCommandOption.createWithOptions(org.javacord.api.interaction.SlashCommandOptionType.SUB_COMMAND_GROUP, subGroup.name(), subGroup.description(), Arrays.asList(convertedSubCommands));
return org.javacord.api.interaction.SlashCommandOption.createWithOptions(
org.javacord.api.interaction.SlashCommandOptionType.SUB_COMMAND_GROUP,
subGroup.name(),
subGroup.description(),
Arrays.asList(convertedSubCommands));
}
private org.javacord.api.interaction.SlashCommandOption convertSubCommandDef(SubCommand sub) {
List<org.javacord.api.interaction.SlashCommandOption> convertedOptions = new ArrayList<>();
Arrays.stream(sub.options()).map(this::convertOptionDef).forEach(convertedOptions::add);
return org.javacord.api.interaction.SlashCommandOption.createWithOptions(org.javacord.api.interaction.SlashCommandOptionType.SUB_COMMAND, sub.name(), sub.description(), convertedOptions);
return org.javacord.api.interaction.SlashCommandOption.createWithOptions(
org.javacord.api.interaction.SlashCommandOptionType.SUB_COMMAND,
sub.name(),
sub.description(),
convertedOptions);
}
private org.javacord.api.interaction.SlashCommandOption convertOptionDef(SlashCommandOption option) {
org.javacord.api.interaction.SlashCommandOptionType type = Enum.valueOf(org.javacord.api.interaction.SlashCommandOptionType.class, option.type().toString());
return org.javacord.api.interaction.SlashCommandOption.create(type, option.name(), option.description(), option.required());
SlashCommandOptionType type = SlashCommandOptionType.fromValue(option.type().getValue());
SlashCommandOptionBuilder builder = new SlashCommandOptionBuilder();
builder
.setType(type)
.setName(option.name())
.setDescription(option.description())
.setRequired(option.required())
.setAutocompletable(option.autocomplete())
.setChoices(convertChoices(option));
return builder.build();
}
private Object getOptionValue(SlashCommandInteractionOption option, SlashCommandOptionType type) {
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.getMentionableValue().get();
case ROLE:
return option.getRoleValue().get();
case STRING:
return option.getStringValue().get();
case USER:
return option.getUserValue().get();
default:
return null;
private List<org.javacord.api.interaction.SlashCommandOptionChoice> convertChoices(SlashCommandOption option) {
List<org.javacord.api.interaction.SlashCommandOptionChoice> convertedChoices = new ArrayList<>();
for (SlashCommandOptionChoice choice : ExecutableSlashCommandDefinition.getActualChoices(option)) {
SlashCommandOptionChoiceBuilder builder = new SlashCommandOptionChoiceBuilder();
builder.setName(choice.name());
if (choice.longValue() != Long.MAX_VALUE)
builder.setValue(choice.longValue());
/*
not yet available
if (choice.doubleValue() != Double.MAX_VALUE)
builder.setValue(choice.doubleValue());
*/
if (!choice.stringValue().isEmpty())
builder.setValue(choice.stringValue());
}
return convertedChoices;
}
@Override
@@ -167,20 +171,8 @@ public class JavacordWrapper extends LibraryWrapper {
}
@Override
public Object getComponentContextObject(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.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;
public ContextObjectProvider getContextObjectProvider() {
return contextObjectProvider;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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);
@@ -26,4 +30,13 @@ public class TestButton implements InteractionHandler {
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!");
}
}

View File

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

View File

@@ -20,7 +20,8 @@ public class TestCommand implements InteractionHandler {
@SlashCommandOption(
name = "foo",
description = "foo bar is very fooby",
type = SlashCommandOptionType.STRING
type = SlashCommandOptionType.STRING,
choiceEnum = TestChoiceEnum.class
)
}
)

View File

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

View File

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

View File

@@ -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

View File

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

View File

@@ -41,52 +41,52 @@ public class SlashCommandInteractionOptionMock implements SlashCommandInteractio
@Override
public Optional<Long> getLongValue() {
throw new UnsupportedOperationException("Unimplemented method 'getLongValue'");
return Optional.empty();
}
@Override
public Optional<Boolean> getBooleanValue() {
throw new UnsupportedOperationException("Unimplemented method 'getBooleanValue'");
return Optional.empty();
}
@Override
public Optional<User> getUserValue() {
throw new UnsupportedOperationException("Unimplemented method 'getUserValue'");
return Optional.empty();
}
@Override
public Optional<CompletableFuture<User>> requestUserValue() {
throw new UnsupportedOperationException("Unimplemented method 'requestUserValue'");
return Optional.empty();
}
@Override
public Optional<ServerChannel> getChannelValue() {
throw new UnsupportedOperationException("Unimplemented method 'getChannelValue'");
return Optional.empty();
}
@Override
public Optional<Attachment> getAttachmentValue() {
throw new UnsupportedOperationException("Unimplemented method 'getAttachmentValue'");
return Optional.empty();
}
@Override
public Optional<Role> getRoleValue() {
throw new UnsupportedOperationException("Unimplemented method 'getRoleValue'");
return Optional.empty();
}
@Override
public Optional<Mentionable> getMentionableValue() {
throw new UnsupportedOperationException("Unimplemented method 'getMentionableValue'");
return Optional.empty();
}
@Override
public Optional<Double> getDecimalValue() {
throw new UnsupportedOperationException("Unimplemented method 'getDecimalValue'");
return Optional.empty();
}
@Override
public Optional<CompletableFuture<Mentionable>> requestMentionableValue() {
throw new UnsupportedOperationException("Unimplemented method 'requestMentionableValue'");
return Optional.empty();
}
@Override