73 Commits

Author SHA1 Message Date
tueem 236c584da8 move Choice classes
github-mirror / push-github (push) Successful in 3s
Build / Gradle-Build (push) Successful in 10s
Test / Gradle-Test (push) Successful in 20s
2025-02-20 09:10:28 +01:00
tueem f6db113deb add CommandConverter stub
github-mirror / push-github (push) Successful in 3s
Build / Gradle-Build (push) Successful in 10s
Test / Gradle-Test (push) Successful in 21s
2025-02-20 00:41:15 +01:00
tueem 9058629af5 remove redundant getFullSlashCommand
github-mirror / push-github (push) Successful in 4s
Build / Gradle-Build (push) Successful in 11s
Test / Gradle-Test (push) Successful in 21s
2025-02-19 21:50:54 +01:00
tueem 8f14b0feb9 split up getSubCommands 2025-02-19 21:49:12 +01:00
tueem 5d8f737481 Merge pull request 'add Discord4J Wrapper' (#14) from wrapper/d4j into dev
github-mirror / push-github (push) Successful in 4s
Build / Gradle-Build (push) Successful in 10s
Publish / Gradle-Publish (push) Successful in 11s
Test / Gradle-Test (push) Successful in 21s
Reviewed-on: #14
2025-02-19 12:43:38 +00:00
tueem 8943d6d4a4 add tests
github-mirror / push-github (push) Successful in 3s
Build / Gradle-Build (push) Successful in 18s
Test / Gradle-Test (push) Successful in 21s
2025-02-19 10:50:30 +01:00
tueem 842fcfe5ac add PermissionCheck 2025-02-19 10:50:19 +01:00
tueem 858fab5e32 fix: Message ContextObject returning Optional 2025-02-19 09:02:52 +01:00
tueem 20471fefea fix: logging causing NullPointerException 2025-02-19 09:01:42 +01:00
tueem 8d27ec28db fix: remove Test Line, allow api to be null, fix getCommandDefinition to also allow ChatInputAutoCompleteEvent 2025-02-18 11:21:32 +01:00
tueem f81602f5db restructure and add discord4j test stub
github-mirror / push-github (push) Successful in 3s
Build / Gradle-Build (push) Successful in 9s
Test / Gradle-Test (push) Failing after 10s
2025-02-17 23:59:08 +01:00
tueem dbad157058 adjust actions to add new wrapper
github-mirror / push-github (push) Successful in 5s
Build / Gradle-Build (push) Successful in 9s
Test / Gradle-Test (push) Failing after 13s
2025-02-17 20:24:40 +01:00
tueem ad19ed6ada add First prototype of Discord4J wrapper
github-mirror / push-github (push) Successful in 4s
Build / Gradle-Build (push) Successful in 21s
Test / Gradle-Test (push) Successful in 14s
2025-02-17 20:13:31 +01:00
tueem ca822909e3 fix: consistency 2025-02-17 19:55:03 +01:00
tueem caeaec1926 add: fromValue method to SlashCommandOptionType 2025-02-17 19:45:18 +01:00
tueem f4dbdc302d fix: improve consistency 2025-02-17 19:44:52 +01:00
tueem fd87431d51 Merge remote-tracking branch 'origin/dev' into wrapper/d4j 2025-02-17 13:06:27 +01:00
tueem 3b65784770 Merge pull request 'Add Choices and Autocomplete' (#8) from feat/choices into dev
github-mirror / push-github (push) Successful in 30s
Build / Gradle-Build (push) Successful in 14s
Publish / Gradle-Publish (push) Successful in 10s
Test / Gradle-Test (push) Successful in 14s
Reviewed-on: #8
Double choices missing because PR will not get merged since Javacord seized development
2025-02-17 10:14:37 +00:00
tueem b4af922ac2 change: getInteractionType check changed from class based to context based check
github-mirror / push-github (push) Successful in 4s
Build / Gradle-Build (push) Successful in 16s
Test / Gradle-Test (push) Successful in 14s
2025-02-17 09:15:54 +01:00
tueem 1146fa1cc6 Merge branch 'dev' into feat/choices
github-mirror / push-github (push) Successful in 4s
Build / Gradle-Build (push) Successful in 34s
Test / Gradle-Test (push) Successful in 41s
2024-12-20 19:30:35 +01:00
tueem 9d88ca902d fix NoSuchElement case on command with no options
github-mirror / push-github (push) Successful in 4s
Build / Gradle-Build (push) Successful in 35s
Publish / Gradle-Publish (push) Successful in 36s
Test / Gradle-Test (push) Successful in 43s
2024-12-20 19:27:36 +01:00
tueem e9b5610f3c Merge pull request 'add Logging to various locations' (#11) from feat/logging into dev
github-mirror / push-github (push) Successful in 4s
Build / Gradle-Build (push) Successful in 37s
Publish / Gradle-Publish (push) Successful in 33s
Test / Gradle-Test (push) Successful in 40s
Reviewed-on: #11
2024-12-20 17:51:24 +00:00
tueem 8e872e7ada Merge branch 'dev' into feat/logging
github-mirror / push-github (push) Successful in 4s
Build / Gradle-Build (push) Successful in 32s
Test / Gradle-Test (push) Successful in 40s
2024-12-20 17:05:18 +01:00
tueem 43c5946227 Merge branch 'dev' into feat/choices
github-mirror / push-github (push) Successful in 4s
Build / Gradle-Build (push) Successful in 32s
Test / Gradle-Test (push) Successful in 40s
2024-12-20 17:04:13 +01:00
tueem 71c910ab93 fix equality check of ExecutableSlashCommand
github-mirror / push-github (push) Successful in 4s
Build / Gradle-Build (push) Successful in 32s
Publish / Gradle-Publish (push) Successful in 32s
Test / Gradle-Test (push) Successful in 39s
2024-12-20 17:03:35 +01:00
tueem c5a7f3665e Merge branch 'dev' into feat/choices
github-mirror / push-github (push) Successful in 5s
Build / Gradle-Build (push) Successful in 32s
Test / Gradle-Test (push) Successful in 40s
2024-12-20 14:56:35 +01:00
tueem 3baf1eda9a log the result of an InteractionCheck
github-mirror / push-github (push) Successful in 4s
Build / Gradle-Build (push) Successful in 32s
Test / Gradle-Test (push) Successful in 38s
2024-12-20 00:44:48 +01:00
tueem 628abb4239 fix order and wording
github-mirror / push-github (push) Successful in 4s
Build / Gradle-Build (push) Successful in 32s
Test / Gradle-Test (push) Successful in 42s
2024-12-20 00:32:20 +01:00
tueem 9a89544a9e Merge branch 'dev' into feat/logging
github-mirror / push-github (push) Successful in 5s
Build / Gradle-Build (push) Successful in 31s
Test / Gradle-Test (push) Successful in 41s
2024-12-19 22:11:21 +01:00
tueem 455fc4955f fix ExecutableSlashCommandDefinition toString method with all various checks
github-mirror / push-github (push) Successful in 4s
Build / Gradle-Build (push) Successful in 33s
Publish / Gradle-Publish (push) Successful in 33s
Test / Gradle-Test (push) Successful in 39s
2024-12-19 22:11:06 +01:00
tueem b72c55fc6e Merge branch 'dev' into feat/logging
github-mirror / push-github (push) Successful in 4s
Build / Gradle-Build (push) Successful in 32s
Test / Gradle-Test (push) Failing after 39s
2024-12-19 21:23:48 +01:00
tueem 3691434f66 fix toString method of ExecutableSlashCommandDefinition and added subCommandGroup display
github-mirror / push-github (push) Successful in 4s
Build / Gradle-Build (push) Successful in 32s
Publish / Gradle-Publish (push) Successful in 33s
Test / Gradle-Test (push) Successful in 39s
2024-12-19 21:23:24 +01:00
tueem 74d55d81ca add remaining logging in Javacord wrapper and exceptions
github-mirror / push-github (push) Successful in 4s
Build / Gradle-Build (push) Successful in 32s
Test / Gradle-Test (push) Failing after 41s
2024-12-19 21:12:36 +01:00
tueem 404f221ccf added Logging for InteractionChecks
github-mirror / push-github (push) Successful in 4s
Build / Gradle-Build (push) Successful in 33s
Test / Gradle-Test (push) Failing after 40s
2024-12-19 21:07:42 +01:00
tueem 7249c99b69 add Logging to Parsers 2024-12-19 20:38:04 +01:00
tueem b764972eba make use of new helper method in InteractionMethod 2024-12-19 20:15:22 +01:00
tueem 83ee4b1efa add method name helper method 2024-12-19 20:04:53 +01:00
tueem 6b86e9ff87 add initialization Logging 2024-12-19 19:41:49 +01:00
tueem 7a40aebd6d add Interactionmethod logging 2024-12-19 19:41:37 +01:00
tueem 76ab779ab2 fix position of Logging in InteractionRegistry
github-mirror / push-github (push) Successful in 5s
Build / Gradle-Build (push) Successful in 35s
Test / Gradle-Test (push) Successful in 47s
2024-12-19 18:36:51 +01:00
tueem 3d19fae6b8 fix null issue in InteractionRegistry
github-mirror / push-github (push) Successful in 4s
Build / Gradle-Build (push) Successful in 33s
Test / Gradle-Test (push) Successful in 44s
2024-12-19 18:34:00 +01:00
tueem 1ecbc563a6 add Logging in InteractionRegistry
github-mirror / push-github (push) Successful in 5s
Build / Gradle-Build (push) Successful in 34s
Test / Gradle-Test (push) Failing after 44s
2024-12-19 18:07:33 +01:00
tueem bf0022775d add helper LoggerUtil method 2024-12-19 13:30:36 +01:00
tueem a17f5e826f add LoggerUtil to decide which Logger to get
github-mirror / push-github (push) Successful in 5s
Build / Gradle-Build (push) Successful in 35s
Test / Gradle-Test (push) Successful in 39s
2024-12-19 12:28:45 +01:00
tueem 3de0f32074 change to just javacord api dependency 2024-12-19 12:28:27 +01:00
tueem aaf4f3297a add autocomplete option toggle, add double value for non javacord wrappers, rename OptionChoices to match with discords naming and general cleanup
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
tueem 445190db89 added Tests for choices 2024-12-16 12:49:51 +01:00
tueem 9d3a6b8b85 add Autocomplete Test
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
tueem 1cb6cd0e05 clean up code and switch to request instead of getting from cache 2024-12-15 23:15:29 +01:00
tueem a5e1230fc6 fix issues with ExecutableSlashCommandDefinition equals check 2024-12-15 23:13:57 +01:00
tueem 432db43bf5 add remaining parts of AutocompleteInteraction
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
tueem 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
tueem f32c7045a1 remove unnecessary getClass call fixing test
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
tueem 69b27e4554 add prototype choices implementation
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
tueem d8504a7cde Merge pull request 'add Checks system' (#6) from feat/checks into dev
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
tueem bef34ee548 fix null issue
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
tueem 29bb7e667e add PermissionCHeck test
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
tueem 94da2a0e3c change logic to always give objects the highest value 2024-12-02 21:20:49 +01:00
tueem 7f47130461 add missing annotation annotations 2024-12-02 21:20:26 +01:00
tueem 83a3efd4b8 add canRun check which i forgor 2024-12-02 21:19:58 +01:00
tueem aefd8a51a0 change wrong annotation class usage 2024-12-02 21:19:42 +01:00
tueem 4332592dfa add Javacord PermissionCheck
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
tueem c363ab9744 add ability to return false to cancel execution in pre Checks and remove return type on post checks
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
tueem 33392b02fb add InteractionCheck test
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
tueem b7333c2e5e fix problem with multiple method overrides in generic types. 2024-11-29 21:41:18 +01:00
tueem 239e921e6f change Annotation#getClass to Annotation#annotationType because it was not working as expected 2024-11-29 21:37:13 +01:00
tueem 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
tueem 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
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
tueem 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
tueem f89ae5e425 - add prototype Interactioncheck impementation.
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
tueem 582e0f0bae implement AnnotationParser system
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
tueem 0ea330d48b move to seperate files because gitea does not have expressions yet
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
tueem c241f6b1fe enable dev branch publishing
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
70 changed files with 2283 additions and 239 deletions
+4 -1
View File
@@ -38,7 +38,10 @@ jobs:
run: chmod +x gradlew; ./gradlew assemble
- name: Move artifacts
run: mkdir artifacts; mv lib/build/libs/*.jar artifacts; mv wrapper/javacord/build/libs/*.jar artifacts;
run: |
mkdir artifacts; mv lib/build/libs/*.jar artifacts;
mv wrapper/javacord/build/libs/*.jar artifacts;
mv wrapper/discord4j/build/libs/*.jar artifacts;
- name: Upload artifact
uses: christopherhx/gitea-upload-artifact@v4
+40
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
+4 -4
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
+1
View File
@@ -44,6 +44,7 @@ jobs:
mkdir test-results/;
[ -d lib/build/test-results/test/ ] && mv lib/build/test-results/test/*.xml test-results/;
[ -d wrapper/javacord/build/test-results/test/ ] && mv wrapper/javacord/build/test-results/test/*.xml test-results/;
[ -d wrapper/discord4j/build/test-results/test/ ] && mv wrapper/discord4j/build/test-results/test/*.xml test-results/;
- name: Upload Test Result
uses: christopherhx/gitea-upload-artifact@v4
+3
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"])
}
}
+5 -1
View File
@@ -5,10 +5,14 @@
junit-jupiter = "5.10.2"
log4j = "2.24.1"
javacord = "3.8.0"
discord4j = "3.2.7"
geantyref = "2.0.0"
mockito = "5.15.2"
[libraries]
junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit-jupiter" }
log4j = { module = "org.apache.logging.log4j:log4j-core", version.ref = "log4j"}
log4j = { module = "org.apache.logging.log4j:log4j-api", version.ref = "log4j"}
javacord = { module = "org.javacord:javacord", version.ref = "javacord"}
discord4j = { module = "com.discord4j:discord4j-core", version.ref = "discord4j"}
geantyref = { module = "io.leangen.geantyref:geantyref", version.ref = "geantyref"}
mockito = {module = "org.mockito:mockito-core", version.ref = "mockito"}
+2 -2
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)
}
}
@@ -1,22 +1,40 @@
package net.tomatentum.marinara;
import org.apache.logging.log4j.Logger;
import net.tomatentum.marinara.registry.InteractionCheckRegistry;
import net.tomatentum.marinara.registry.InteractionRegistry;
import net.tomatentum.marinara.util.LoggerUtil;
import net.tomatentum.marinara.wrapper.LibraryWrapper;
public class Marinara {
private Logger logger = LoggerUtil.getLogger(getClass());
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();
logger.info("Marinara loaded successfully!");
}
public InteractionRegistry getRegistry() {
return registry;
}
public InteractionCheckRegistry getCheckRegistry() {
return checkRegistry;
}
public LibraryWrapper getWrapper() {
return wrapper;
}
}
@@ -0,0 +1,50 @@
package net.tomatentum.marinara.checks;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import org.apache.logging.log4j.Logger;
import net.tomatentum.marinara.util.LoggerUtil;
import net.tomatentum.marinara.util.ReflectionUtil;
public record AppliedCheck(InteractionCheck<?> check, Annotation annotation) {
private static Logger logger = LoggerUtil.getLogger(AppliedCheck.class);
public boolean pre(Object context) {
Method[] methods = Arrays.stream(check.getClass().getMethods())
.filter(x -> x.getName().equals("preExec"))
.filter(x -> !x.isBridge())
.toArray(s -> new Method[s]);
Method method = ReflectionUtil.getMostSpecificMethod(methods, context.getClass(), annotation.annotationType());
method.setAccessible(true);
try {
logger.debug("Executing pre check {} with context {}", check.getClass().getName(), context.toString());
boolean result = (boolean) method.invoke(check, context, annotation);
logger.debug("Pre Check {} {} with context {}", check.getClass().getName(), result ? "succeeded" : "failed", context.toString());
return result;
} catch (IllegalAccessException | InvocationTargetException | SecurityException e) {
logger.fatal(e);
return false;
}
}
public void post(Object context) {
Method[] methods = Arrays.stream(check.getClass().getMethods())
.filter(x -> x.getName().equals("postExec"))
.filter(x -> !x.isBridge())
.toArray(s -> new Method[s]);
Method method = ReflectionUtil.getMostSpecificMethod(methods, context.getClass(), annotation.annotationType());
method.setAccessible(true);
try {
logger.debug("Executing post check {} with context {}", check.getClass().getName(), context.toString());
method.invoke(check, context, annotation);
} catch (IllegalAccessException | InvocationTargetException | SecurityException e) {
logger.fatal(e);
}
}
}
@@ -0,0 +1,10 @@
package net.tomatentum.marinara.checks;
import java.lang.annotation.Annotation;
public interface InteractionCheck<A extends Annotation> {
public boolean preExec(Object context, A annotation);
public void postExec(Object context, A annotation);
}
@@ -0,0 +1,12 @@
package net.tomatentum.marinara.interaction.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoComplete {
}
@@ -0,0 +1,23 @@
package net.tomatentum.marinara.interaction.commands;
import net.tomatentum.marinara.interaction.commands.annotation.SlashCommandOption;
import net.tomatentum.marinara.interaction.commands.annotation.SubCommand;
import net.tomatentum.marinara.interaction.commands.annotation.SubCommandGroup;
public class CommandConverter<A extends Object> {
private Spec<A, ?, ?> spec;
public A convert(SlashCommandDefinition def) {
return null;
}
public interface Spec<A extends Object, O extends Object, C extends Object> {
public A convertCommand(SlashCommandDefinition def);
public O convertSubCommand(SubCommand def, O[] options);
public O convertSubCommandGroup(SubCommandGroup def, O[] options);
public O convertOption(SlashCommandOption option, C[] choices);
public C[] convertChoice(SlashCommandOption option);
}
}
@@ -2,8 +2,11 @@ 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;
import net.tomatentum.marinara.interaction.commands.choice.EnumChoices;
public record ExecutableSlashCommandDefinition(
SlashCommand applicationCommand,
@@ -11,19 +14,41 @@ 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
public final String toString() {
return applicationCommand.name() + subCommand.name() != null ? "::" + subCommand.name() : "";
StringBuilder builder = new StringBuilder();
builder.append(applicationCommand.name());
if (subCommandGroup != null && subCommandGroup.name() != null)
builder.append("::").append(subCommandGroup.name());
if (subCommand != null && subCommand.name() != null)
builder.append("::").append(subCommand.name());
return builder.toString();
}
public boolean isRootCommand() {
@@ -51,21 +51,16 @@ public class SlashCommandDefinition {
subCommandGroupMap.put(x.name(), x);
});
return subCommandGroupMap.values().toArray(new SubCommandGroup[0]);
return subCommandGroupMap.values().toArray(SubCommandGroup[]::new);
}
public SubCommand[] getSubCommands(String groupName) {
public SubCommand[] getSubCommands() {
List<SubCommand> subCommands;
if (groupName == null)
subCommands = Arrays.stream(getExecutableDefinitons())
.filter((x) -> x.subCommandGroup() == null && x.subCommand() != null)
.map((x) -> x.subCommand())
.toList();
else
subCommands = Arrays.stream(getExecutableDefinitons())
.filter((x) -> x.subCommandGroup().name().equals(groupName) && x.subCommand() != null)
.map((x) -> x.subCommand())
.toList();
subCommands = Arrays.stream(getExecutableDefinitons())
.filter((x) -> x.subCommandGroup() == null && x.subCommand() != null)
.map((x) -> x.subCommand())
.toList();
HashMap<String, SubCommand> subCommandMap = new HashMap<>();
subCommands.forEach((x) -> {
@@ -74,18 +69,24 @@ public class SlashCommandDefinition {
subCommandMap.put(x.name(), x);
});
return subCommandMap.values().toArray(new SubCommand[0]);
return subCommandMap.values().toArray(SubCommand[]::new);
}
public SlashCommand getFullSlashCommand() {
if (isRootCommand())
return getSlashCommand();
for (ExecutableSlashCommandDefinition executableSlashCommandDefinition : executableDefinitons) {
if (executableSlashCommandDefinition.options().length > 0)
return executableSlashCommandDefinition.applicationCommand();
}
public SubCommand[] getSubCommands(String groupName) {
List<SubCommand> subCommands;
subCommands = Arrays.stream(getExecutableDefinitons())
.filter((x) -> x.subCommandGroup().name().equals(groupName) && x.subCommand() != null)
.map((x) -> x.subCommand())
.toList();
HashMap<String, SubCommand> subCommandMap = new HashMap<>();
subCommands.forEach((x) -> {
SubCommand current = subCommandMap.get(x.name());
if (current == null || (current.description().isBlank() && !x.description().isBlank()))
subCommandMap.put(x.name(), x);
});
return null;
return subCommandMap.values().toArray(SubCommand[]::new);
}
public SlashCommand getSlashCommand() {
@@ -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 {
}
}
@@ -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 "";
}
@@ -0,0 +1,5 @@
package net.tomatentum.marinara.interaction.commands.choice;
public interface ChoiceValueProvider<T> {
T getChoiceValue();
}
@@ -0,0 +1,78 @@
package net.tomatentum.marinara.interaction.commands.choice;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import io.leangen.geantyref.AnnotationFormatException;
import io.leangen.geantyref.GenericTypeReflector;
import io.leangen.geantyref.TypeFactory;
import net.tomatentum.marinara.interaction.commands.annotation.SlashCommandOptionChoice;
public record EnumChoices(Class<? extends Enum<?>> enumClass, ChoiceType type, SlashCommandOptionChoice[] choices) {
private static Method method;
static {
try {
method = ChoiceValueProvider.class.getMethod("getChoiceValue");
} catch (NoSuchMethodException | SecurityException e) {
e.printStackTrace();
}
}
public static EnumChoices of(Class<? extends Enum<?>> enumClass) {
if (!ChoiceValueProvider.class.isAssignableFrom(enumClass))
throw new IllegalArgumentException("Provided class needs to implement the ChoiceValueProvider interface.");
ChoiceType type = parseChoiceType(enumClass);
SlashCommandOptionChoice[] choices = parseChoices(enumClass, type);
return new EnumChoices(enumClass, type, choices);
}
private static ChoiceType parseChoiceType(Class<? extends Enum<?>> enumClass) {
ParameterizedType type = (ParameterizedType) GenericTypeReflector.getExactSuperType(enumClass, ChoiceValueProvider.class);
Type typeParam = type.getActualTypeArguments()[0];
if (!(typeParam instanceof Class<?>))
throw new IllegalArgumentException("ChoiceValueProvider need either a String or Number type parameter.");
if (Long.class.isAssignableFrom((Class<?>) typeParam))
return ChoiceType.INTEGER;
if (Double.class.isAssignableFrom((Class<?>) typeParam))
return ChoiceType.DOUBLE;
if (String.class.isAssignableFrom((Class<?>) typeParam))
return ChoiceType.String;
throw new IllegalArgumentException("ChoiceValueProvider need either a String, Number or Decimal type parameter.");
}
private static SlashCommandOptionChoice[] parseChoices(Class<? extends Enum<?>> enumClass, ChoiceType type) {
Enum<? extends Enum<?>>[] constants = enumClass.getEnumConstants();
List<SlashCommandOptionChoice> choices = new ArrayList<>();
for (Enum<? extends Enum<?>> enumInstance : constants) {
Object value;
try {
value = method.invoke(enumInstance);
if (type.equals(ChoiceType.INTEGER))
choices.add(TypeFactory.annotation(SlashCommandOptionChoice.class, Map.of("name", enumInstance.name(), "longValue", value)));
if (type.equals(ChoiceType.DOUBLE))
choices.add(TypeFactory.annotation(SlashCommandOptionChoice.class, Map.of("name", enumInstance.name(), "doubleValue", value)));
if (type.equals(ChoiceType.String))
choices.add(TypeFactory.annotation(SlashCommandOptionChoice.class, Map.of("name", enumInstance.name(), "stringValue", value)));
} catch (IllegalAccessException | InvocationTargetException | AnnotationFormatException e) {
e.printStackTrace();
return null;
}
}
return choices.toArray(SlashCommandOptionChoice[]::new);
}
public static enum ChoiceType {
String,
INTEGER,
DOUBLE
}
}
@@ -1,16 +1,35 @@
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;
}
public static SlashCommandOptionType fromValue(int value) {
for (SlashCommandOptionType type : values()) {
if (type.getValue() == value) {
return type;
}
}
return UNKNOWN;
}
}
@@ -0,0 +1,51 @@
package net.tomatentum.marinara.interaction.methods;
import java.lang.reflect.Method;
import net.tomatentum.marinara.Marinara;
import net.tomatentum.marinara.interaction.InteractionHandler;
import net.tomatentum.marinara.interaction.InteractionType;
import net.tomatentum.marinara.interaction.commands.ExecutableSlashCommandDefinition;
import net.tomatentum.marinara.parser.AnnotationParser;
import net.tomatentum.marinara.parser.SlashCommandParser;
public class AutoCompleteInteractionMethod extends InteractionMethod {
private ExecutableSlashCommandDefinition commandDefinition;
public AutoCompleteInteractionMethod(Method method,
InteractionHandler handler,
Marinara marinara
) {
super(method, handler, marinara);
}
@Override
public AnnotationParser[] getParsers() {
return new AnnotationParser[] {
new SlashCommandParser(method, (x) -> { this.commandDefinition = x; } )
};
}
@Override
public Object getParameter(Object context, int index) {
Class<?> type = getMethod().getParameterTypes()[index+1];
Object autocompleteOptionValue = marinara.getWrapper().getContextObjectProvider().getAutocompleteFocusedOption(context);
if (autocompleteOptionValue != null)
return autocompleteOptionValue;
return marinara.getWrapper().getContextObjectProvider().getComponentContextObject(context, type);
}
@Override
public boolean canRun(Object context) {
ExecutableSlashCommandDefinition other = marinara.getWrapper().getCommandDefinition(context);
return commandDefinition.equals(other);
}
@Override
public InteractionType getType() {
return InteractionType.AUTOCOMPLETE;
}
}
@@ -2,39 +2,41 @@ 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
public InteractionType getType() {
return InteractionType.BUTTON;
}
private void parseMethod() {
Button button = getMethod().getAnnotation(Button.class);
this.customId = button.value();
}
}
@@ -7,62 +7,100 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.apache.logging.log4j.Logger;
import net.tomatentum.marinara.Marinara;
import net.tomatentum.marinara.checks.AppliedCheck;
import net.tomatentum.marinara.interaction.InteractionHandler;
import net.tomatentum.marinara.interaction.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;
import net.tomatentum.marinara.util.LoggerUtil;
import net.tomatentum.marinara.util.ReflectionUtil;
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) {
private Logger logger = LoggerUtil.getLogger(getClass());
protected InteractionMethod(Method method,
InteractionHandler handler,
Marinara marinara
) {
if (!Arrays.asList(handler.getClass().getMethods()).contains(method))
throw new InvalidParameterException("Method does not apply to specified handler");
this.method = method;
this.handler = handler;
this.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) {
int parameterCount = method.getParameterCount();
List<Object> parameters = new ArrayList<>();
for (int i = 0; i < parameterCount; i++) {
if (i == 0) {
parameters.add(context);
continue;
}
parameters.add(getParameter(context, i-1));
}
if (this.appliedChecks.stream().filter(x -> !x.pre(context)).count() > 0)
return;
method.setAccessible(true);
try {
method.invoke(handler, parameters.toArray());
method.invoke(handler, getParameters(context));
}catch (IllegalAccessException | InvocationTargetException ex) {
throw new RuntimeException(ex);
logger.fatal(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<>();
for (int i = 0; i < parameterCount; i++) {
Object parameter;
if (i == 0) {
parameter = context;
}else
parameter = getParameter(context, i-1);
logger.trace("Found parameter {}={} for method {}", parameter != null ? parameter.getClass().toString() : " ", parameter, ReflectionUtil.getFullMethodName(method));
parameters.add(parameter);
}
return parameters.toArray();
}
}
@@ -2,32 +2,36 @@ package net.tomatentum.marinara.interaction.methods;
import java.lang.reflect.Method;
import 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;
}
}
@@ -0,0 +1,8 @@
package net.tomatentum.marinara.parser;
import java.lang.reflect.Method;
public interface AnnotationParser {
void parse();
Method getMethod();
}
@@ -0,0 +1,36 @@
package net.tomatentum.marinara.parser;
import java.lang.reflect.Method;
import java.util.function.Consumer;
import org.apache.logging.log4j.Logger;
import net.tomatentum.marinara.interaction.annotation.Button;
import net.tomatentum.marinara.util.LoggerUtil;
import net.tomatentum.marinara.util.ReflectionUtil;
public class ButtonParser implements AnnotationParser {
private Method method;
private Consumer<String> consumer;
private Logger logger = LoggerUtil.getLogger(getClass());
public ButtonParser(Method method, Consumer<String> consumer) {
this.method = method;
this.consumer = consumer;
}
@Override
public void parse() {
Button button = getMethod().getAnnotation(Button.class);
logger.trace("Parsed Button annotation {} for method {}", button.toString(), ReflectionUtil.getFullMethodName(method));
this.consumer.accept(button.value());
}
@Override
public Method getMethod() {
return this.method;
}
}
@@ -0,0 +1,51 @@
package net.tomatentum.marinara.parser;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Optional;
import java.util.function.Consumer;
import org.apache.logging.log4j.Logger;
import net.tomatentum.marinara.checks.AppliedCheck;
import net.tomatentum.marinara.checks.InteractionCheck;
import net.tomatentum.marinara.registry.InteractionCheckRegistry;
import net.tomatentum.marinara.util.LoggerUtil;
import net.tomatentum.marinara.util.ReflectionUtil;
public class InteractionCheckParser implements AnnotationParser {
private InteractionCheckRegistry checkRegistry;
private Method method;
private Consumer<AppliedCheck> consumer;
private Logger logger = LoggerUtil.getLogger(getClass());
public InteractionCheckParser(Method method, Consumer<AppliedCheck> consumer, InteractionCheckRegistry checkRegistry) {
this.checkRegistry = checkRegistry;
this.method = method;
this.consumer = consumer;
}
@Override
public void parse() {
Annotation[] annotations = method.getAnnotations();
Arrays.stream(annotations).forEach(this::convertAnnotation);
}
private void convertAnnotation(Annotation annotation) {
Optional<InteractionCheck<?>> check = this.checkRegistry.getCheckFromAnnotation(annotation.annotationType());
if (check.isPresent()) {
AppliedCheck appliedCheck = new AppliedCheck(check.get(), annotation);
logger.trace("Parsed InteractionCheck {} for annotation {} for method {}", check.getClass().getName(), annotation.toString(), ReflectionUtil.getFullMethodName(method));
consumer.accept(appliedCheck);
}
}
@Override
public Method getMethod() {
return this.method;
}
}
@@ -0,0 +1,71 @@
package net.tomatentum.marinara.parser;
import java.lang.reflect.Method;
import java.util.function.Consumer;
import org.apache.logging.log4j.Logger;
import net.tomatentum.marinara.interaction.commands.ExecutableSlashCommandDefinition;
import net.tomatentum.marinara.interaction.commands.annotation.SlashCommand;
import net.tomatentum.marinara.interaction.commands.annotation.SubCommand;
import net.tomatentum.marinara.interaction.commands.annotation.SubCommandGroup;
import net.tomatentum.marinara.util.LoggerUtil;
import net.tomatentum.marinara.util.ReflectionUtil;
public class SlashCommandParser implements AnnotationParser {
private Method method;
private Consumer<ExecutableSlashCommandDefinition> consumer;
private Logger logger = LoggerUtil.getLogger(getClass());
public SlashCommandParser(Method method, Consumer<ExecutableSlashCommandDefinition> consumer) {
this.method = method;
this.consumer = consumer;
}
@Override
public void parse() {
this.checkValidCommandMethod(method);
SlashCommand cmd = ReflectionUtil.getAnnotation(method, SlashCommand.class);
ExecutableSlashCommandDefinition.Builder builder = new ExecutableSlashCommandDefinition.Builder();
builder.setApplicationCommand(cmd);
if (ReflectionUtil.isAnnotationPresent(method, SubCommandGroup.class)) {
SubCommandGroup cmdGroup = ReflectionUtil.getAnnotation(method, SubCommandGroup.class);
builder.setSubCommandGroup(cmdGroup);
}
if (ReflectionUtil.isAnnotationPresent(method, SubCommand.class)) {
SubCommand subCmd = ReflectionUtil.getAnnotation(method, SubCommand.class);
builder.setSubCommand(subCmd);
}
ExecutableSlashCommandDefinition def = builder.build();
logger.trace("Parsed using SlashCommandParser for method {} with the result:\n{}", ReflectionUtil.getFullMethodName(method), def.toString());
consumer.accept(builder.build());
}
@Override
public Method getMethod() {
return this.method;
}
private void checkValidCommandMethod(Method method) {
if (method.isAnnotationPresent(SlashCommand.class) &&
method.getDeclaringClass().isAnnotationPresent(SlashCommand.class)) {
throw new RuntimeException(method.getName() + ": Can't have ApplicationCommand Annotation on Class and Method");
}
if (!ReflectionUtil.isAnnotationPresent(method, SlashCommand.class))
throw new RuntimeException(method.getName() + ": Missing ApplicationCommand Annotation on either Class or Method");
if ((method.isAnnotationPresent(SubCommand.class) &&
!ReflectionUtil.isAnnotationPresent(method, SlashCommand.class))) {
throw new RuntimeException(method.getName() + ": Missing ApplicationCommand Annotation on either Method or Class");
}
}
}
@@ -0,0 +1,40 @@
package net.tomatentum.marinara.registry;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import org.apache.logging.log4j.Logger;
import io.leangen.geantyref.GenericTypeReflector;
import net.tomatentum.marinara.checks.InteractionCheck;
import net.tomatentum.marinara.util.LoggerUtil;
public class InteractionCheckRegistry {
private List<InteractionCheck<?>> checks;
private Logger logger = LoggerUtil.getLogger(getClass());
public InteractionCheckRegistry() {
this.checks = new ArrayList<>();
}
public void addCheck(InteractionCheck<?> check) {
checks.add(check);
logger.info("Registered Check {}", check.getClass().getName());
}
public Optional<InteractionCheck<?>> getCheckFromAnnotation(Type annotation) {
for (InteractionCheck<?> interactionCheck : checks) {
ParameterizedType type = (ParameterizedType) GenericTypeReflector.getExactSuperType(interactionCheck.getClass(), InteractionCheck.class);
Type typeParam = type.getActualTypeArguments()[0];
if (typeParam.equals(annotation))
return Optional.of(interactionCheck);
}
return Optional.empty();
}
}
@@ -5,30 +5,37 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import org.apache.logging.log4j.Logger;
import net.tomatentum.marinara.Marinara;
import net.tomatentum.marinara.interaction.InteractionHandler;
import net.tomatentum.marinara.interaction.InteractionType;
import net.tomatentum.marinara.interaction.commands.SlashCommandDefinition;
import net.tomatentum.marinara.interaction.commands.ExecutableSlashCommandDefinition;
import net.tomatentum.marinara.interaction.methods.SlashCommandInteractionMethod;
import net.tomatentum.marinara.util.LoggerUtil;
import net.tomatentum.marinara.interaction.methods.InteractionMethod;
import net.tomatentum.marinara.wrapper.LibraryWrapper;
public class InteractionRegistry {
private Logger logger = LoggerUtil.getLogger(getClass());
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);
if (iMethod != null)
InteractionMethod iMethod = InteractionMethod.create(method, interactionHandler, marinara);
if (iMethod != null) {
this.interactionMethods.add(iMethod);
logger.debug("Added {} method from {}", iMethod.getMethod().getName(), interactionHandler.getClass().getSimpleName());
}
}
logger.info("Added all Interactions from {}", interactionHandler.getClass().getSimpleName());
}
public void registerCommands() {
@@ -46,16 +53,26 @@ public class InteractionRegistry {
appDef.get().addExecutableCommand(def);
else
defs.add(new SlashCommandDefinition(def.applicationCommand()).addExecutableCommand(def));
logger.debug("Added Executable Command {}{}{} for registration",
def.applicationCommand().name(),
def.subCommandGroup() == null ? "" : "." + def.subCommandGroup().name(),
def.subCommand() == null ? "" : "." + def.subCommand().name()
);
});
wrapper.registerSlashCommands(defs.toArray(new SlashCommandDefinition[0]));
marinara.getWrapper().registerSlashCommands(defs.toArray(SlashCommandDefinition[]::new));
logger.info("Registered all SlashCommands");
}
public void handle(Object context) {
InteractionType type = marinara.getWrapper().getInteractionType(context);
logger.debug("Received {} interaction ", context);
interactionMethods.forEach((m) -> {
InteractionType type = wrapper.getInteractionType(context.getClass());
if (m.getType().equals(type))
if (m.getType().equals(type) && m.canRun(context)) {
logger.info("Running {} interaction using {}\ncontext: {}", type, m.getMethod().toString(), context.toString());
m.run(context);
}
});
}
}
@@ -0,0 +1,24 @@
package net.tomatentum.marinara.util;
import java.util.Properties;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.simple.SimpleLogger;
import org.apache.logging.log4j.util.PropertiesUtil;
import org.apache.logging.log4j.util.ProviderUtil;
public class LoggerUtil {
public static Logger getLogger(String name) {
if (ProviderUtil.hasProviders()) {
return LogManager.getLogger(name);
}else
return new SimpleLogger(name, Level.DEBUG, true, false, true, true, "yyyy-MM-dd HH:mm:ss.SSSZ", null,
new PropertiesUtil(new Properties()), System.out);
}
public static Logger getLogger(Class<?> clazz) {
return getLogger(clazz.getName());
}
}
@@ -2,9 +2,10 @@ package net.tomatentum.marinara.util;
import java.lang.annotation.Annotation;
import java.lang.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,86 @@ 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;
}
public static String getFullMethodName(Method method) {
return method.getClass().getName() + "." + method.getName();
}
}
@@ -0,0 +1,11 @@
package net.tomatentum.marinara.wrapper;
public interface ContextObjectProvider {
public Object convertCommandOption(Object context, String optionName);
public Object getComponentContextObject(Object context, Class<?> type);
public Object getInteractionContextObject(Object context, Class<?> type);
public Object getAutocompleteFocusedOption(Object context);
}
@@ -6,7 +6,6 @@ import java.util.function.Consumer;
import net.tomatentum.marinara.interaction.commands.SlashCommandDefinition;
import net.tomatentum.marinara.interaction.commands.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));
}
@@ -29,12 +27,13 @@ public abstract class LibraryWrapper {
interactionSubscriber.remove(consumer);
}
public abstract InteractionType getInteractionType(Class<?> clazz);
public abstract InteractionType getInteractionType(Object context);
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();
}
+4
View File
@@ -13,5 +13,9 @@ plugins {
rootProject.name = "Marinara"
include(":lib")
include(":wrapper-javacord")
include(":wrapper-discord4j")
project(":wrapper-javacord").projectDir = file("wrapper/javacord")
project(":wrapper-discord4j").projectDir = file("wrapper/discord4j")
+42
View File
@@ -0,0 +1,42 @@
/*
* This file was generated by the Gradle 'init' task.
*
* This generated file contains a sample Java library project to get you started.
* For more details on building Java & JVM projects, please refer to https://docs.gradle.org/8.8/userguide/building_java_projects.html in the Gradle documentation.
*/
plugins {
// Apply the java-library plugin for API and implementation separation.
`java-library`
}
repositories {
// Use Maven Central for resolving dependencies.
mavenCentral()
}
dependencies {
// Use JUnit Jupiter for testing.
testImplementation(libs.junit.jupiter)
testImplementation(libs.mockito)
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
implementation(libs.log4j)
implementation(libs.discord4j) {
// exclude(module="discord4j-voice")
}
implementation(libs.geantyref)
implementation(project(":lib"))
}
// Apply a specific Java toolchain to ease working on different environments.
java {
toolchain {
languageVersion = JavaLanguageVersion.of(23)
}
}
tasks.named<Test>("test") {
// Use JUnit Platform for unit tests.
useJUnitPlatform()
}
@@ -0,0 +1,102 @@
package net.tomatentum.marinara.wrapper.discord4j;
import java.util.List;
import discord4j.core.event.domain.interaction.ChatInputAutoCompleteEvent;
import discord4j.core.event.domain.interaction.ChatInputInteractionEvent;
import discord4j.core.event.domain.interaction.ComponentInteractionEvent;
import discord4j.core.object.command.ApplicationCommandInteractionOption;
import net.tomatentum.marinara.interaction.commands.option.SlashCommandOptionType;
import net.tomatentum.marinara.wrapper.ContextObjectProvider;
public class Discord4JContextObjectProvider implements ContextObjectProvider {
@Override
public Object convertCommandOption(Object context, String optionName) {
if (!(context instanceof ChatInputInteractionEvent))
return null;
ChatInputInteractionEvent interactionEvent = (ChatInputInteractionEvent) context;
List<ApplicationCommandInteractionOption> subOptions = Discord4JWrapper.SUB_FILTER.apply(interactionEvent.getOptions());
if (subOptions.isEmpty())
return getOptionValue(interactionEvent.getOption(optionName).get());
ApplicationCommandInteractionOption subCommandOption = interactionEvent.getOptions().getFirst();
subOptions = Discord4JWrapper.SUB_FILTER.apply(subCommandOption.getOptions());
if (!subOptions.isEmpty())
subCommandOption = subOptions.getFirst();
return getOptionValue(interactionEvent.getOption(optionName).get());
}
private Object getOptionValue(ApplicationCommandInteractionOption option) {
if (!option.getValue().isPresent())
return null;
SlashCommandOptionType type = getOptionType(option);
switch (type) {
case ATTACHMENT:
return option.getValue().get().asAttachment();
case BOOLEAN:
return option.getValue().get().asBoolean();
case CHANNEL:
return option.getValue().get().asChannel();
case DOUBLE:
return option.getValue().get().asDouble();
case INTEGER:
return option.getValue().get().asLong();
case MENTIONABLE:
return option.getValue().get().asSnowflake();
case ROLE:
return option.getValue().get().asRole();
case STRING:
return option.getValue().get().asString();
case USER:
return option.getValue().get().asUser();
default:
return null;
}
}
private SlashCommandOptionType getOptionType(ApplicationCommandInteractionOption option) {
return SlashCommandOptionType.fromValue(option.getType().getValue());
}
@Override
public Object getComponentContextObject(Object context, Class<?> type) {
ComponentInteractionEvent componentInteractionEvent = (ComponentInteractionEvent) context;
switch (type.getName()) {
case "discord4j.core.object.entity.Message":
return componentInteractionEvent.getMessage().orElse(null);
default:
return getInteractionContextObject(context, type);
}
}
@Override
public Object getInteractionContextObject(Object context, Class<?> type) {
ComponentInteractionEvent componentInteractionEvent = (ComponentInteractionEvent) context;
switch (type.getName()) {
case "discord4j.core.object.entity.channel.MessageChannel":
return componentInteractionEvent.getInteraction().getChannel().block();
case "discord4j.core.object.entity.Guild":
return componentInteractionEvent.getInteraction().getGuild().block();
case "discord4j.core.object.entity.Member":
return componentInteractionEvent.getInteraction().getMember().orElse(null);
case "discord4j.core.object.entity.User":
return componentInteractionEvent.getInteraction().getUser();
}
return null;
}
@Override
public Object getAutocompleteFocusedOption(Object context) {
ChatInputAutoCompleteEvent interaction = (ChatInputAutoCompleteEvent) context;
return getOptionValue(interaction.getFocusedOption());
}
}
@@ -0,0 +1,210 @@
package net.tomatentum.marinara.wrapper.discord4j;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import org.apache.logging.log4j.Logger;
import discord4j.core.GatewayDiscordClient;
import discord4j.core.event.domain.interaction.ButtonInteractionEvent;
import discord4j.core.event.domain.interaction.ChatInputAutoCompleteEvent;
import discord4j.core.event.domain.interaction.ChatInputInteractionEvent;
import discord4j.core.event.domain.interaction.InteractionCreateEvent;
import discord4j.core.object.command.ApplicationCommandInteractionOption;
import discord4j.core.object.command.ApplicationCommandOption.Type;
import discord4j.discordjson.json.ApplicationCommandOptionChoiceData;
import discord4j.discordjson.json.ApplicationCommandOptionData;
import discord4j.discordjson.json.ApplicationCommandRequest;
import io.leangen.geantyref.AnnotationFormatException;
import io.leangen.geantyref.TypeFactory;
import net.tomatentum.marinara.interaction.InteractionType;
import net.tomatentum.marinara.interaction.commands.ExecutableSlashCommandDefinition;
import net.tomatentum.marinara.interaction.commands.SlashCommandDefinition;
import net.tomatentum.marinara.interaction.commands.annotation.SlashCommand;
import net.tomatentum.marinara.interaction.commands.annotation.SlashCommandOption;
import net.tomatentum.marinara.interaction.commands.annotation.SlashCommandOptionChoice;
import net.tomatentum.marinara.interaction.commands.annotation.SubCommand;
import net.tomatentum.marinara.interaction.commands.annotation.SubCommandGroup;
import net.tomatentum.marinara.util.LoggerUtil;
import net.tomatentum.marinara.wrapper.ContextObjectProvider;
import net.tomatentum.marinara.wrapper.LibraryWrapper;
public class Discord4JWrapper extends LibraryWrapper {
public static final Function<List<ApplicationCommandInteractionOption>, List<ApplicationCommandInteractionOption>> SUB_FILTER = (i) ->
i.stream()
.filter(o -> o.getType().equals(Type.SUB_COMMAND) || o.getType().equals(Type.SUB_COMMAND_GROUP))
.toList();
public static final Function<List<ApplicationCommandInteractionOption>, List<ApplicationCommandInteractionOption>> ARG_FILTER = (i) ->
i.stream()
.filter(o -> !o.getType().equals(Type.SUB_COMMAND) && !o.getType().equals(Type.SUB_COMMAND_GROUP))
.toList();
private GatewayDiscordClient api;
private Discord4JContextObjectProvider contextObjectProvider;
private Logger logger = LoggerUtil.getLogger(getClass());
public Discord4JWrapper(GatewayDiscordClient api) {
this.api = api;
this.contextObjectProvider = new Discord4JContextObjectProvider();
if (api != null)
api.on(InteractionCreateEvent.class)
.subscribe(event -> handleInteraction(event));
logger.info("Discord4J wrapper loaded!");
}
@Override
public InteractionType getInteractionType(Object context) {
if (ChatInputAutoCompleteEvent.class.isAssignableFrom(context.getClass()))
return InteractionType.AUTOCOMPLETE;
if (ChatInputInteractionEvent.class.isAssignableFrom(context.getClass()))
return InteractionType.COMMAND;
if (ButtonInteractionEvent.class.isAssignableFrom(context.getClass()))
return InteractionType.BUTTON;
return null;
}
@Override
public void registerSlashCommands(SlashCommandDefinition[] defs) {
HashMap<Long, List<ApplicationCommandRequest>> serverCommands = new HashMap<>();
List<ApplicationCommandRequest> globalCommands = new ArrayList<>();
long applicationId = api.getRestClient().getApplicationId().block();
for (SlashCommandDefinition slashCommandDefinition : defs) {
ApplicationCommandRequest request = convertSlashCommand(slashCommandDefinition);
if (slashCommandDefinition.getSlashCommand().serverIds().length > 0) {
for (long serverId : slashCommandDefinition.getSlashCommand().serverIds()) {
serverCommands.putIfAbsent(serverId, new ArrayList<>());
serverCommands.get(serverId).add(request);
}
}else
globalCommands.add(request);
}
for (long serverId : serverCommands.keySet()) {
api.getRestClient().getApplicationService().bulkOverwriteGuildApplicationCommand(applicationId, serverId, serverCommands.get(serverId));
}
api.getRestClient().getApplicationService().bulkOverwriteGlobalApplicationCommand(applicationId, globalCommands);
}
@Override
public ExecutableSlashCommandDefinition getCommandDefinition(Object context) {
List<ApplicationCommandInteractionOption> options;
String commandName;
if (context instanceof ChatInputInteractionEvent) {
ChatInputInteractionEvent interaction = (ChatInputInteractionEvent) context;
options = SUB_FILTER.apply(interaction.getOptions());
commandName = interaction.getCommandName();
}else if (context instanceof ChatInputAutoCompleteEvent) {
ChatInputAutoCompleteEvent interaction = (ChatInputAutoCompleteEvent) context;
options = SUB_FILTER.apply(interaction.getOptions());
commandName = interaction.getCommandName();
}else
return null;
ExecutableSlashCommandDefinition.Builder builder = new ExecutableSlashCommandDefinition.Builder();
try {
builder.setApplicationCommand(TypeFactory.annotation(SlashCommand.class, Map.of("name", commandName)));
if (!options.isEmpty()) {
if (!ARG_FILTER.apply(options.getFirst().getOptions()).isEmpty()) {
builder.setSubCommandGroup(TypeFactory.annotation(SubCommandGroup.class, Map.of("name", options.getFirst().getName())));
builder.setSubCommand(TypeFactory.annotation(SubCommand.class, Map.of("name", SUB_FILTER.apply(options.getFirst().getOptions()).getFirst().getName())));
}else
builder.setSubCommand(TypeFactory.annotation(SubCommand.class, Map.of("name", options.getFirst().getName())));
}
} catch (AnnotationFormatException e) {
logger.fatal(e);
}
return builder.build();
}
private ApplicationCommandRequest convertSlashCommand(SlashCommandDefinition def) {
List<ApplicationCommandOptionData> options = new ArrayList<>();
SlashCommand cmd = def.getSlashCommand();
if (!def.isRootCommand()) {
Arrays.stream(def.getSubCommands(null)).map(this::convertSubCommandDef).forEach(options::add);
Arrays.stream(def.getSubCommandGroups()).map((x) -> convertSubCommandGroupDef(def, x)).forEach(options::add);
}else {
Arrays.stream(cmd.options()).map(this::convertOptionDef).forEach(options::add);
}
return ApplicationCommandRequest.builder()
.name(cmd.name())
.description(cmd.description())
.options(options)
.build();
}
private ApplicationCommandOptionData convertSubCommandGroupDef(SlashCommandDefinition def, SubCommandGroup subGroup) {
SubCommand[] subCommands = def.getSubCommands(subGroup.name());
List<ApplicationCommandOptionData> convertedSubCommands = Arrays.stream(subCommands).map(this::convertSubCommandDef).toList();
return ApplicationCommandOptionData.builder()
.type(Type.SUB_COMMAND_GROUP.getValue())
.name(subGroup.name())
.description(subGroup.description())
.options(convertedSubCommands)
.build();
}
private ApplicationCommandOptionData convertSubCommandDef(SubCommand sub) {
List<ApplicationCommandOptionData> convertedOptions = Arrays.stream(sub.options()).map(this::convertOptionDef).toList();
return ApplicationCommandOptionData.builder()
.type(Type.SUB_COMMAND_GROUP.getValue())
.name(sub.name())
.description(sub.description())
.options(convertedOptions)
.build();
}
private ApplicationCommandOptionData convertOptionDef(SlashCommandOption option) {
Type type = Enum.valueOf(Type.class, option.type().toString());
return ApplicationCommandOptionData.builder()
.type(type.getValue())
.name(option.name())
.description(option.description())
.required(option.required())
.autocomplete(option.autocomplete())
.choices(convertChoices(option))
.build();
}
private List<ApplicationCommandOptionChoiceData> convertChoices(SlashCommandOption option) {
List<ApplicationCommandOptionChoiceData> convertedChoices = new ArrayList<>();
for (SlashCommandOptionChoice choice : ExecutableSlashCommandDefinition.getActualChoices(option)) {
var builder = ApplicationCommandOptionChoiceData.builder();
builder.name(choice.name());
if (choice.longValue() != Long.MAX_VALUE)
builder.value(choice.longValue());
if (choice.doubleValue() != Double.MAX_VALUE)
builder.value(choice.doubleValue());
if (!choice.stringValue().isEmpty())
builder.value(choice.stringValue());
convertedChoices.add(builder.build());
}
return convertedChoices;
}
@Override
public String getButtonId(Object context) {
ButtonInteractionEvent button = (ButtonInteractionEvent) context;
return button.getCustomId();
}
@Override
public ContextObjectProvider getContextObjectProvider() {
return this.contextObjectProvider;
}
}
@@ -0,0 +1,42 @@
package net.tomatentum.marinara.wrapper.discord4j.checks;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Optional;
import discord4j.core.event.domain.interaction.InteractionCreateEvent;
import discord4j.core.object.entity.Member;
import discord4j.rest.util.Permission;
import discord4j.rest.util.PermissionSet;
import net.tomatentum.marinara.checks.InteractionCheck;
public class PermissionCheck implements InteractionCheck<PermissionCheck.HasPermission> {
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public static @interface HasPermission {
public Permission[] value();
}
@Override
public boolean preExec(Object context, HasPermission annotation) {
throw new UnsupportedOperationException("Unimplemented method 'preExec'");
}
public boolean preExec(InteractionCreateEvent context, HasPermission annotation) {
Optional<Member> member = context.getInteraction().getMember();
if (member.isEmpty())
return false;
PermissionSet ownPerms = PermissionSet.of(annotation.value());
PermissionSet permSet = member.get().getBasePermissions().block();
return permSet.containsAll(ownPerms);
}
@Override
public void postExec(Object context, HasPermission annotation) {
}
}
@@ -0,0 +1,45 @@
package net.tomatentum.marinara.test.discord4j;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.ArrayList;
import java.util.Optional;
import org.junit.jupiter.api.Test;
import discord4j.core.event.domain.interaction.ChatInputAutoCompleteEvent;
import discord4j.core.object.command.ApplicationCommandInteractionOption;
import discord4j.core.object.command.ApplicationCommandInteractionOptionValue;
import discord4j.core.object.command.ApplicationCommandOption.Type;
import net.tomatentum.marinara.Marinara;
import net.tomatentum.marinara.wrapper.LibraryWrapper;
import net.tomatentum.marinara.wrapper.discord4j.Discord4JWrapper;
public class AutoCompleteTest {
@Test
public void testAutocomplete() {
ApplicationCommandInteractionOption optionMock = mock();
ChatInputAutoCompleteEvent autoCompleteEventMock = mock();
when(optionMock.getName()).thenReturn("foo");
when(optionMock.getType()).thenReturn(Type.STRING);
when(optionMock.getValue()).thenReturn(
Optional.of(
new ApplicationCommandInteractionOptionValue(null, null, Type.STRING.getValue(), "test", null)
));
when(autoCompleteEventMock.getCommandName()).thenReturn("test");
when(autoCompleteEventMock.getOptions()).thenReturn(new ArrayList<>());
when(autoCompleteEventMock.getFocusedOption()).thenReturn(optionMock);
LibraryWrapper wrapper = new Discord4JWrapper(null); //null okay as we don't use the discord API in this test.
Marinara marinara = Marinara.load(wrapper);
marinara.getRegistry().addInteractions(new TestAutocomplete());
wrapper.handleInteraction(autoCompleteEventMock);
verify(autoCompleteEventMock).respondWithSuggestions(any());
}
}
@@ -0,0 +1,29 @@
package net.tomatentum.marinara.test.discord4j;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestInstance.Lifecycle;
import discord4j.core.event.domain.interaction.ButtonInteractionEvent;
import net.tomatentum.marinara.Marinara;
import net.tomatentum.marinara.test.discord4j.mocks.CommonMocks;
import net.tomatentum.marinara.wrapper.LibraryWrapper;
import net.tomatentum.marinara.wrapper.discord4j.Discord4JWrapper;
@TestInstance(Lifecycle.PER_CLASS)
public class ButtonTest {
@Test
public void testButtonExecution() {
ButtonInteractionEvent buttonEventMock = CommonMocks.getButtonEventMock("test");
LibraryWrapper wrapper = new Discord4JWrapper(null); //null okay as we don't use the discord API in this test.
Marinara marinara = Marinara.load(wrapper);
marinara.getRegistry().addInteractions(new TestButton());
wrapper.handleInteraction(buttonEventMock);
assertTrue(TestButton.didRun);
}
}
@@ -0,0 +1,70 @@
package net.tomatentum.marinara.test.discord4j;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.Optional;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestInstance.Lifecycle;
import discord4j.core.event.domain.interaction.ButtonInteractionEvent;
import discord4j.core.object.command.Interaction;
import discord4j.core.object.entity.Member;
import discord4j.rest.util.Permission;
import discord4j.rest.util.PermissionSet;
import net.tomatentum.marinara.Marinara;
import net.tomatentum.marinara.test.discord4j.mocks.CommonMocks;
import net.tomatentum.marinara.wrapper.LibraryWrapper;
import net.tomatentum.marinara.wrapper.discord4j.Discord4JWrapper;
import net.tomatentum.marinara.wrapper.discord4j.checks.PermissionCheck;
import reactor.core.publisher.Mono;
@TestInstance(Lifecycle.PER_CLASS)
public class InteractionCheckTest {
@Test
public void testInteractionCheck() {
ButtonInteractionEvent buttonEventMock = CommonMocks.getButtonEventMock("test");
LibraryWrapper wrapper = new Discord4JWrapper(null);
Marinara marinara = Marinara.load(wrapper);
marinara.getCheckRegistry().addCheck(new TestInteractionCheck());
marinara.getRegistry().addInteractions(new TestButton());
wrapper.handleInteraction(buttonEventMock);
assertTrue(TestInteractionCheck.preExecuted);
assertTrue(TestInteractionCheck.postExecuted);
assertTrue(TestButton.didRun);
}
@Test
public void testPermissionCheck() {
Member memberMock = mock();
Interaction interactionMock = mock();
when(memberMock.getBasePermissions()).thenReturn(Mono.just(PermissionSet.none()));
when(interactionMock.getMember()).thenReturn(Optional.of(memberMock));
ButtonInteractionEvent buttonEventMock = CommonMocks.getButtonEventMock("permissionCheck", interactionMock);
LibraryWrapper wrapper = new Discord4JWrapper(null);
Marinara marinara = Marinara.load(wrapper);
marinara.getCheckRegistry().addCheck(new PermissionCheck());
marinara.getRegistry().addInteractions(new TestButton());
wrapper.handleInteraction(buttonEventMock);
assertFalse(TestButton.didPermRun);
TestButton.didPermRun = false;
when(memberMock.getBasePermissions()).thenReturn(Mono.just(PermissionSet.of(Permission.ATTACH_FILES)));
wrapper.handleInteraction(buttonEventMock);
assertTrue(TestButton.didPermRun);
}
}
@@ -0,0 +1,74 @@
package net.tomatentum.marinara.test.discord4j;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.Arrays;
import java.util.Optional;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestInstance.Lifecycle;
import discord4j.core.DiscordClient;
import discord4j.core.GatewayDiscordClient;
import discord4j.core.event.domain.interaction.ChatInputInteractionEvent;
import discord4j.core.object.command.ApplicationCommandInteractionOption;
import discord4j.core.object.command.ApplicationCommandInteractionOptionValue;
import discord4j.core.object.command.ApplicationCommandOption.Type;
import net.tomatentum.marinara.Marinara;
import net.tomatentum.marinara.wrapper.LibraryWrapper;
import net.tomatentum.marinara.wrapper.discord4j.Discord4JWrapper;
@TestInstance(Lifecycle.PER_CLASS)
public class SlashCommandTest {
String DISCORD_TOKEN = System.getenv("DISCORD_TEST_TOKEN");
GatewayDiscordClient client;
@BeforeAll
void setUp() {
client = DiscordClient.create(DISCORD_TOKEN).login().block();
}
@AfterAll
void tearDown() {
client.logout().block();
client = null;
}
@Test
void testSlashCommand() {
Marinara marinara = Marinara.load(new Discord4JWrapper(client));
marinara.getRegistry().addInteractions(new TestCommand());
marinara.getRegistry().registerCommands();
System.out.println("Success!");
}
@Test
void testSlashCommandExecution() {
ApplicationCommandInteractionOption optionMock = mock();
ChatInputInteractionEvent eventMock = mock();
when(optionMock.getName()).thenReturn("foo");
when(optionMock.getType()).thenReturn(Type.STRING);
when(optionMock.getValue()).thenReturn(
Optional.of(
new ApplicationCommandInteractionOptionValue(null, null, Type.STRING.getValue(), "test", null)
));
when(eventMock.getCommandName()).thenReturn("test");
when(eventMock.getOptions()).thenReturn(Arrays.asList(optionMock));
when(eventMock.getOption("foo")).thenReturn(Optional.of(optionMock));
LibraryWrapper wrapper = new Discord4JWrapper(client);
Marinara marinara = Marinara.load(wrapper);
marinara.getRegistry().addInteractions(new TestCommand());
wrapper.handleInteraction(eventMock);
}
}
@@ -0,0 +1,22 @@
package net.tomatentum.marinara.test.discord4j;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.Collections;
import discord4j.core.event.domain.interaction.ChatInputAutoCompleteEvent;
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(ChatInputAutoCompleteEvent context, String value) {
System.out.println("Success!");
assertEquals(value, "test");
context.respondWithSuggestions(Collections.emptyList());
}
}
@@ -0,0 +1,43 @@
package net.tomatentum.marinara.test.discord4j;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import discord4j.core.event.domain.interaction.ButtonInteractionEvent;
import discord4j.core.object.entity.Guild;
import discord4j.core.object.entity.Member;
import discord4j.core.object.entity.Message;
import discord4j.core.object.entity.User;
import discord4j.core.object.entity.channel.MessageChannel;
import discord4j.rest.util.Permission;
import net.tomatentum.marinara.interaction.InteractionHandler;
import net.tomatentum.marinara.interaction.annotation.Button;
import net.tomatentum.marinara.test.discord4j.TestInteractionCheck.TestCheck;
import net.tomatentum.marinara.wrapper.discord4j.checks.PermissionCheck.HasPermission;
public class TestButton implements InteractionHandler {
public static boolean didRun = false;
@Button("test")
@TestCheck
public void exec(ButtonInteractionEvent interaction, MessageChannel channel, Message message, Member member, User user, Guild server) {
assertNotNull(interaction);
assertNotNull(channel);
assertNotNull(message);
assertNotNull(member);
assertNotNull(user);
assertNotNull(server);
didRun = true;
System.out.println("Success!");
}
public static boolean didPermRun = false;
@Button("permissionCheck")
@HasPermission({Permission.ATTACH_FILES})
public void exec(ButtonInteractionEvent interaction) {
didPermRun = true;
System.out.println("It worked!");
}
}
@@ -0,0 +1,20 @@
package net.tomatentum.marinara.test.discord4j;
import net.tomatentum.marinara.interaction.commands.choice.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;
}
}
@@ -0,0 +1,31 @@
package net.tomatentum.marinara.test.discord4j;
import static org.junit.jupiter.api.Assertions.assertEquals;
import discord4j.core.event.domain.interaction.ChatInputInteractionEvent;
import net.tomatentum.marinara.interaction.InteractionHandler;
import net.tomatentum.marinara.interaction.commands.annotation.SlashCommand;
import net.tomatentum.marinara.interaction.commands.annotation.SlashCommandOption;
import net.tomatentum.marinara.interaction.commands.option.SlashCommandOptionType;
public class TestCommand implements InteractionHandler {
@SlashCommand(
name = "test",
description = "testingen",
serverIds = {
1037753048602255440L
},
options = {
@SlashCommandOption(
name = "foo",
description = "foo bar is very fooby",
type = SlashCommandOptionType.STRING,
choiceEnum = TestChoiceEnum.class
)
}
)
public void exec(ChatInputInteractionEvent event, String test) {
assertEquals(test, "test");
System.out.println("Success!");
}
}
@@ -0,0 +1,37 @@
package net.tomatentum.marinara.test.discord4j;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import net.tomatentum.marinara.checks.InteractionCheck;
public class TestInteractionCheck implements InteractionCheck<TestInteractionCheck.TestCheck> {
public static boolean preExecuted = false;
public static boolean postExecuted = false;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public static @interface TestCheck {
}
@Override
public boolean preExec(Object context, TestCheck annotation) {
assertNotNull(annotation);
assertNotNull(context);
preExecuted = true;
return true;
}
@Override
public void postExec(Object context, TestCheck annotation) {
assertNotNull(annotation);
assertNotNull(context);
postExecuted = true;
}
}
@@ -0,0 +1,55 @@
package net.tomatentum.marinara.test.discord4j.mocks;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.Optional;
import discord4j.core.event.domain.interaction.ButtonInteractionEvent;
import discord4j.core.object.command.Interaction;
import discord4j.core.object.entity.Guild;
import discord4j.core.object.entity.Member;
import discord4j.core.object.entity.Message;
import discord4j.core.object.entity.User;
import discord4j.core.object.entity.channel.MessageChannel;
import reactor.core.publisher.Mono;
public class CommonMocks {
public static Interaction getInteractionMock() {
Interaction interaction = mock(Interaction.class);
Message message = mock(Message.class);
MessageChannel channel = mock(MessageChannel.class);
Guild guild = mock(Guild.class);
User user = mock(User.class);
Member member = mock(Member.class);
when(interaction.getMessage()).thenReturn(Optional.of(message));
when(interaction.getChannel()).thenReturn(Mono.just(channel));
when(interaction.getGuild()).thenReturn(Mono.just(guild));
when(interaction.getUser()).thenReturn(user);
when(interaction.getMember()).thenReturn(Optional.of(member));
return interaction;
}
public static ButtonInteractionEvent getButtonEventMock(String customId) {
ButtonInteractionEvent buttonEventMock = mock(ButtonInteractionEvent.class);
when(buttonEventMock.getCustomId()).thenReturn(customId);
Interaction interactionMock = getInteractionMock();
when(buttonEventMock.getInteraction()).thenReturn(interactionMock);
Optional<Message> message = interactionMock.getMessage();
when (buttonEventMock.getMessage()).thenReturn(message);
return buttonEventMock;
}
public static ButtonInteractionEvent getButtonEventMock(String customId, Interaction interaction) {
ButtonInteractionEvent buttonEventMock = mock(ButtonInteractionEvent.class);
when(buttonEventMock.getCustomId()).thenReturn(customId);
when(buttonEventMock.getInteraction()).thenReturn(interaction);
return buttonEventMock;
}
}
+1 -1
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)
}
}
@@ -0,0 +1,110 @@
package net.tomatentum.marinara.wrapper.javacord;
import org.javacord.api.interaction.AutocompleteInteraction;
import org.javacord.api.interaction.ButtonInteraction;
import org.javacord.api.interaction.SlashCommandInteraction;
import org.javacord.api.interaction.SlashCommandInteractionOption;
import org.javacord.api.interaction.SlashCommandOptionType;
import net.tomatentum.marinara.wrapper.ContextObjectProvider;
public class JavacordContextObjectProvider implements ContextObjectProvider {
@Override
public Object convertCommandOption(Object context, String optionName) {
if (!(context instanceof SlashCommandInteraction))
return null;
SlashCommandInteraction interaction = (SlashCommandInteraction) context;
if (!interaction.getArguments().isEmpty())
return getOptionValue(interaction.getOptionByName(optionName).get());
SlashCommandInteractionOption subCommandOption = interaction.getOptions().getFirst();
if (!subCommandOption.getOptions().isEmpty())
subCommandOption = subCommandOption.getOptions().getFirst();
return getOptionValue(subCommandOption.getOptionByName(optionName).get());
}
private Object getOptionValue(SlashCommandInteractionOption option) {
SlashCommandOptionType type = getOptionType(option);
switch (type) {
case ATTACHMENT:
return option.getAttachmentValue().get();
case BOOLEAN:
return option.getBooleanValue().get();
case CHANNEL:
return option.getChannelValue().get();
case DECIMAL:
return option.getDecimalValue().get();
case LONG:
return option.getLongValue().get();
case MENTIONABLE:
return option.requestMentionableValue().get();
case ROLE:
return option.getRoleValue().get();
case STRING:
return option.getStringValue().get();
case USER:
return option.requestUserValue().get();
default:
return null;
}
}
private SlashCommandOptionType getOptionType(SlashCommandInteractionOption option) {
if (option.getAttachmentValue().isPresent())
return SlashCommandOptionType.ATTACHMENT;
if (option.getBooleanValue().isPresent())
return SlashCommandOptionType.BOOLEAN;
if (option.getChannelValue().isPresent())
return SlashCommandOptionType.CHANNEL;
if (option.getDecimalValue().isPresent())
return SlashCommandOptionType.DECIMAL;
if (option.getLongValue().isPresent())
return SlashCommandOptionType.LONG;
if (option.requestMentionableValue().isPresent())
return SlashCommandOptionType.MENTIONABLE;
if (option.getRoleValue().isPresent())
return SlashCommandOptionType.ROLE;
if (option.getStringValue().isPresent())
return SlashCommandOptionType.STRING;
if (option.requestUserValue().isPresent())
return SlashCommandOptionType.USER;
return SlashCommandOptionType.UNKNOWN;
}
@Override
public Object getComponentContextObject(Object context, Class<?> type) {
ButtonInteraction button = (ButtonInteraction) context;
switch (type.getName()) {
case "org.javacord.api.entity.message.Message":
return button.getMessage();
default:
return getInteractionContextObject(context, type);
}
}
@Override
public Object getInteractionContextObject(Object context, Class<?> type) {
ButtonInteraction button = (ButtonInteraction) context;
switch (type.getName()) {
case "org.javacord.api.entity.channel.TextChannel":
return button.getChannel().orElse(null);
case "org.javacord.api.entity.server.Server":
return button.getServer().orElse(null);
case "org.javacord.api.entity.user.User":
return button.getUser();
}
return null;
}
@Override
public Object getAutocompleteFocusedOption(Object context) {
AutocompleteInteraction interaction = (AutocompleteInteraction) context;
return getOptionValue(interaction.getFocusedOption());
}
}
@@ -8,12 +8,17 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.logging.log4j.Logger;
import org.javacord.api.DiscordApi;
import org.javacord.api.interaction.ApplicationCommandInteraction;
import org.javacord.api.interaction.AutocompleteInteraction;
import org.javacord.api.interaction.ButtonInteraction;
import org.javacord.api.interaction.SlashCommandBuilder;
import org.javacord.api.interaction.SlashCommandInteraction;
import org.javacord.api.interaction.SlashCommandInteractionOption;
import org.javacord.api.interaction.SlashCommandOptionBuilder;
import org.javacord.api.interaction.SlashCommandOptionChoiceBuilder;
import org.javacord.api.interaction.SlashCommandOptionType;
import io.leangen.geantyref.AnnotationFormatException;
import io.leangen.geantyref.TypeFactory;
@@ -22,27 +27,35 @@ 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.util.LoggerUtil;
import net.tomatentum.marinara.wrapper.LibraryWrapper;
public class JavacordWrapper extends LibraryWrapper {
private DiscordApi api;
private JavacordContextObjectProvider contextObjectProvider;
private Logger logger = LoggerUtil.getLogger(getClass());
public JavacordWrapper(DiscordApi api) {
this.api = api;
this.contextObjectProvider = new JavacordContextObjectProvider();
api.addInteractionCreateListener((e) -> handleInteraction(e.getInteraction()));
logger.info("Javacord wrapper loaded!");
}
@Override
public InteractionType getInteractionType(Class<?> clazz) {
if (ApplicationCommandInteraction.class.isAssignableFrom(clazz))
public InteractionType getInteractionType(Object context) {
if (AutocompleteInteraction.class.isAssignableFrom(context.getClass()))
return InteractionType.AUTOCOMPLETE;
if (ApplicationCommandInteraction.class.isAssignableFrom(context.getClass()))
return InteractionType.COMMAND;
if (ButtonInteraction.class.isAssignableFrom(clazz))
if (ButtonInteraction.class.isAssignableFrom(context.getClass()))
return InteractionType.BUTTON;
return null;
}
@@ -52,8 +65,8 @@ public class JavacordWrapper extends LibraryWrapper {
Set<SlashCommandBuilder> globalCommands = new HashSet<>();
for (SlashCommandDefinition slashCommandDefinition : defs) {
SlashCommandBuilder builder = convertSlashCommand(slashCommandDefinition);
if (slashCommandDefinition.getFullSlashCommand().serverIds().length > 0) {
for (long serverId : slashCommandDefinition.getFullSlashCommand().serverIds()) {
if (slashCommandDefinition.getSlashCommand().serverIds().length > 0) {
for (long serverId : slashCommandDefinition.getSlashCommand().serverIds()) {
serverCommands.putIfAbsent(serverId, new HashSet<>());
serverCommands.get(serverId).add(builder);
}
@@ -67,22 +80,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,13 +90,15 @@ public class JavacordWrapper extends LibraryWrapper {
List<SlashCommandInteractionOption> options = interaction.getOptions();
try {
builder.setApplicationCommand(TypeFactory.annotation(SlashCommand.class, Map.of("name", interaction.getCommandName())));
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())));
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();
logger.fatal(e);
}
return builder.build();
@@ -107,7 +106,7 @@ public class JavacordWrapper extends LibraryWrapper {
private SlashCommandBuilder convertSlashCommand(SlashCommandDefinition def) {
List<org.javacord.api.interaction.SlashCommandOption> options = new ArrayList<>();
SlashCommand cmd = def.getFullSlashCommand();
SlashCommand cmd = def.getSlashCommand();
if (!def.isRootCommand()) {
Arrays.stream(def.getSubCommands(null)).map(this::convertSubCommandDef).forEach(options::add);
Arrays.stream(def.getSubCommandGroups()).map((x) -> convertSubCommandGroupDef(def, x)).forEach(options::add);
@@ -120,44 +119,54 @@ 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));
List<org.javacord.api.interaction.SlashCommandOption> convertedSubCommands = Arrays.stream(subCommands).map(this::convertSubCommandDef).toList();
return org.javacord.api.interaction.SlashCommandOption.createWithOptions(
org.javacord.api.interaction.SlashCommandOptionType.SUB_COMMAND_GROUP,
subGroup.name(),
subGroup.description(),
convertedSubCommands);
}
private org.javacord.api.interaction.SlashCommandOption convertSubCommandDef(SubCommand sub) {
List<org.javacord.api.interaction.SlashCommandOption> convertedOptions = 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);
List<org.javacord.api.interaction.SlashCommandOption> convertedOptions = Arrays.stream(sub.options()).map(this::convertOptionDef).toList();
return org.javacord.api.interaction.SlashCommandOption.createWithOptions(
org.javacord.api.interaction.SlashCommandOptionType.SUB_COMMAND,
sub.name(),
sub.description(),
convertedOptions);
}
private org.javacord.api.interaction.SlashCommandOption convertOptionDef(SlashCommandOption option) {
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());
convertedChoices.add(builder.build());
}
return convertedChoices;
}
@Override
@@ -167,20 +176,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;
}
}
@@ -0,0 +1,41 @@
package net.tomatentum.marinara.wrapper.javacord.checks;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Optional;
import org.javacord.api.entity.permission.PermissionType;
import org.javacord.api.entity.server.Server;
import org.javacord.api.interaction.InteractionBase;
import net.tomatentum.marinara.checks.InteractionCheck;
public class PermissionCheck implements InteractionCheck<PermissionCheck.HasPermission> {
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public static @interface HasPermission {
public PermissionType[] value();
}
@Override
public boolean preExec(Object context, HasPermission annotation) {
throw new UnsupportedOperationException("Unimplemented method 'preExec'");
}
public boolean preExec(InteractionBase context, HasPermission annotation) {
Optional<Server> server = context.getServer();
if (!server.isPresent())
return false;
return server.get().hasPermissions(context.getUser(), annotation.value());
}
@Override
public void postExec(Object context, HasPermission annotation) {
}
}
@@ -0,0 +1,23 @@
package net.tomatentum.marinara.test.javacord;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Test;
import net.tomatentum.marinara.Marinara;
import net.tomatentum.marinara.test.javacord.mocks.AutocompleteInteractionMock;
import net.tomatentum.marinara.test.javacord.mocks.DiscordApiMock;
import net.tomatentum.marinara.wrapper.LibraryWrapper;
import net.tomatentum.marinara.wrapper.javacord.JavacordWrapper;
public class AutoCompleteTest {
@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);
}
}
@@ -1,4 +1,4 @@
package net.tomatentum.marinara.test;
package net.tomatentum.marinara.test.javacord;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -7,8 +7,8 @@ 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.javacord.mocks.ButtonInteractionMock;
import net.tomatentum.marinara.test.javacord.mocks.DiscordApiMock;
import net.tomatentum.marinara.wrapper.LibraryWrapper;
import net.tomatentum.marinara.wrapper.javacord.JavacordWrapper;
@@ -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.javacord;
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.javacord.mocks.ButtonInteractionMock;
import net.tomatentum.marinara.test.javacord.mocks.DiscordApiMock;
import net.tomatentum.marinara.test.javacord.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);
}
}
@@ -1,4 +1,4 @@
package net.tomatentum.marinara.test;
package net.tomatentum.marinara.test.javacord;
import org.javacord.api.DiscordApi;
import org.javacord.api.DiscordApiBuilder;
@@ -9,7 +9,7 @@ import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestInstance.Lifecycle;
import net.tomatentum.marinara.Marinara;
import net.tomatentum.marinara.test.mocks.SlashCommandInteractionMock;
import net.tomatentum.marinara.test.javacord.mocks.SlashCommandInteractionMock;
import net.tomatentum.marinara.wrapper.LibraryWrapper;
import net.tomatentum.marinara.wrapper.javacord.JavacordWrapper;
@TestInstance(Lifecycle.PER_CLASS)
@@ -0,0 +1,23 @@
package net.tomatentum.marinara.test.javacord;
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());
}
}
@@ -1,21 +1,25 @@
package net.tomatentum.marinara.test;
package net.tomatentum.marinara.test.javacord;
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.javacord.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,20 @@
package net.tomatentum.marinara.test.javacord;
import net.tomatentum.marinara.interaction.commands.choice.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;
}
}
@@ -1,4 +1,4 @@
package net.tomatentum.marinara.test;
package net.tomatentum.marinara.test.javacord;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -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
)
}
)
@@ -0,0 +1,37 @@
package net.tomatentum.marinara.test.javacord;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import net.tomatentum.marinara.checks.InteractionCheck;
public class TestInteractionCheck implements InteractionCheck<TestInteractionCheck.TestCheck> {
public static boolean preExecuted = false;
public static boolean postExecuted = false;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public static @interface TestCheck {
}
@Override
public boolean preExec(Object context, TestCheck annotation) {
assertNotNull(annotation);
assertNotNull(context);
preExecuted = true;
return true;
}
@Override
public void postExec(Object context, TestCheck annotation) {
assertNotNull(annotation);
assertNotNull(context);
postExecuted = true;
}
}
@@ -0,0 +1,179 @@
package net.tomatentum.marinara.test.javacord.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();
}
}
@@ -1,4 +1,4 @@
package net.tomatentum.marinara.test.mocks;
package net.tomatentum.marinara.test.javacord.mocks;
import java.util.EnumSet;
import java.util.List;
@@ -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,4 +1,4 @@
package net.tomatentum.marinara.test.mocks;
package net.tomatentum.marinara.test.javacord.mocks;
import java.util.Collection;
import java.util.List;
@@ -1,4 +1,4 @@
package net.tomatentum.marinara.test.mocks;
package net.tomatentum.marinara.test.javacord.mocks;
import java.time.Duration;
import java.util.Collection;
@@ -1,4 +1,4 @@
package net.tomatentum.marinara.test.mocks;
package net.tomatentum.marinara.test.javacord.mocks;
import java.time.Instant;
import java.util.Collection;
@@ -1,4 +1,6 @@
package net.tomatentum.marinara.test.mocks;
package net.tomatentum.marinara.test.javacord.mocks;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import java.time.Instant;
import java.util.Collection;
@@ -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]);
}
}
@@ -1,4 +1,4 @@
package net.tomatentum.marinara.test.mocks;
package net.tomatentum.marinara.test.javacord.mocks;
import java.util.Arrays;
import java.util.EnumSet;
@@ -1,4 +1,4 @@
package net.tomatentum.marinara.test.mocks;
package net.tomatentum.marinara.test.javacord.mocks;
import java.util.Collections;
import java.util.List;
@@ -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
@@ -1,4 +1,4 @@
package net.tomatentum.marinara.test.mocks;
package net.tomatentum.marinara.test.javacord.mocks;
import java.awt.Color;
import java.time.Instant;