From de3da2e5c175b74980f78ae1d9349593d425775f Mon Sep 17 00:00:00 2001 From: Kaktushose <42280757+Kaktushose@users.noreply.github.com> Date: Wed, 24 Apr 2024 17:54:26 +0200 Subject: [PATCH 01/38] remove obsolete methods in PermissionsProvider --- pom.xml | 11 ++++++++ .../permissions/PermissionsProvider.java | 26 ------------------- 2 files changed, 11 insertions(+), 26 deletions(-) diff --git a/pom.xml b/pom.xml index a3f933bec..ffa45b1b0 100644 --- a/pom.xml +++ b/pom.xml @@ -106,5 +106,16 @@ annotations 24.0.1 + + + ch.qos.logback + logback-classic + 1.4.12 + + + ch.qos.logback + logback-core + 1.4.12 + diff --git a/src/main/java/com/github/kaktushose/jda/commands/permissions/PermissionsProvider.java b/src/main/java/com/github/kaktushose/jda/commands/permissions/PermissionsProvider.java index 5dfd8d9c3..54cd209b1 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/permissions/PermissionsProvider.java +++ b/src/main/java/com/github/kaktushose/jda/commands/permissions/PermissionsProvider.java @@ -2,14 +2,10 @@ import com.github.kaktushose.jda.commands.annotations.interactions.Permissions; import com.github.kaktushose.jda.commands.dispatching.interactions.Context; -import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.User; import org.jetbrains.annotations.NotNull; -import java.util.ArrayList; -import java.util.List; - /** * Interface for performing permission checks. * @@ -40,26 +36,4 @@ public interface PermissionsProvider { */ boolean hasPermission(@NotNull Member member, @NotNull Context context); - /** - * Gets a {@link List} of user ids that map to the given permission string. - * - * @param guild the corresponding guild - * @param permission the corresponding string - * @return a {@link List} of user ids - */ - default List getUsersWithPermission(Guild guild, String permission) { - return new ArrayList<>(); - } - - /** - * Gets a {@link List} of role ids that map to the given permission string. - * - * @param guild the corresponding guild - * @param permission the corresponding string - * @return a {@link List} of role ids - */ - default List getRolesWithPermission(Guild guild, String permission) { - return new ArrayList<>(); - } - } From da36e41af0bacdc2cddfbd9aca78c30b5e9d0df5 Mon Sep 17 00:00:00 2001 From: Kaktushose <42280757+Kaktushose@users.noreply.github.com> Date: Mon, 29 Apr 2024 14:10:02 +0200 Subject: [PATCH 02/38] fix modal defaultValue not being set --- .../kaktushose/jda/commands/reflect/TextInputDefinition.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/github/kaktushose/jda/commands/reflect/TextInputDefinition.java b/src/main/java/com/github/kaktushose/jda/commands/reflect/TextInputDefinition.java index 66471b880..386024e7f 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/reflect/TextInputDefinition.java +++ b/src/main/java/com/github/kaktushose/jda/commands/reflect/TextInputDefinition.java @@ -88,7 +88,7 @@ public net.dv8tion.jda.api.interactions.components.text.TextInput toTextInput() if (!placeholder.isBlank()) { textInput.setPlaceholder(placeholder); } - if (!placeholder.isBlank()) { + if (!defaultValue.isBlank()) { textInput.setValue(defaultValue); } From 649440b3e890ce30e79be5795ec577c524e08929 Mon Sep 17 00:00:00 2001 From: Kaktushose <42280757+Kaktushose@users.noreply.github.com> Date: Fri, 10 May 2024 13:39:54 +0200 Subject: [PATCH 03/38] add option to link runtimeId for message builder components --- .../kaktushose/jda/commands/JDACommands.java | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/src/main/java/com/github/kaktushose/jda/commands/JDACommands.java b/src/main/java/com/github/kaktushose/jda/commands/JDACommands.java index f973e600d..09f9a3860 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/JDACommands.java +++ b/src/main/java/com/github/kaktushose/jda/commands/JDACommands.java @@ -230,6 +230,36 @@ public Button getButton(String button) { return buttonDefinition.toButton().withId(buttonDefinition.createCustomId(runtime.getRuntimeId())); } + /** + * Gets a JDA {@link Button} to use it for message builders based on the jda-commands id and links it an + * existing + * {@link com.github.kaktushose.jda.commands.dispatching.RuntimeSupervisor.InteractionRuntime InteractionRuntime}. + * + * + *

+ * The id is made up of the simple class name and the method name. E.g. the id of a button defined by a + * {@code onButton(ComponentEvent event)} method inside an {@code ExampleButton} class would be + * {@code ExampleButton.onButton}. + *

+ * + * @param button the id of the button + * @param runtimeId the id of the + * {@link com.github.kaktushose.jda.commands.dispatching.RuntimeSupervisor.InteractionRuntime InteractionRuntime} + * @return a JDA {@link Button} + */ + public Button getButton(String button, String runtimeId) { + if (!button.matches("[a-zA-Z]+\\.[a-zA-Z]+")) { + throw new IllegalArgumentException("Unknown Button"); + } + + String sanitizedId = button.replaceAll("\\.", ""); + ButtonDefinition buttonDefinition = interactionRegistry.getButtons().stream() + .filter(it -> it.getDefinitionId().equals(sanitizedId)) + .findFirst().orElseThrow(() -> new IllegalArgumentException("Unknown Button")); + + return buttonDefinition.toButton().withId(buttonDefinition.createCustomId(runtimeId)); + } + /** * Gets a JDA {@link SelectMenu} to use it for message builders based on the jda-commands id. * @@ -256,6 +286,35 @@ public SelectMenu getSelectMenu(String selectMenu) { return selectMenuDefinition.toSelectMenu(runtime.getRuntimeId(), true); } + /** + * Gets a JDA {@link SelectMenu} to use it for message builders based on the jda-commands id and links it an + * existing + * {@link com.github.kaktushose.jda.commands.dispatching.RuntimeSupervisor.InteractionRuntime InteractionRuntime}. + * + *

+ * The id is made up of the simple class name and the method name. E.g. the id of a a select menu defined by a + * {@code onSelectMenu(ComponentEvent event)} method inside an {@code ExampleMenu} class would be + * {@code ExampleMenu.onSelectMenu}. + *

+ * + * @param selectMenu the id of the selectMenu + * @param runtimeId the id of the + * {@link com.github.kaktushose.jda.commands.dispatching.RuntimeSupervisor.InteractionRuntime InteractionRuntime} + * @return a JDA {@link SelectMenu} + */ + public SelectMenu getSelectMenu(String selectMenu, String runtimeId) { + if (!selectMenu.matches("[a-zA-Z]+\\.[a-zA-Z]+")) { + throw new IllegalArgumentException("Unknown Select Menu"); + } + + String sanitizedId = selectMenu.replaceAll("\\.", ""); + GenericSelectMenuDefinition selectMenuDefinition = interactionRegistry.getSelectMenus().stream() + .filter(it -> it.getDefinitionId().equals(sanitizedId)) + .findFirst().orElseThrow(() -> new IllegalArgumentException("Unknown Select Menu")); + + return selectMenuDefinition.toSelectMenu(runtimeId, true); + } + /** * Gets the {@link ImplementationRegistry}. * From 18635417f1a62fe575b70048d2d874a1beaf8492 Mon Sep 17 00:00:00 2001 From: Kaktushose <42280757+Kaktushose@users.noreply.github.com> Date: Sun, 2 Jun 2024 14:49:46 +0200 Subject: [PATCH 04/38] improve builder components methods --- .../kaktushose/jda/commands/JDACommands.java | 45 ++++++++++++++-- .../interactions/GenericEvent.java | 54 +++++++++++++++++++ 2 files changed, 96 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/github/kaktushose/jda/commands/JDACommands.java b/src/main/java/com/github/kaktushose/jda/commands/JDACommands.java index 09f9a3860..d6bd23ec7 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/JDACommands.java +++ b/src/main/java/com/github/kaktushose/jda/commands/JDACommands.java @@ -242,9 +242,9 @@ public Button getButton(String button) { * {@code ExampleButton.onButton}. *

* - * @param button the id of the button - * @param runtimeId the id of the - * {@link com.github.kaktushose.jda.commands.dispatching.RuntimeSupervisor.InteractionRuntime InteractionRuntime} + * @param button the id of the button + * @param runtimeId the id of the + * {@link com.github.kaktushose.jda.commands.dispatching.RuntimeSupervisor.InteractionRuntime InteractionRuntime} * @return a JDA {@link Button} */ public Button getButton(String button, String runtimeId) { @@ -286,6 +286,23 @@ public SelectMenu getSelectMenu(String selectMenu) { return selectMenuDefinition.toSelectMenu(runtime.getRuntimeId(), true); } + /** + * Gets a JDA {@link SelectMenu} to use it for message builders based on the jda-commands id. + * + *

+ * The id is made up of the simple class name and the method name. E.g. the id of a a select menu defined by a + * {@code onSelectMenu(ComponentEvent event)} method inside an {@code ExampleMenu} class would be + * {@code ExampleMenu.onSelectMenu}. + *

+ * + * @param selectMenu the id of the selectMenu + * @param clazz the subtype of {@link SelectMenu} + * @return a JDA {@link SelectMenu} + */ + public T getSelectMenu(String selectMenu, Class clazz) { + return (T) getSelectMenu(selectMenu); + } + /** * Gets a JDA {@link SelectMenu} to use it for message builders based on the jda-commands id and links it an * existing @@ -315,6 +332,28 @@ public SelectMenu getSelectMenu(String selectMenu, String runtimeId) { return selectMenuDefinition.toSelectMenu(runtimeId, true); } + /** + * Gets a JDA {@link SelectMenu} subtype to use it for message builders based on the jda-commands id and links it an + * existing + * {@link com.github.kaktushose.jda.commands.dispatching.RuntimeSupervisor.InteractionRuntime InteractionRuntime}. + * + *

+ * The id is made up of the simple class name and the method name. E.g. the id of a a select menu defined by a + * {@code onSelectMenu(ComponentEvent event)} method inside an {@code ExampleMenu} class would be + * {@code ExampleMenu.onSelectMenu}. + *

+ * + * @param selectMenu the id of the selectMenu + * @param runtimeId the id of the + * {@link com.github.kaktushose.jda.commands.dispatching.RuntimeSupervisor.InteractionRuntime + * InteractionRuntime} + * @param clazz the subtype of {@link SelectMenu} + * @return a JDA {@link SelectMenu} + */ + public T getSelectMenu(String selectMenu, String runtimeId, Class clazz) { + return (T) getSelectMenu(selectMenu, runtimeId); + } + /** * Gets the {@link ImplementationRegistry}. * diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/GenericEvent.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/GenericEvent.java index 7a81dd311..8c732a70b 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/GenericEvent.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/GenericEvent.java @@ -3,6 +3,8 @@ import com.github.kaktushose.jda.commands.JDACommands; import com.github.kaktushose.jda.commands.reflect.interactions.GenericInteractionDefinition; import net.dv8tion.jda.api.events.interaction.GenericInteractionCreateEvent; +import net.dv8tion.jda.api.interactions.components.buttons.Button; +import net.dv8tion.jda.api.interactions.components.selections.SelectMenu; /** * Extension of JDAs {@link GenericInteractionCreateEvent} class. This is the base class for the different event classes. @@ -56,4 +58,56 @@ public Context getContext() { public JDACommands getJdaCommands() { return context.getJdaCommands(); } + + /** + * Gets a JDA {@link Button} to use it for message builders based on the jda-commands id. The returned button will + * be linked to the runtime of this event. + * + *

+ * The id is made up of the simple class name and the method name. E.g. the id of a button defined by a + * {@code onButton(ComponentEvent event)} method inside an {@code ExampleButton} class would be + * {@code ExampleButton.onButton}. + *

+ * + * @param button the id of the button + * @return a JDA {@link Button} + */ + public Button getButton(String button) { + return getJdaCommands().getButton(button, context.getRuntime().getRuntimeId()); + } + + /** + * Gets a JDA {@link SelectMenu} to use it for message builders based on the jda-commands id. The returned + * SelectMenu will be linked to the runtime of this event. + * + *

+ * The id is made up of the simple class name and the method name. E.g. the id of a a select menu defined by a + * {@code onSelectMenu(ComponentEvent event)} method inside an {@code ExampleMenu} class would be + * {@code ExampleMenu.onSelectMenu}. + *

+ * + * @param menu the id of the selectMenu + * @return a JDA {@link SelectMenu} + */ + public SelectMenu getSelectMenu(String menu) { + return getJdaCommands().getSelectMenu(menu, context.getRuntime().getRuntimeId()); + } + + /** + * Gets a JDA {@link SelectMenu} to use it for message builders based on the jda-commands id. The returned + * SelectMenu will be linked to the runtime of this event. + * + *

+ * The id is made up of the simple class name and the method name. E.g. the id of a a select menu defined by a + * {@code onSelectMenu(ComponentEvent event)} method inside an {@code ExampleMenu} class would be + * {@code ExampleMenu.onSelectMenu}. + *

+ * + * @param menu the id of the selectMenu + * @param clazz the subtype of {@link SelectMenu} + * @return a JDA {@link SelectMenu} + */ + public S getSelectMenu(String menu, Class clazz) { + return getJdaCommands().getSelectMenu(menu, getContext().getRuntime().getRuntimeId(), clazz); + } } From 04ff5c267d14b77582772071697eb6cb495dd897 Mon Sep 17 00:00:00 2001 From: Kaktushose <42280757+Kaktushose@users.noreply.github.com> Date: Thu, 4 Jul 2024 10:52:57 +0200 Subject: [PATCH 05/38] properly escape string replacements --- .../jda/commands/data/EmbedDTO.java | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/github/kaktushose/jda/commands/data/EmbedDTO.java b/src/main/java/com/github/kaktushose/jda/commands/data/EmbedDTO.java index 7c394019a..f4b561366 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/data/EmbedDTO.java +++ b/src/main/java/com/github/kaktushose/jda/commands/data/EmbedDTO.java @@ -10,6 +10,7 @@ import java.time.ZonedDateTime; import java.util.Arrays; import java.util.Map; +import java.util.regex.Matcher; import java.util.regex.Pattern; /** @@ -279,56 +280,56 @@ public EmbedDTO injectValues(Map values) { */ public EmbedDTO injectValue(String name, Object object) { if (title != null) { - title = title.replaceAll(String.format(Pattern.quote("{%s}"), name), String.valueOf(object)); + title = title.replaceAll(String.format(Pattern.quote("{%s}"), Matcher.quoteReplacement(name)), String.valueOf(object)); } if (description != null) { - description = description.replaceAll(String.format(Pattern.quote("{%s}"), name), String.valueOf(object)); + description = description.replaceAll(String.format(Pattern.quote("{%s}"), Matcher.quoteReplacement(name)), String.valueOf(object)); } if (url != null) { - url = url.replaceAll(String.format(Pattern.quote("{%s}"), name), String.valueOf(object)); + url = url.replaceAll(String.format(Pattern.quote("{%s}"), Matcher.quoteReplacement(name)), String.valueOf(object)); } if (color != null) { - color = color.replaceAll(String.format(Pattern.quote("{%s}"), name), String.valueOf(object)); + color = color.replaceAll(String.format(Pattern.quote("{%s}"), Matcher.quoteReplacement(name)), String.valueOf(object)); } if (timestamp != null) { - timestamp = timestamp.replaceAll(String.format(Pattern.quote("{%s}"), name), String.valueOf(object)); + timestamp = timestamp.replaceAll(String.format(Pattern.quote("{%s}"), Matcher.quoteReplacement(name)), String.valueOf(object)); } if (footer != null) { if (footer.iconUrl != null) { - footer.iconUrl = footer.iconUrl.replaceAll(String.format(Pattern.quote("{%s}"), name), String.valueOf(object)); + footer.iconUrl = footer.iconUrl.replaceAll(String.format(Pattern.quote("{%s}"), Matcher.quoteReplacement(name)), String.valueOf(object)); } if (footer.text != null) { - footer.text = footer.text.replaceAll(String.format(Pattern.quote("{%s}"), name), String.valueOf(object)); + footer.text = footer.text.replaceAll(String.format(Pattern.quote("{%s}"), Matcher.quoteReplacement(name)), String.valueOf(object)); } } if (thumbnail != null) { if (thumbnail.url != null) { - thumbnail.url = thumbnail.url.replaceAll(String.format(Pattern.quote("{%s}"), name), String.valueOf(object)); + thumbnail.url = thumbnail.url.replaceAll(String.format(Pattern.quote("{%s}"), Matcher.quoteReplacement(name)), String.valueOf(object)); } } if (image != null) { if (image.url != null) { - image.url = image.url.replaceAll(String.format(Pattern.quote("{%s}"), name), String.valueOf(object)); + image.url = image.url.replaceAll(String.format(Pattern.quote("{%s}"), Matcher.quoteReplacement(name)), String.valueOf(object)); } } if (author != null) { if (author.iconUrl != null) { - author.iconUrl = author.iconUrl.replaceAll(String.format(Pattern.quote("{%s}"), name), String.valueOf(object)); + author.iconUrl = author.iconUrl.replaceAll(String.format(Pattern.quote("{%s}"), Matcher.quoteReplacement(name)), String.valueOf(object)); } if (author.name != null) { - author.name = author.name.replaceAll(String.format(Pattern.quote("{%s}"), name), String.valueOf(object)); + author.name = author.name.replaceAll(String.format(Pattern.quote("{%s}"), Matcher.quoteReplacement(name)), String.valueOf(object)); } if (author.url != null) { - author.url = author.url.replaceAll(String.format(Pattern.quote("{%s}"), name), String.valueOf(object)); + author.url = author.url.replaceAll(String.format(Pattern.quote("{%s}"), Matcher.quoteReplacement(name)), String.valueOf(object)); } } if (fields != null) { for (Field field : fields) { if (field.name != null) { - field.name = field.name.replaceAll(String.format(Pattern.quote("{%s}"), name), String.valueOf(object)); + field.name = field.name.replaceAll(String.format(Pattern.quote("{%s}"), Matcher.quoteReplacement(name)), String.valueOf(object)); } if (field.value != null) { - field.value = field.value.replaceAll(String.format(Pattern.quote("{%s}"), name), String.valueOf(object)); + field.value = field.value.replaceAll(String.format(Pattern.quote("{%s}"), Matcher.quoteReplacement(name)), String.valueOf(object)); } } } From 5bc8482d6438b536e6ded174d8599f7b4027e7af Mon Sep 17 00:00:00 2001 From: Kaktushose <42280757+Kaktushose@users.noreply.github.com> Date: Sun, 4 Aug 2024 17:35:49 +0200 Subject: [PATCH 06/38] bump jda version and fix reply bug [#137] --- pom.xml | 2 +- .../jda/commands/dispatching/reply/Replyable.java | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index ffa45b1b0..ac3c0ae15 100644 --- a/pom.xml +++ b/pom.xml @@ -66,7 +66,7 @@ net.dv8tion JDA - 5.0.0-beta.22 + 5.0.2 club.minnced diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/reply/Replyable.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/reply/Replyable.java index 2808b1842..e844403c6 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/reply/Replyable.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/reply/Replyable.java @@ -97,7 +97,7 @@ default void reply(@NotNull EmbedDTO embedDTO) { * @see JDA RestAction Documentation */ default void reply(@NotNull String message, @Nullable Consumer success) { - reply(message); + getReplyContext().getBuilder().setContent(message); setSuccessCallback(success); reply(); } @@ -111,7 +111,7 @@ default void reply(@NotNull String message, @Nullable Consumer success) * @see JDA RestAction Documentation */ default void reply(@NotNull MessageCreateData message, @Nullable Consumer success) { - reply(message); + getReplyContext().getBuilder().applyData(message); setSuccessCallback(success); reply(); } @@ -125,7 +125,7 @@ default void reply(@NotNull MessageCreateData message, @Nullable ConsumerJDA RestAction Documentation */ default void reply(@NotNull EmbedBuilder builder, @Nullable Consumer success) { - reply(builder); + getReplyContext().getBuilder().setEmbeds(builder.build()); setSuccessCallback(success); reply(); } @@ -139,7 +139,7 @@ default void reply(@NotNull EmbedBuilder builder, @Nullable Consumer su * @see JDA RestAction Documentation */ default void reply(@NotNull EmbedDTO embedDTO, @Nullable Consumer success) { - reply(embedDTO); + getReplyContext().getBuilder().setEmbeds(embedDTO.toMessageEmbed()); setSuccessCallback(success); reply(); } From 90bf1148b57760950a81ccf1743d050c68925776 Mon Sep 17 00:00:00 2001 From: Kaktushose <42280757+Kaktushose@users.noreply.github.com> Date: Sun, 4 Aug 2024 17:42:02 +0200 Subject: [PATCH 07/38] fix unit tests --- src/test/java/adapting/mock/GuildMock.java | 2 +- src/test/java/adapting/mock/JDAMock.java | 26 +++++++++++++++++++++ src/test/java/adapting/mock/MemberMock.java | 6 ----- 3 files changed, 27 insertions(+), 7 deletions(-) diff --git a/src/test/java/adapting/mock/GuildMock.java b/src/test/java/adapting/mock/GuildMock.java index 4701b0110..6e6340ea2 100644 --- a/src/test/java/adapting/mock/GuildMock.java +++ b/src/test/java/adapting/mock/GuildMock.java @@ -128,7 +128,7 @@ public AuditableRestAction ban(@NotNull UserSnowflake user, int deletionTi @NotNull @Override - public AuditableRestAction ban(@NotNull Collection collection, @Nullable Duration duration) { + public AuditableRestAction ban(@NotNull Collection users, @Nullable Duration deletionTime) { return null; } diff --git a/src/test/java/adapting/mock/JDAMock.java b/src/test/java/adapting/mock/JDAMock.java index d209a5542..5e840d744 100644 --- a/src/test/java/adapting/mock/JDAMock.java +++ b/src/test/java/adapting/mock/JDAMock.java @@ -9,6 +9,7 @@ import net.dv8tion.jda.api.entities.sticker.StickerPack; import net.dv8tion.jda.api.entities.sticker.StickerSnowflake; import net.dv8tion.jda.api.entities.sticker.StickerUnion; +import net.dv8tion.jda.api.events.GenericEvent; import net.dv8tion.jda.api.exceptions.ErrorResponseException; import net.dv8tion.jda.api.hooks.IEventManager; import net.dv8tion.jda.api.interactions.commands.Command; @@ -23,6 +24,7 @@ import net.dv8tion.jda.api.requests.restaction.*; import net.dv8tion.jda.api.requests.restaction.pagination.EntitlementPaginationAction; import net.dv8tion.jda.api.sharding.ShardManager; +import net.dv8tion.jda.api.utils.Once; import net.dv8tion.jda.api.utils.cache.CacheFlag; import net.dv8tion.jda.api.utils.cache.CacheView; import net.dv8tion.jda.api.utils.cache.ChannelCacheView; @@ -162,6 +164,12 @@ public List getRegisteredListeners() { return null; } + @NotNull + @Override + public Once.Builder listenOnce(@NotNull Class eventType) { + return null; + } + @NotNull @Override public RestAction> retrieveCommands() { @@ -452,6 +460,24 @@ public EntitlementPaginationAction retrieveEntitlements() { return null; } + @NotNull + @Override + public RestAction retrieveEntitlementById(long entitlementId) { + return null; + } + + @NotNull + @Override + public TestEntitlementCreateAction createTestEntitlement(long skuId, long ownerId, @NotNull TestEntitlementCreateAction.OwnerType ownerType) { + return null; + } + + @NotNull + @Override + public RestAction deleteTestEntitlement(long entitlementId) { + return null; + } + @NotNull @Override public JDA setRequiredScopes(@NotNull Collection collection) { diff --git a/src/test/java/adapting/mock/MemberMock.java b/src/test/java/adapting/mock/MemberMock.java index ad5ca995a..9b7563280 100644 --- a/src/test/java/adapting/mock/MemberMock.java +++ b/src/test/java/adapting/mock/MemberMock.java @@ -255,12 +255,6 @@ public AuditableRestAction kick() { return Member.super.kick(); } - @NotNull - @Override - public AuditableRestAction kick(@Nullable String reason) { - return Member.super.kick(reason); - } - @NotNull @Override public AuditableRestAction mute(boolean mute) { From d1e7cb62293fff0ba56214875eea12ae8bec5f4b Mon Sep 17 00:00:00 2001 From: Kaktushose <42280757+Kaktushose@users.noreply.github.com> Date: Wed, 7 Aug 2024 18:11:20 +0200 Subject: [PATCH 08/38] update JsonErrorMessageFactory to use new executionFailed embed format --- src/examples/embeds.json | 8 +++++++- .../jda/commands/embeds/JsonErrorMessageFactory.java | 11 ++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/examples/embeds.json b/src/examples/embeds.json index 25d070914..11da18637 100644 --- a/src/examples/embeds.json +++ b/src/examples/embeds.json @@ -42,7 +42,13 @@ }, "executionFailed": { "title": "Command Execution Failed", - "description": "```exception```", + "description": "The command execution has unexpectedly failed. Please report the following error to the bot devs.", + "fields": [ + { + "name": "Error Message", + "value": "{error}" + } + ], "color": "#ff0000" }, "unknownInteraction": { diff --git a/src/main/java/com/github/kaktushose/jda/commands/embeds/JsonErrorMessageFactory.java b/src/main/java/com/github/kaktushose/jda/commands/embeds/JsonErrorMessageFactory.java index 3cb381d9b..be7186167 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/embeds/JsonErrorMessageFactory.java +++ b/src/main/java/com/github/kaktushose/jda/commands/embeds/JsonErrorMessageFactory.java @@ -10,6 +10,7 @@ import net.dv8tion.jda.api.utils.messages.MessageCreateData; import org.jetbrains.annotations.NotNull; +import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.List; import java.util.concurrent.TimeUnit; @@ -118,8 +119,16 @@ public MessageCreateData getCommandExecutionFailedMessage(@NotNull Context conte if (!embedCache.containsEmbed("executionFailed")) { return super.getCommandExecutionFailedMessage(context, exception); } + String error = String.format("```The user \"%s\" attempted to execute an \"%s\" interaction at %s, " + + "but a \"%s\" occurred. " + + "Please refer to the logs for further information.```", + context.getEvent().getUser().toString(), + context.getEvent().getInteraction().getType(), + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(System.currentTimeMillis()), + exception.getClass().getName() + ); return embedCache.getEmbed("executionFailed") - .injectValue("exception", exception.toString()) + .injectValue("error", error) .toMessageCreateData(); } From 37ee8b06ecb5dd441c7615a6f47042fe67b62828 Mon Sep 17 00:00:00 2001 From: Kaktushose <42280757+Kaktushose@users.noreply.github.com> Date: Mon, 25 Nov 2024 14:22:29 +0100 Subject: [PATCH 09/38] fix guild commands not registering --- .../kaktushose/jda/commands/JDACommands.java | 3 +- .../jda/commands/SlashCommandUpdater.java | 99 ++++++++----------- 2 files changed, 44 insertions(+), 58 deletions(-) diff --git a/src/main/java/com/github/kaktushose/jda/commands/JDACommands.java b/src/main/java/com/github/kaktushose/jda/commands/JDACommands.java index d6bd23ec7..27d18b48b 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/JDACommands.java +++ b/src/main/java/com/github/kaktushose/jda/commands/JDACommands.java @@ -80,8 +80,7 @@ private JDACommands(Object jda, Class clazz, LocalizationFunction function, D interactionRegistry.index(clazz, packages); updater = new SlashCommandUpdater(this); - updater.updateGlobalCommands(); - jdaContext.performTask(it -> it.addEventListener(dispatcherSupervisor)); + updater.updateAllCommands(); isActive = true; log.info("Finished loading!"); diff --git a/src/main/java/com/github/kaktushose/jda/commands/SlashCommandUpdater.java b/src/main/java/com/github/kaktushose/jda/commands/SlashCommandUpdater.java index 89b0c1a32..ccbe5ea52 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/SlashCommandUpdater.java +++ b/src/main/java/com/github/kaktushose/jda/commands/SlashCommandUpdater.java @@ -7,12 +7,9 @@ import com.github.kaktushose.jda.commands.reflect.interactions.commands.SlashCommandDefinition; import com.github.kaktushose.jda.commands.scope.GuildScopeProvider; import net.dv8tion.jda.api.entities.Guild; -import net.dv8tion.jda.api.events.guild.GuildReadyEvent; -import net.dv8tion.jda.api.hooks.ListenerAdapter; import net.dv8tion.jda.api.interactions.commands.Command; import net.dv8tion.jda.api.interactions.commands.build.CommandData; import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData; -import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -26,13 +23,12 @@ * @see CommandTree * @since 2.3.0 */ -public class SlashCommandUpdater extends ListenerAdapter { +public class SlashCommandUpdater { private static final Logger log = LoggerFactory.getLogger(SlashCommandUpdater.class); private final JDAContext jdaContext; private final GuildScopeProvider guildScopeProvider; private final InteractionRegistry interactionRegistry; - private Map> guildMapping; /** * Constructs a new SlashCommandUpdater. @@ -43,47 +39,6 @@ public SlashCommandUpdater(JDACommands jdaCommands) { this.jdaContext = jdaCommands.getJDAContext(); guildScopeProvider = jdaCommands.getImplementationRegistry().getGuildScopeProvider(); interactionRegistry = jdaCommands.getInteractionRegistry(); - guildMapping = getGuildMapping(); - } - - private Map> getGuildMapping() { - Set guildCommands = interactionRegistry.getCommands() - .stream() - .filter(it -> it.getCommandScope() == SlashCommand.CommandScope.GUILD) - .collect(Collectors.toSet()); - - CommandTree tree = new CommandTree( - guildCommands.stream() - .filter(it -> it.getCommandType() == Command.Type.SLASH) - .map(it -> (SlashCommandDefinition) it) - .collect(Collectors.toSet()) - ); - log.debug("Generated slash command tree:\n" + tree); - - Set result = new HashSet<>(); - result.addAll(tree.getCommands()); - result.addAll(guildCommands.stream(). - filter(it -> (it.getCommandType() == Command.Type.USER || it.getCommandType() == Command.Type.MESSAGE)) - .map(GenericCommandDefinition::toCommandData) - .collect(Collectors.toSet()) - ); - log.debug("Registering commands: " + result.stream().map(CommandData::getName).collect(Collectors.toSet())); - - Map> guildMapping = new HashMap<>(); - for (CommandData command : result) { - // create a copy so that a user doesn't modify the command data used for registration - Set guildIds = guildScopeProvider.getGuildsForCommand(CommandData.fromData(command.toData())); - if (guildIds.isEmpty()) { - log.debug("No guilds provided for command {}", command.getName()); - } else { - log.debug("Using guilds {} for command {}", guildIds, command.getName()); - } - guildIds.forEach(id -> { - guildMapping.putIfAbsent(id, new HashSet<>()); - guildMapping.get(id).add(command); - }); - } - return guildMapping; } /** @@ -100,12 +55,12 @@ public void updateAllCommands() { */ public void updateGuildCommands() { log.debug("Updating guild commands..."); - guildMapping = getGuildMapping(); + Map> guildMapping = getGuildMapping(); for (Guild guild : jdaContext.getGuildCache()) { Set commands = guildMapping.getOrDefault(guild.getIdLong(), Collections.emptySet()); guild.updateCommands().addCommands(commands).queue(); + log.debug(String.format("Registered guild command(s) %s for %s", commands.stream().map(CommandData::getName).collect(Collectors.toSet()), guild)); } - log.debug("Done!"); } /** @@ -125,7 +80,7 @@ public void updateGlobalCommands() { .map(it -> (SlashCommandDefinition) it) .collect(Collectors.toSet()) ); - log.debug("Generated slash command tree:\n" + tree); + log.debug("Generated slash command tree with CommandScope.GLOBAL:\n" + tree); Set result = new HashSet<>(); result.addAll(tree.getCommands()); @@ -134,16 +89,48 @@ public void updateGlobalCommands() { .map(GenericCommandDefinition::toCommandData) .collect(Collectors.toSet()) ); - log.debug("Registering commands: " + result.stream().map(CommandData::getName).collect(Collectors.toSet())); + log.debug("Registered global command(s): " + result.stream().map(CommandData::getName).collect(Collectors.toSet())); jdaContext.performTask(jda -> jda.updateCommands().addCommands(result).queue()); - log.debug("Done!"); } - @Override - public void onGuildReady(@NotNull GuildReadyEvent event) { - Guild guild = event.getGuild(); - Set commands = guildMapping.getOrDefault(guild.getIdLong(), Collections.emptySet()); - guild.updateCommands().addCommands(commands).queue(); + private Map> getGuildMapping() { + Set guildCommands = interactionRegistry.getCommands() + .stream() + .filter(it -> it.getCommandScope() == SlashCommand.CommandScope.GUILD) + .collect(Collectors.toSet()); + + CommandTree tree = new CommandTree( + guildCommands.stream() + .filter(it -> it.getCommandType() == Command.Type.SLASH) + .map(it -> (SlashCommandDefinition) it) + .collect(Collectors.toSet()) + ); + log.debug("Generated slash command tree with CommandScope.GUILD:\n" + tree); + + Set result = new HashSet<>(); + result.addAll(tree.getCommands()); + result.addAll(guildCommands.stream(). + filter(it -> (it.getCommandType() == Command.Type.USER || it.getCommandType() == Command.Type.MESSAGE)) + .map(GenericCommandDefinition::toCommandData) + .collect(Collectors.toSet()) + ); + log.debug("Interactions eligible for registration: " + result.stream().map(CommandData::getName).collect(Collectors.toSet())); + + Map> guildMapping = new HashMap<>(); + for (CommandData command : result) { + // create a copy so that a user doesn't modify the command data used for registration + Set guildIds = guildScopeProvider.getGuildsForCommand(CommandData.fromData(command.toData())); + if (guildIds.isEmpty()) { + log.debug("No guilds provided for command \"{}\"", command.getName()); + } else { + log.debug("Using guild(s) {} for command \"{}\"", guildIds, command.getName()); + } + guildIds.forEach(id -> { + guildMapping.putIfAbsent(id, new HashSet<>()); + guildMapping.get(id).add(command); + }); + } + return guildMapping; } } From aa52965bb229ebc5fd0fd1d200ee0b9308982b9a Mon Sep 17 00:00:00 2001 From: Kaktushose <42280757+Kaktushose@users.noreply.github.com> Date: Mon, 25 Nov 2024 14:22:29 +0100 Subject: [PATCH 10/38] fix guild commands not registering --- .../kaktushose/jda/commands/JDACommands.java | 2 +- .../jda/commands/SlashCommandUpdater.java | 99 ++++++++----------- 2 files changed, 44 insertions(+), 57 deletions(-) diff --git a/src/main/java/com/github/kaktushose/jda/commands/JDACommands.java b/src/main/java/com/github/kaktushose/jda/commands/JDACommands.java index d6bd23ec7..e1165047e 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/JDACommands.java +++ b/src/main/java/com/github/kaktushose/jda/commands/JDACommands.java @@ -80,7 +80,7 @@ private JDACommands(Object jda, Class clazz, LocalizationFunction function, D interactionRegistry.index(clazz, packages); updater = new SlashCommandUpdater(this); - updater.updateGlobalCommands(); + updater.updateAllCommands(); jdaContext.performTask(it -> it.addEventListener(dispatcherSupervisor)); isActive = true; diff --git a/src/main/java/com/github/kaktushose/jda/commands/SlashCommandUpdater.java b/src/main/java/com/github/kaktushose/jda/commands/SlashCommandUpdater.java index 89b0c1a32..ccbe5ea52 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/SlashCommandUpdater.java +++ b/src/main/java/com/github/kaktushose/jda/commands/SlashCommandUpdater.java @@ -7,12 +7,9 @@ import com.github.kaktushose.jda.commands.reflect.interactions.commands.SlashCommandDefinition; import com.github.kaktushose.jda.commands.scope.GuildScopeProvider; import net.dv8tion.jda.api.entities.Guild; -import net.dv8tion.jda.api.events.guild.GuildReadyEvent; -import net.dv8tion.jda.api.hooks.ListenerAdapter; import net.dv8tion.jda.api.interactions.commands.Command; import net.dv8tion.jda.api.interactions.commands.build.CommandData; import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData; -import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -26,13 +23,12 @@ * @see CommandTree * @since 2.3.0 */ -public class SlashCommandUpdater extends ListenerAdapter { +public class SlashCommandUpdater { private static final Logger log = LoggerFactory.getLogger(SlashCommandUpdater.class); private final JDAContext jdaContext; private final GuildScopeProvider guildScopeProvider; private final InteractionRegistry interactionRegistry; - private Map> guildMapping; /** * Constructs a new SlashCommandUpdater. @@ -43,47 +39,6 @@ public SlashCommandUpdater(JDACommands jdaCommands) { this.jdaContext = jdaCommands.getJDAContext(); guildScopeProvider = jdaCommands.getImplementationRegistry().getGuildScopeProvider(); interactionRegistry = jdaCommands.getInteractionRegistry(); - guildMapping = getGuildMapping(); - } - - private Map> getGuildMapping() { - Set guildCommands = interactionRegistry.getCommands() - .stream() - .filter(it -> it.getCommandScope() == SlashCommand.CommandScope.GUILD) - .collect(Collectors.toSet()); - - CommandTree tree = new CommandTree( - guildCommands.stream() - .filter(it -> it.getCommandType() == Command.Type.SLASH) - .map(it -> (SlashCommandDefinition) it) - .collect(Collectors.toSet()) - ); - log.debug("Generated slash command tree:\n" + tree); - - Set result = new HashSet<>(); - result.addAll(tree.getCommands()); - result.addAll(guildCommands.stream(). - filter(it -> (it.getCommandType() == Command.Type.USER || it.getCommandType() == Command.Type.MESSAGE)) - .map(GenericCommandDefinition::toCommandData) - .collect(Collectors.toSet()) - ); - log.debug("Registering commands: " + result.stream().map(CommandData::getName).collect(Collectors.toSet())); - - Map> guildMapping = new HashMap<>(); - for (CommandData command : result) { - // create a copy so that a user doesn't modify the command data used for registration - Set guildIds = guildScopeProvider.getGuildsForCommand(CommandData.fromData(command.toData())); - if (guildIds.isEmpty()) { - log.debug("No guilds provided for command {}", command.getName()); - } else { - log.debug("Using guilds {} for command {}", guildIds, command.getName()); - } - guildIds.forEach(id -> { - guildMapping.putIfAbsent(id, new HashSet<>()); - guildMapping.get(id).add(command); - }); - } - return guildMapping; } /** @@ -100,12 +55,12 @@ public void updateAllCommands() { */ public void updateGuildCommands() { log.debug("Updating guild commands..."); - guildMapping = getGuildMapping(); + Map> guildMapping = getGuildMapping(); for (Guild guild : jdaContext.getGuildCache()) { Set commands = guildMapping.getOrDefault(guild.getIdLong(), Collections.emptySet()); guild.updateCommands().addCommands(commands).queue(); + log.debug(String.format("Registered guild command(s) %s for %s", commands.stream().map(CommandData::getName).collect(Collectors.toSet()), guild)); } - log.debug("Done!"); } /** @@ -125,7 +80,7 @@ public void updateGlobalCommands() { .map(it -> (SlashCommandDefinition) it) .collect(Collectors.toSet()) ); - log.debug("Generated slash command tree:\n" + tree); + log.debug("Generated slash command tree with CommandScope.GLOBAL:\n" + tree); Set result = new HashSet<>(); result.addAll(tree.getCommands()); @@ -134,16 +89,48 @@ public void updateGlobalCommands() { .map(GenericCommandDefinition::toCommandData) .collect(Collectors.toSet()) ); - log.debug("Registering commands: " + result.stream().map(CommandData::getName).collect(Collectors.toSet())); + log.debug("Registered global command(s): " + result.stream().map(CommandData::getName).collect(Collectors.toSet())); jdaContext.performTask(jda -> jda.updateCommands().addCommands(result).queue()); - log.debug("Done!"); } - @Override - public void onGuildReady(@NotNull GuildReadyEvent event) { - Guild guild = event.getGuild(); - Set commands = guildMapping.getOrDefault(guild.getIdLong(), Collections.emptySet()); - guild.updateCommands().addCommands(commands).queue(); + private Map> getGuildMapping() { + Set guildCommands = interactionRegistry.getCommands() + .stream() + .filter(it -> it.getCommandScope() == SlashCommand.CommandScope.GUILD) + .collect(Collectors.toSet()); + + CommandTree tree = new CommandTree( + guildCommands.stream() + .filter(it -> it.getCommandType() == Command.Type.SLASH) + .map(it -> (SlashCommandDefinition) it) + .collect(Collectors.toSet()) + ); + log.debug("Generated slash command tree with CommandScope.GUILD:\n" + tree); + + Set result = new HashSet<>(); + result.addAll(tree.getCommands()); + result.addAll(guildCommands.stream(). + filter(it -> (it.getCommandType() == Command.Type.USER || it.getCommandType() == Command.Type.MESSAGE)) + .map(GenericCommandDefinition::toCommandData) + .collect(Collectors.toSet()) + ); + log.debug("Interactions eligible for registration: " + result.stream().map(CommandData::getName).collect(Collectors.toSet())); + + Map> guildMapping = new HashMap<>(); + for (CommandData command : result) { + // create a copy so that a user doesn't modify the command data used for registration + Set guildIds = guildScopeProvider.getGuildsForCommand(CommandData.fromData(command.toData())); + if (guildIds.isEmpty()) { + log.debug("No guilds provided for command \"{}\"", command.getName()); + } else { + log.debug("Using guild(s) {} for command \"{}\"", guildIds, command.getName()); + } + guildIds.forEach(id -> { + guildMapping.putIfAbsent(id, new HashSet<>()); + guildMapping.get(id).add(command); + }); + } + return guildMapping; } } From fce1488337f6b585edd90aaf351187f3bb1214a1 Mon Sep 17 00:00:00 2001 From: Kaktushose <42280757+Kaktushose@users.noreply.github.com> Date: Fri, 29 Nov 2024 20:17:02 +0100 Subject: [PATCH 11/38] improve documentation --- .../jda/commands/reflect/InteractionRegistry.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/github/kaktushose/jda/commands/reflect/InteractionRegistry.java b/src/main/java/com/github/kaktushose/jda/commands/reflect/InteractionRegistry.java index 2c66e5cd3..5d41a5f4a 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/reflect/InteractionRegistry.java +++ b/src/main/java/com/github/kaktushose/jda/commands/reflect/InteractionRegistry.java @@ -129,7 +129,8 @@ public Set getInteractionControllers() { } /** - * Gets a possibly-empty list of all {@link GenericCommandDefinition CommandDefinitions}. + * Gets a possibly-empty list of all {@link GenericCommandDefinition CommandDefinitions}, this includes both + * {@link SlashCommandDefinition} and {@link ContextCommandDefinition}. * * @return a possibly-empty list of all {@link GenericCommandDefinition CommandDefinitions} */ @@ -138,9 +139,9 @@ public Set getCommands() { } /** - * Gets a possibly-empty list of all {@link SlashCommandDefinition CommandDefinitions}. + * Gets a possibly-empty list of all {@link SlashCommandDefinition SlashCommandDefinitions}. * - * @return a possibly-empty list of all {@link SlashCommandDefinition CommandDefinitions} + * @return a possibly-empty list of all {@link SlashCommandDefinition SlashCommandDefinitions} */ public Set getSlashCommands() { return commands.stream().filter(it -> (it.getCommandType() == Command.Type.SLASH)) @@ -149,9 +150,9 @@ public Set getSlashCommands() { } /** - * Gets a possibly-empty list of all {@link ContextCommandDefinition CommandDefinitions}. + * Gets a possibly-empty list of all {@link ContextCommandDefinition ContextCommandDefinitions}. * - * @return a possibly-empty list of all {@link ContextCommandDefinition CommandDefinitions} + * @return a possibly-empty list of all {@link ContextCommandDefinition ContextCommandDefinitions} */ public Set getContextCommands() { return commands.stream().filter(it -> From c1ab4dfe5b8c8d06baf120c44bf0f35d4602b728 Mon Sep 17 00:00:00 2001 From: Nick Hensel <47005420+Goldmensch@users.noreply.github.com> Date: Wed, 4 Dec 2024 21:27:16 +0100 Subject: [PATCH 12/38] Bump to java 23 and refactoring (#140) * Refactor part of reflect package and JDACommands.java * fix compile errors and unit tests * fix incorrect java version in ci * bump jacoco version * remove isActive functionality * fix auto complete dispatching * move parameter validation to helper methods * Make AutoCompleteDefinition and SlashCommandDefinition immutable * Refactor tree * fix unit tests * make getComponent methods accessible again * fix CommandTree printing * remove unnecessary generic type from GenericEvent * fix unit tests * unify functional interfaces * refactor reply * change Helpers class and fix unit tests * formatting * formatting --------- Co-authored-by: Kaktushose <42280757+Kaktushose@users.noreply.github.com> --- .envrc | 1 + .github/workflows/ci.yml | 10 +- flake.lock | 58 ++++ flake.nix | 37 +++ pom.xml | 6 +- .../kaktushose/jda/commands/Helpers.java | 91 ++++++ .../kaktushose/jda/commands/JDACommands.java | 246 ++++------------ .../kaktushose/jda/commands/JDAContext.java | 87 ++---- .../jda/commands/SlashCommandUpdater.java | 12 +- .../interactions/ContextCommand.java | 7 - .../annotations/interactions/Interaction.java | 9 +- .../interactions/SlashCommand.java | 7 - .../jda/commands/data/CommandTree.java | 10 +- .../jda/commands/data/EmbedCache.java | 5 +- .../jda/commands/data/TreeNode.java | 13 +- .../dependency/DependencyInjector.java | 2 +- .../dispatching/DispatcherSupervisor.java | 30 +- .../dispatching/RuntimeSupervisor.java | 2 +- .../dispatching/adapter/TypeAdapter.java | 21 +- .../adapter/TypeAdapterRegistry.java | 16 +- .../adapter/impl/AudioChannelAdapter.java | 26 +- .../adapter/impl/BooleanAdapter.java | 2 +- .../dispatching/adapter/impl/ByteAdapter.java | 2 +- .../adapter/impl/CharacterAdapter.java | 2 +- .../adapter/impl/DoubleAdapter.java | 2 +- .../adapter/impl/FloatAdapter.java | 2 +- .../adapter/impl/GuildChannelAdapter.java | 20 +- .../impl/GuildMessageChannelAdapter.java | 26 +- .../adapter/impl/IntegerAdapter.java | 2 +- .../dispatching/adapter/impl/LongAdapter.java | 2 +- .../adapter/impl/MemberAdapter.java | 5 +- .../adapter/impl/NewsChannelAdapter.java | 18 +- .../dispatching/adapter/impl/RoleAdapter.java | 5 +- .../adapter/impl/ShortAdapter.java | 2 +- .../adapter/impl/StageChannelAdapter.java | 18 +- .../adapter/impl/TextChannelAdapter.java | 18 +- .../adapter/impl/ThreadChannelAdapter.java | 18 +- .../dispatching/adapter/impl/UserAdapter.java | 5 +- .../adapter/impl/VoiceChannelAdapter.java | 18 +- .../dispatching/interactions/Context.java | 54 +--- .../interactions/GenericDispatcher.java | 51 ++-- .../interactions/GenericEvent.java | 75 ++--- .../autocomplete/AutoCompleteDispatcher.java | 20 +- .../autocomplete/AutoCompleteEvent.java | 8 +- .../commands/CommandDispatcher.java | 30 +- .../interactions/commands/CommandEvent.java | 8 +- .../commands/SlashCommandContext.java | 10 +- .../components/ComponentDispatcher.java | 21 +- .../components/ComponentEvent.java | 8 +- .../interactions/modals/ModalDispatcher.java | 17 +- .../interactions/modals/ModalEvent.java | 8 +- .../dispatching/middleware/Middleware.java | 7 +- .../middleware/MiddlewareRegistry.java | 13 +- .../middleware/impl/ConstraintMiddleware.java | 8 +- .../middleware/impl/CooldownMiddleware.java | 4 +- .../impl/PermissionsMiddleware.java | 2 +- .../dispatching/reply/ModalReplyable.java | 2 +- .../commands/dispatching/reply/Replyable.java | 24 +- .../dispatching/reply/components/Buttons.java | 55 +--- .../reply/components/Component.java | 2 +- .../reply/components/SelectMenus.java | 18 +- .../dispatching/validation/Validator.java | 3 +- .../validation/impl/MaximumValidator.java | 2 +- .../validation/impl/MinimumValidator.java | 2 +- .../impl/NotPermissionValidator.java | 2 +- .../validation/impl/NotRoleValidator.java | 4 +- .../validation/impl/NotUserValidator.java | 4 +- .../validation/impl/PermissionValidator.java | 2 +- .../validation/impl/RoleValidator.java | 4 +- .../validation/impl/UserValidator.java | 4 +- .../embeds/DefaultErrorMessageFactory.java | 6 +- .../embeds/JsonErrorMessageFactory.java | 10 +- .../reflect/ConstraintDefinition.java | 54 +--- .../commands/reflect/CooldownDefinition.java | 69 +---- .../InteractionControllerDefinition.java | 269 ++++++------------ .../commands/reflect/InteractionRegistry.java | 114 +++----- .../commands/reflect/MethodBuildContext.java | 22 ++ .../commands/reflect/ParameterDefinition.java | 236 ++++----------- .../commands/reflect/TextInputDefinition.java | 101 +------ .../interactions/AutoCompleteDefinition.java | 48 +--- .../EphemeralInteractionDefinition.java | 5 +- .../GenericInteractionDefinition.java | 2 +- .../reflect/interactions/ModalDefinition.java | 27 +- .../commands/ContextCommandDefinition.java | 50 ++-- .../commands/GenericCommandDefinition.java | 26 +- .../commands/SlashCommandDefinition.java | 133 ++++----- .../components/ButtonDefinition.java | 66 ++--- .../GenericComponentDefinition.java | 3 +- .../menus/EntitySelectMenuDefinition.java | 82 +++--- .../menus/GenericSelectMenuDefinition.java | 2 +- .../menus/SelectOptionDefinition.java | 81 +----- .../menus/StringSelectMenuDefinition.java | 74 ++--- .../scope/DefaultGuildScopeProvider.java | 3 +- .../commands/scope/GuildScopeProvider.java | 6 +- src/main/java/module-info.java | 9 + src/test/java/adapting/CustomTypeAdapter.java | 2 +- .../adapting/TypeAdapterRegistryTest.java | 42 ++- src/test/java/adapting/TypeAdapterTest.java | 147 +++++----- src/test/java/adapting/mock/GuildMock.java | 10 +- .../java/adapting/mock/JDACommandsMock.java | 28 +- .../CommandDefinitionTestController.java | 5 - .../commands/SlashCommandDefinitionTest.java | 76 ++--- .../InteractionControllerDefinitionTest.java | 41 ++- .../parameters/ParameterDefinitionTest.java | 16 +- 104 files changed, 1233 insertions(+), 1967 deletions(-) create mode 100644 .envrc create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 src/main/java/com/github/kaktushose/jda/commands/Helpers.java create mode 100644 src/main/java/com/github/kaktushose/jda/commands/reflect/MethodBuildContext.java create mode 100644 src/main/java/module-info.java diff --git a/.envrc b/.envrc new file mode 100644 index 000000000..8392d159f --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b7b48e9a6..516aef305 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,7 +2,7 @@ name: Java CI on: push: - branches: [ main, development, release/* ] + branches: [ main, development, release/*, feature/*, bump-to-java-23 ] pull_request: branches: [ main, development, release/* ] @@ -11,10 +11,10 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - name: Set up JDK 11 - uses: actions/setup-java@v1 + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 with: - java-version: 11 + distribution: 'temurin' + java-version: '23' - name: Build with Maven run: mvn -B package --file pom.xml diff --git a/flake.lock b/flake.lock new file mode 100644 index 000000000..413c1f80b --- /dev/null +++ b/flake.lock @@ -0,0 +1,58 @@ +{ + "nodes": { + "flake-parts": { + "inputs": { + "nixpkgs-lib": "nixpkgs-lib" + }, + "locked": { + "lastModified": 1730504689, + "narHash": "sha256-hgmguH29K2fvs9szpq2r3pz2/8cJd2LPS+b4tfNFCwE=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "506278e768c2a08bec68eb62932193e341f55c90", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1733064805, + "narHash": "sha256-7NbtSLfZO0q7MXPl5hzA0sbVJt6pWxxtGWbaVUDDmjs=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "31d66ae40417bb13765b0ad75dd200400e98de84", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-lib": { + "locked": { + "lastModified": 1730504152, + "narHash": "sha256-lXvH/vOfb4aGYyvFmZK/HlsNsr/0CVWlwYvo2rxJk3s=", + "type": "tarball", + "url": "https://github.com/NixOS/nixpkgs/archive/cc2f28000298e1269cea6612cd06ec9979dd5d7f.tar.gz" + }, + "original": { + "type": "tarball", + "url": "https://github.com/NixOS/nixpkgs/archive/cc2f28000298e1269cea6612cd06ec9979dd5d7f.tar.gz" + } + }, + "root": { + "inputs": { + "flake-parts": "flake-parts", + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 000000000..7ecec0e01 --- /dev/null +++ b/flake.nix @@ -0,0 +1,37 @@ +{ + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + flake-parts.url = "github:hercules-ci/flake-parts"; + }; + + outputs = { + self, + flake-parts, + ... + } @ inputs: + flake-parts.lib.mkFlake {inherit inputs;} { + systems = ["x86_64-linux"]; + + perSystem = { + config, + lib, + pkgs, + system, + ... + }: let + javaVersion = 23; + + jdk = pkgs."temurin-bin-${toString javaVersion}"; + gradle = pkgs.gradle.override { + javaToolchains = [ + jdk + ]; + }; + in { + devShells.default = pkgs.mkShell { + name = "Jack"; + packages = with pkgs; [git jdk gradle maven]; + }; + }; + }; +} \ No newline at end of file diff --git a/pom.xml b/pom.xml index ac3c0ae15..891c26d86 100644 --- a/pom.xml +++ b/pom.xml @@ -19,8 +19,8 @@ maven-compiler-plugin 3.10.1 - 11 - 11 + 23 + 23 -parameters @@ -43,7 +43,7 @@ org.jacoco jacoco-maven-plugin - 0.8.8 + 0.8.12 diff --git a/src/main/java/com/github/kaktushose/jda/commands/Helpers.java b/src/main/java/com/github/kaktushose/jda/commands/Helpers.java new file mode 100644 index 000000000..e78e8649f --- /dev/null +++ b/src/main/java/com/github/kaktushose/jda/commands/Helpers.java @@ -0,0 +1,91 @@ +package com.github.kaktushose.jda.commands; + +import com.github.kaktushose.jda.commands.annotations.interactions.Permissions; +import com.github.kaktushose.jda.commands.dispatching.interactions.Context; +import com.github.kaktushose.jda.commands.reflect.MethodBuildContext; +import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.entities.channel.middleman.GuildChannel; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; + +public final class Helpers { + + private static final Logger log = LoggerFactory.getLogger(Helpers.class); + + /** + * Sanitizes a String containing a raw mention. This will remove all markdown characters namely < @ # & ! > + * For instance: {@code <@!393843637437464588>} gets sanitized to {@code 393843637437464588} + * + * @param mention the raw String to sanitize + * @return the sanitized String + */ + public static String sanitizeMention(@NotNull String mention) { + if (mention.matches("<[@#][&!]?([0-9]{4,})>")) { + return mention.replaceAll("<[@#][&!]?", "").replace(">", ""); + } + return mention; + } + + public static Optional resolveGuildChannel(Context context, String raw) { + GuildChannel guildChannel; + raw = sanitizeMention(raw); + + Guild guild = context.getEvent().getGuild(); + if (raw.matches("\\d+")) { + guildChannel = guild.getGuildChannelById(raw); + } else { + String finalRaw = raw; + guildChannel = guild.getChannels().stream().filter(it -> it.getName().equalsIgnoreCase(finalRaw)) + .findFirst().orElse(null); + } + return Optional.ofNullable(guildChannel); + } + + public static Set permissions(MethodBuildContext context) { + Permissions permission = context.method().getAnnotation(Permissions.class); + + if (permission != null) { + HashSet mergedPermissions = new HashSet<>(context.permissions()); + mergedPermissions.addAll(Set.of(permission.value())); + return Collections.unmodifiableSet(mergedPermissions); + } + return context.permissions(); + } + + public static boolean ephemeral(MethodBuildContext context, boolean localEphemeral) { + return context.interaction().ephemeral() || localEphemeral; + } + + public static boolean isIncorrectParameterType(Method method, int index, Class type) { + if (!type.isAssignableFrom(method.getParameters()[index].getType())) { + log.error("An error has occurred! Skipping Interaction {}.{}:", + method.getDeclaringClass().getSimpleName(), + method.getName(), + new IllegalArgumentException(String.format("%d. parameter must be of type %s", index + 1, type.getSimpleName()))); + return true; + } + return false; + } + + public static boolean isIncorrectParameterAmount(Method method, int amount) { + if (method.getParameters().length != amount) { + log.error("An error has occurred! Skipping Interaction {}.{}:", + method.getDeclaringClass().getSimpleName(), + method.getName(), + new IllegalArgumentException(String.format( + "Invalid amount of parameters!. Expected: %d Actual: %d", + amount, + method.getParameters().length + ))); + return true; + } + return false; + } +} diff --git a/src/main/java/com/github/kaktushose/jda/commands/JDACommands.java b/src/main/java/com/github/kaktushose/jda/commands/JDACommands.java index e1165047e..33864f8e1 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/JDACommands.java +++ b/src/main/java/com/github/kaktushose/jda/commands/JDACommands.java @@ -21,70 +21,57 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * Represents an active instance of this framework and provides access to all underlying classes. - * - * @since 1.0.0 - */ -public class JDACommands { +public record JDACommands( + JDAContext jdaContext, + DispatcherSupervisor dispatcherSupervisor, + MiddlewareRegistry middlewareRegistry, + TypeAdapterRegistry adapterRegistry, + ValidatorRegistry validatorRegistry, + DependencyInjector dependencyInjector, + InteractionRegistry interactionRegistry, + SlashCommandUpdater updater, + RuntimeSupervisor runtimeSupervisor +) { private static final Logger log = LoggerFactory.getLogger(JDACommands.class); - private static boolean isActive; - private final JDAContext jdaContext; - private final ImplementationRegistry implementationRegistry; - private final DispatcherSupervisor dispatcherSupervisor; - private final MiddlewareRegistry middlewareRegistry; - private final TypeAdapterRegistry adapterRegistry; - private final ValidatorRegistry validatorRegistry; - private final DependencyInjector dependencyInjector; - private final InteractionRegistry interactionRegistry; - private final SlashCommandUpdater updater; - private final RuntimeSupervisor runtimeSupervisor; - - // this is needed for unit testing - protected JDACommands() { - jdaContext = null; - implementationRegistry = null; - runtimeSupervisor = null; - middlewareRegistry = null; - adapterRegistry = null; - validatorRegistry = null; - dependencyInjector = null; - dispatcherSupervisor = null; - interactionRegistry = null; - updater = null; - } - private JDACommands(Object jda, Class clazz, LocalizationFunction function, DependencyInjector injector, String... packages) { + private static JDACommands startInternal(Object jda, Class clazz, LocalizationFunction function, DependencyInjector dependencyInjector, String... packages) { log.info("Starting JDA-Commands..."); - if (isActive) { - throw new IllegalStateException("An instance of the command framework is already running!"); - } - - jdaContext = new JDAContext(jda); - dependencyInjector = injector; + var jdaContext = new JDAContext(jda); dependencyInjector.index(clazz, packages); - middlewareRegistry = new MiddlewareRegistry(); - adapterRegistry = new TypeAdapterRegistry(); - validatorRegistry = new ValidatorRegistry(); - implementationRegistry = new ImplementationRegistry(dependencyInjector, middlewareRegistry, adapterRegistry, validatorRegistry); - interactionRegistry = new InteractionRegistry(validatorRegistry, dependencyInjector, function); + var middlewareRegistry = new MiddlewareRegistry(); + var adapterRegistry = new TypeAdapterRegistry(); + var validatorRegistry = new ValidatorRegistry(); + var implementationRegistry = new ImplementationRegistry(dependencyInjector, middlewareRegistry, adapterRegistry, validatorRegistry); + var interactionRegistry = new InteractionRegistry(validatorRegistry, dependencyInjector, function); - runtimeSupervisor = new RuntimeSupervisor(dependencyInjector); - dispatcherSupervisor = new DispatcherSupervisor(this); + var runtimeSupervisor = new RuntimeSupervisor(dependencyInjector); + var dispatcherSupervisor = new DispatcherSupervisor(middlewareRegistry, implementationRegistry, interactionRegistry, adapterRegistry, runtimeSupervisor); implementationRegistry.index(clazz, packages); interactionRegistry.index(clazz, packages); - updater = new SlashCommandUpdater(this); + var updater = new SlashCommandUpdater(jdaContext, implementationRegistry.getGuildScopeProvider(), interactionRegistry); updater.updateAllCommands(); + jdaContext.performTask(it -> it.addEventListener(dispatcherSupervisor)); - isActive = true; log.info("Finished loading!"); + + return new JDACommands( + jdaContext, + dispatcherSupervisor, + middlewareRegistry, + adapterRegistry, + validatorRegistry, + dependencyInjector, + interactionRegistry, + updater, + runtimeSupervisor + ); } /** @@ -96,7 +83,7 @@ private JDACommands(Object jda, Class clazz, LocalizationFunction function, D * @return a new JDACommands instance */ public static JDACommands start(@NotNull JDA jda, @NotNull Class clazz, @NotNull String... packages) { - return new JDACommands(jda, clazz, ResourceBundleLocalizationFunction.empty().build(), new DefaultDependencyInjector(), packages); + return startInternal(jda, clazz, ResourceBundleLocalizationFunction.empty().build(), new DefaultDependencyInjector(), packages); } /** @@ -108,7 +95,7 @@ public static JDACommands start(@NotNull JDA jda, @NotNull Class clazz, @NotN * @return a new JDACommands instance */ public static JDACommands start(@NotNull ShardManager shardManager, @NotNull Class clazz, @NotNull String... packages) { - return new JDACommands(shardManager, clazz, ResourceBundleLocalizationFunction.empty().build(), new DefaultDependencyInjector(), packages); + return startInternal(shardManager, clazz, ResourceBundleLocalizationFunction.empty().build(), new DefaultDependencyInjector(), packages); } /** @@ -121,7 +108,7 @@ public static JDACommands start(@NotNull ShardManager shardManager, @NotNull Cla * @return a new JDACommands instance */ public static JDACommands start(@NotNull JDA jda, @NotNull Class clazz, LocalizationFunction function, @NotNull String... packages) { - return new JDACommands(jda, clazz, function, new DefaultDependencyInjector(), packages); + return startInternal(jda, clazz, function, new DefaultDependencyInjector(), packages); } /** @@ -134,7 +121,7 @@ public static JDACommands start(@NotNull JDA jda, @NotNull Class clazz, Local * @return a new JDACommands instance */ public static JDACommands start(@NotNull ShardManager shardManager, @NotNull Class clazz, LocalizationFunction function, @NotNull String... packages) { - return new JDACommands(shardManager, clazz, function, new DefaultDependencyInjector(), packages); + return startInternal(shardManager, clazz, function, new DefaultDependencyInjector(), packages); } /** @@ -148,7 +135,7 @@ public static JDACommands start(@NotNull ShardManager shardManager, @NotNull Cla * @return a new JDACommands instance */ public static JDACommands start(@NotNull JDA jda, @NotNull Class clazz, LocalizationFunction function, DependencyInjector injector, @NotNull String... packages) { - return new JDACommands(jda, clazz, function, injector, packages); + return startInternal(jda, clazz, function, injector, packages); } /** @@ -162,16 +149,7 @@ public static JDACommands start(@NotNull JDA jda, @NotNull Class clazz, Local * @return a new JDACommands instance */ public static JDACommands start(@NotNull ShardManager shardManager, @NotNull Class clazz, LocalizationFunction function, DependencyInjector injector, @NotNull String... packages) { - return new JDACommands(shardManager, clazz, function, injector, packages); - } - - /** - * Whether this JDACommands instance is active. - * - * @return {@code true} if the JDACommands instance is active - */ - public static boolean isActive() { - return isActive; + return startInternal(shardManager, clazz, function, injector, packages); } /** @@ -180,28 +158,15 @@ public static boolean isActive() { */ public void shutdown() { jdaContext.performTask(jda -> jda.removeEventListener(dispatcherSupervisor)); - isActive = false; - } - - /** - * Gets the {@link DispatcherSupervisor}. - * - * @return the {@link DispatcherSupervisor} - */ - public DispatcherSupervisor getDispatcherSupervisor() { - return dispatcherSupervisor; } /** * Updates all slash commands that are registered with * {@link com.github.kaktushose.jda.commands.annotations.interactions.SlashCommand.CommandScope#GUILD * CommandScope#Guild} - * - * @return this instance */ - public JDACommands updateGuildCommands() { + public void updateGuildCommands() { updater.updateGuildCommands(); - return this; } /** @@ -272,7 +237,8 @@ public Button getButton(String button, String runtimeId) { * @param selectMenu the id of the selectMenu * @return a JDA {@link SelectMenu} */ - public SelectMenu getSelectMenu(String selectMenu) { + @SuppressWarnings("unchecked") + public T getSelectMenu(String selectMenu) { if (!selectMenu.matches("[a-zA-Z]+\\.[a-zA-Z]+")) { throw new IllegalArgumentException("Unknown Select Menu"); } @@ -283,43 +249,28 @@ public SelectMenu getSelectMenu(String selectMenu) { .findFirst().orElseThrow(() -> new IllegalArgumentException("Unknown Select Menu")); RuntimeSupervisor.InteractionRuntime runtime = runtimeSupervisor.newRuntime(selectMenuDefinition); - return selectMenuDefinition.toSelectMenu(runtime.getRuntimeId(), true); + return (T) selectMenuDefinition.toSelectMenu(runtime.getRuntimeId(), true); } /** - * Gets a JDA {@link SelectMenu} to use it for message builders based on the jda-commands id. - * - *

- * The id is made up of the simple class name and the method name. E.g. the id of a a select menu defined by a - * {@code onSelectMenu(ComponentEvent event)} method inside an {@code ExampleMenu} class would be - * {@code ExampleMenu.onSelectMenu}. - *

- * - * @param selectMenu the id of the selectMenu - * @param clazz the subtype of {@link SelectMenu} - * @return a JDA {@link SelectMenu} - */ - public T getSelectMenu(String selectMenu, Class clazz) { - return (T) getSelectMenu(selectMenu); - } - - /** - * Gets a JDA {@link SelectMenu} to use it for message builders based on the jda-commands id and links it an + * Gets a JDA {@link SelectMenu} subtype to use it for message builders based on the jda-commands id and links it an * existing * {@link com.github.kaktushose.jda.commands.dispatching.RuntimeSupervisor.InteractionRuntime InteractionRuntime}. * *

- * The id is made up of the simple class name and the method name. E.g. the id of a a select menu defined by a + * The id is made up of the simple class name and the method name. E.g. the id of a select menu defined by a * {@code onSelectMenu(ComponentEvent event)} method inside an {@code ExampleMenu} class would be * {@code ExampleMenu.onSelectMenu}. *

* * @param selectMenu the id of the selectMenu * @param runtimeId the id of the - * {@link com.github.kaktushose.jda.commands.dispatching.RuntimeSupervisor.InteractionRuntime InteractionRuntime} + * {@link com.github.kaktushose.jda.commands.dispatching.RuntimeSupervisor.InteractionRuntime + * InteractionRuntime} * @return a JDA {@link SelectMenu} */ - public SelectMenu getSelectMenu(String selectMenu, String runtimeId) { + @SuppressWarnings("unchecked") + public T getSelectMenu(String selectMenu, String runtimeId) { if (!selectMenu.matches("[a-zA-Z]+\\.[a-zA-Z]+")) { throw new IllegalArgumentException("Unknown Select Menu"); } @@ -329,101 +280,6 @@ public SelectMenu getSelectMenu(String selectMenu, String runtimeId) { .filter(it -> it.getDefinitionId().equals(sanitizedId)) .findFirst().orElseThrow(() -> new IllegalArgumentException("Unknown Select Menu")); - return selectMenuDefinition.toSelectMenu(runtimeId, true); - } - - /** - * Gets a JDA {@link SelectMenu} subtype to use it for message builders based on the jda-commands id and links it an - * existing - * {@link com.github.kaktushose.jda.commands.dispatching.RuntimeSupervisor.InteractionRuntime InteractionRuntime}. - * - *

- * The id is made up of the simple class name and the method name. E.g. the id of a a select menu defined by a - * {@code onSelectMenu(ComponentEvent event)} method inside an {@code ExampleMenu} class would be - * {@code ExampleMenu.onSelectMenu}. - *

- * - * @param selectMenu the id of the selectMenu - * @param runtimeId the id of the - * {@link com.github.kaktushose.jda.commands.dispatching.RuntimeSupervisor.InteractionRuntime - * InteractionRuntime} - * @param clazz the subtype of {@link SelectMenu} - * @return a JDA {@link SelectMenu} - */ - public T getSelectMenu(String selectMenu, String runtimeId, Class clazz) { - return (T) getSelectMenu(selectMenu, runtimeId); - } - - /** - * Gets the {@link ImplementationRegistry}. - * - * @return the {@link ImplementationRegistry} - */ - public ImplementationRegistry getImplementationRegistry() { - return implementationRegistry; - } - - - /** - * Gets the {@link RuntimeSupervisor} - * - * @return the {@link RuntimeSupervisor} - */ - public RuntimeSupervisor getRuntimeSupervisor() { - return runtimeSupervisor; - } - - /** - * Gets the {@link TypeAdapterRegistry}. - * - * @return the {@link TypeAdapterRegistry} - */ - public TypeAdapterRegistry getAdapterRegistry() { - return adapterRegistry; - } - - /** - * Gets the {@link ValidatorRegistry}. - * - * @return the {@link ValidatorRegistry} - */ - public ValidatorRegistry getValidatorRegistry() { - return validatorRegistry; - } - - /** - * Gets the {@link InteractionRegistry}. - * - * @return the {@link InteractionRegistry} - */ - public InteractionRegistry getInteractionRegistry() { - return interactionRegistry; - } - - /** - * Gets the {@link JDAContext}. - * - * @return the JDAContext. - */ - public JDAContext getJDAContext() { - return jdaContext; - } - - /** - * Gets the {@link MiddlewareRegistry}. - * - * @return the {@link MiddlewareRegistry} - */ - public MiddlewareRegistry getMiddlewareRegistry() { - return middlewareRegistry; - } - - /** - * Gets the {@link DependencyInjector}. - * - * @return the {@link DependencyInjector} - */ - public DependencyInjector getDependencyInjector() { - return dependencyInjector; + return (T) selectMenuDefinition.toSelectMenu(runtimeId, true); } } diff --git a/src/main/java/com/github/kaktushose/jda/commands/JDAContext.java b/src/main/java/com/github/kaktushose/jda/commands/JDAContext.java index e1a8aa0a8..612ccf373 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/JDAContext.java +++ b/src/main/java/com/github/kaktushose/jda/commands/JDAContext.java @@ -4,6 +4,7 @@ import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.sharding.ShardManager; import net.dv8tion.jda.api.utils.cache.SnowflakeCacheView; +import org.jetbrains.annotations.ApiStatus; import java.util.List; import java.util.function.Consumer; @@ -14,17 +15,18 @@ * * @since 2.3.0 */ -public class JDAContext { +@ApiStatus.Internal +public final class JDAContext { - private final Object jda; + private final Object context; /** * Constructs a new JDAContext. * - * @param jda the {@link JDA} or {@link ShardManager} object + * @param context the {@link JDA} or {@link ShardManager} object */ - public JDAContext(Object jda) { - this.jda = jda; + public JDAContext(Object context) { + this.context = context; } /** @@ -33,35 +35,14 @@ public JDAContext(Object jda) { * @param consumer the operation to perform */ public void performTask(Consumer consumer) { - if (jda instanceof ShardManager) { - ((ShardManager) jda).getShardCache().forEach(consumer); - } else if (jda instanceof JDA) { - consumer.accept((JDA) jda); - } else { - throw new IllegalArgumentException(String.format("Cannot cast %s", jda.getClass().getSimpleName())); + switch (context) { + case ShardManager shardManager -> shardManager.getShardCache().forEach(consumer); + case JDA jda -> consumer.accept(jda); + default -> + throw new IllegalArgumentException(String.format("Cannot cast %s", context.getClass().getSimpleName())); } } - /** - * Gets the JDA instance as an Object. This can either be {@link JDA} or a {@link ShardManager}. - * Use {@link #isShardManager()} to distinguish. - * - * @return the JDA instance. - */ - public Object getJDAObject() { - return jda; - } - - /** - * Whether the JDA instance is a {@link ShardManager}. - * - * @return {@code true} if the JDA instance is a {@link ShardManager} - * @deprecated - */ - public boolean isShardManager() { - return jda instanceof ShardManager; - } - /** * An unmodifiable List of all {@link Guild Guilds} that the logged account is connected to. * If this account is not connected to any {@link Guild Guilds}, this will return an empty list. @@ -72,13 +53,12 @@ public boolean isShardManager() { * @return Possibly-empty list of all the {@link Guild Guilds} that this account is connected to. */ public List getGuilds() { - if (jda instanceof ShardManager) { - return ((ShardManager) jda).getGuilds(); - } else if (jda instanceof JDA) { - return ((JDA) jda).getGuilds(); - } else { - throw new IllegalArgumentException(String.format("Cannot cast %s", jda.getClass().getSimpleName())); - } + return switch (context) { + case ShardManager shardManager -> shardManager.getGuilds(); + case JDA jda -> jda.getGuilds(); + default -> + throw new IllegalArgumentException(String.format("Cannot cast %s", context.getClass().getSimpleName())); + }; } /** @@ -87,30 +67,11 @@ public List getGuilds() { * @return {@link SnowflakeCacheView} */ public SnowflakeCacheView getGuildCache() { - if (jda instanceof ShardManager) { - return ((ShardManager) jda).getGuildCache(); - } else if (jda instanceof JDA) { - return ((JDA) jda).getGuildCache(); - } else { - throw new IllegalArgumentException(String.format("Cannot cast %s", jda.getClass().getSimpleName())); - } - } - - /** - * Gets the JDA instance as {@link JDA}. - * - * @return the JDA instance as {@link JDA} - */ - public JDA getAsJDA() { - return (JDA) jda; - } - - /** - * Gets the JDA instance as {@link ShardManager}. - * - * @return the JDA instance as {@link ShardManager} - */ - public ShardManager getAsShardManager() { - return (ShardManager) jda; + return switch (context) { + case ShardManager shardManager -> shardManager.getGuildCache(); + case JDA jda -> jda.getGuildCache(); + default -> + throw new IllegalArgumentException(String.format("Cannot cast %s", context.getClass().getSimpleName())); + }; } } diff --git a/src/main/java/com/github/kaktushose/jda/commands/SlashCommandUpdater.java b/src/main/java/com/github/kaktushose/jda/commands/SlashCommandUpdater.java index ccbe5ea52..b3be2c4b2 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/SlashCommandUpdater.java +++ b/src/main/java/com/github/kaktushose/jda/commands/SlashCommandUpdater.java @@ -32,13 +32,11 @@ public class SlashCommandUpdater { /** * Constructs a new SlashCommandUpdater. - * - * @param jdaCommands the corresponding {@link JDACommands} instance */ - public SlashCommandUpdater(JDACommands jdaCommands) { - this.jdaContext = jdaCommands.getJDAContext(); - guildScopeProvider = jdaCommands.getImplementationRegistry().getGuildScopeProvider(); - interactionRegistry = jdaCommands.getInteractionRegistry(); + public SlashCommandUpdater(JDAContext jdaContext, GuildScopeProvider guildScopeProvider, InteractionRegistry interactionRegistry) { + this.jdaContext = jdaContext; + this.guildScopeProvider = guildScopeProvider; + this.interactionRegistry = interactionRegistry; } /** @@ -120,7 +118,7 @@ private Map> getGuildMapping() { Map> guildMapping = new HashMap<>(); for (CommandData command : result) { // create a copy so that a user doesn't modify the command data used for registration - Set guildIds = guildScopeProvider.getGuildsForCommand(CommandData.fromData(command.toData())); + Set guildIds = guildScopeProvider.apply(CommandData.fromData(command.toData())); if (guildIds.isEmpty()) { log.debug("No guilds provided for command \"{}\"", command.getName()); } else { diff --git a/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/ContextCommand.java b/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/ContextCommand.java index f99449093..5dbf518d9 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/ContextCommand.java +++ b/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/ContextCommand.java @@ -41,13 +41,6 @@ */ boolean isGuildOnly() default false; - /** - * Returns whether this command is active and thus can be executed or not. - * - * @return {@code true} if this command is active - */ - boolean isActive() default true; - /** * Returns whether this command can only be executed in NSFW channels. * diff --git a/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/Interaction.java b/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/Interaction.java index a732ce9a9..608b708b7 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/Interaction.java +++ b/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/Interaction.java @@ -7,7 +7,7 @@ /** * Classes annotated with Interaction will be scanned at startup and are eligible for defining interactions such as - * slash commands, buttons, modals or context menus. + * slash commands, buttonContainers, modals or context menus. * * @since 4.0.0 */ @@ -22,13 +22,6 @@ */ String value() default ""; - /** - * Returns whether this interaction is active and thus can be executed or not - * - * @return {@code true} if this interaction is active - */ - boolean isActive() default true; - /** * Returns whether this interaction should send ephemeral replies by default. * diff --git a/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/SlashCommand.java b/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/SlashCommand.java index f82920fb1..8a5792b05 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/SlashCommand.java +++ b/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/SlashCommand.java @@ -52,13 +52,6 @@ */ String desc() default "N/A"; - /** - * Returns whether this command is active and thus can be executed or not. - * - * @return {@code true} if this command is active - */ - boolean isActive() default true; - /** * Returns whether this command can only be executed in NSFW channels. * diff --git a/src/main/java/com/github/kaktushose/jda/commands/data/CommandTree.java b/src/main/java/com/github/kaktushose/jda/commands/data/CommandTree.java index d97eaebec..e82ed4a24 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/data/CommandTree.java +++ b/src/main/java/com/github/kaktushose/jda/commands/data/CommandTree.java @@ -14,22 +14,22 @@ * @see Discord Subcommands and Subcommand Groups Documentation * @since 2.3.0 */ -public class CommandTree { - - private final TreeNode root; +public record CommandTree( + TreeNode root +) { /** * Constructs an empty CommandTree. */ public CommandTree() { - root = new TreeNode(); + this(new TreeNode()); } /** * Constructs a new CommandTree. */ public CommandTree(Collection commands) { - root = new TreeNode(); + this(new TreeNode()); addAll(commands); } diff --git a/src/main/java/com/github/kaktushose/jda/commands/data/EmbedCache.java b/src/main/java/com/github/kaktushose/jda/commands/data/EmbedCache.java index b833daf7f..34c769c34 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/data/EmbedCache.java +++ b/src/main/java/com/github/kaktushose/jda/commands/data/EmbedCache.java @@ -27,7 +27,7 @@ public class EmbedCache { private static final Gson gson = new Gson(); private final File file; private final InputStream stream; - private Map embedMap; + private final Map embedMap; /** * Constructs a new EmbedCache object. @@ -93,7 +93,8 @@ public void loadEmbeds() { JsonReader jsonReader = new JsonReader(reader); Type type = new TypeToken>() { }.getType(); - embedMap = gson.fromJson(jsonReader, type); + embedMap.clear(); + embedMap.putAll(gson.fromJson(jsonReader, type)); } catch (FileNotFoundException | JsonIOException | JsonSyntaxException e) { log.error("An error has occurred while loading the file!", e); } diff --git a/src/main/java/com/github/kaktushose/jda/commands/data/TreeNode.java b/src/main/java/com/github/kaktushose/jda/commands/data/TreeNode.java index b37acc9f0..7fa68c38b 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/data/TreeNode.java +++ b/src/main/java/com/github/kaktushose/jda/commands/data/TreeNode.java @@ -21,12 +21,13 @@ * @see CommandTree * @since 2.3.0 */ -public class TreeNode implements Iterable { +public record TreeNode( + String name, + SlashCommandDefinition command, + List children +) implements Iterable { private static final Logger log = LoggerFactory.getLogger(SlashCommandUpdater.class); - private final String name; - private final SlashCommandDefinition command; - private final List children; /** * Constructs an empty TreeNode. Should only be used for root nodes. @@ -42,9 +43,7 @@ public TreeNode() { * @param command the {@link SlashCommandDefinition} */ public TreeNode(@NotNull String name, @Nullable SlashCommandDefinition command) { - this.name = name; - this.command = command; - children = new ArrayList<>(); + this(name, command, new ArrayList<>()); } /** diff --git a/src/main/java/com/github/kaktushose/jda/commands/dependency/DependencyInjector.java b/src/main/java/com/github/kaktushose/jda/commands/dependency/DependencyInjector.java index 57d5b7e1c..937db317e 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dependency/DependencyInjector.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dependency/DependencyInjector.java @@ -47,5 +47,5 @@ public interface DependencyInjector { * Injects all registered dependencies with the corresponding value. If no value is present {@code null} gets injected. */ void inject(Object instance); - } + diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/DispatcherSupervisor.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/DispatcherSupervisor.java index 34dcd9b90..1eba503bc 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/DispatcherSupervisor.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/DispatcherSupervisor.java @@ -1,6 +1,6 @@ package com.github.kaktushose.jda.commands.dispatching; -import com.github.kaktushose.jda.commands.JDACommands; +import com.github.kaktushose.jda.commands.dispatching.adapter.TypeAdapterRegistry; import com.github.kaktushose.jda.commands.dispatching.interactions.Context; import com.github.kaktushose.jda.commands.dispatching.interactions.GenericDispatcher; import com.github.kaktushose.jda.commands.dispatching.interactions.autocomplete.AutoCompleteDispatcher; @@ -8,6 +8,9 @@ import com.github.kaktushose.jda.commands.dispatching.interactions.commands.SlashCommandContext; import com.github.kaktushose.jda.commands.dispatching.interactions.components.ComponentDispatcher; import com.github.kaktushose.jda.commands.dispatching.interactions.modals.ModalDispatcher; +import com.github.kaktushose.jda.commands.dispatching.middleware.MiddlewareRegistry; +import com.github.kaktushose.jda.commands.reflect.ImplementationRegistry; +import com.github.kaktushose.jda.commands.reflect.InteractionRegistry; import net.dv8tion.jda.api.events.interaction.GenericInteractionCreateEvent; import net.dv8tion.jda.api.events.interaction.ModalInteractionEvent; import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent; @@ -33,18 +36,20 @@ public class DispatcherSupervisor extends ListenerAdapter { private static final Logger log = LoggerFactory.getLogger(DispatcherSupervisor.class); private final Map, GenericDispatcher> dispatchers; - private final JDACommands jdaCommands; + private final InteractionRegistry interactionRegistry; + private final ImplementationRegistry implementationRegistry; /** * Constructs a new DispatcherSupervisor. */ - public DispatcherSupervisor(JDACommands jdaCommands) { - this.jdaCommands = jdaCommands; + public DispatcherSupervisor(MiddlewareRegistry middlewareRegistry, ImplementationRegistry implementationRegistry, InteractionRegistry interactionRegistry, TypeAdapterRegistry adapterRegistry, RuntimeSupervisor runtimeSupervisor) { dispatchers = new HashMap<>(); - register(GenericCommandInteractionEvent.class, new CommandDispatcher(jdaCommands)); - register(CommandAutoCompleteInteractionEvent.class, new AutoCompleteDispatcher(jdaCommands)); - register(GenericComponentInteractionCreateEvent.class, new ComponentDispatcher(jdaCommands)); - register(ModalInteractionEvent.class, new ModalDispatcher(jdaCommands)); + register(GenericCommandInteractionEvent.class, new CommandDispatcher(middlewareRegistry, implementationRegistry, interactionRegistry, adapterRegistry, runtimeSupervisor)); + register(CommandAutoCompleteInteractionEvent.class, new AutoCompleteDispatcher(middlewareRegistry, implementationRegistry, interactionRegistry, adapterRegistry, runtimeSupervisor)); + register(GenericComponentInteractionCreateEvent.class, new ComponentDispatcher(middlewareRegistry, implementationRegistry, interactionRegistry, adapterRegistry, runtimeSupervisor)); + register(ModalInteractionEvent.class, new ModalDispatcher(middlewareRegistry, implementationRegistry, interactionRegistry, adapterRegistry, runtimeSupervisor)); + this.interactionRegistry = interactionRegistry; + this.implementationRegistry = implementationRegistry; } /** @@ -79,12 +84,9 @@ public void onGenericInteractionCreate(@NotNull GenericInteractionCreateEvent ev return; } - Context context; - if (SlashCommandInteractionEvent.class.isAssignableFrom(clazz)) { - context = new SlashCommandContext((SlashCommandInteractionEvent) event, jdaCommands); - } else { - context = new Context(event, jdaCommands); - } + Context context = SlashCommandInteractionEvent.class.isAssignableFrom(clazz) + ? new SlashCommandContext((SlashCommandInteractionEvent) event, interactionRegistry, implementationRegistry) + : new Context(event, interactionRegistry, implementationRegistry); GenericDispatcher dispatcher = dispatchers.get(key.get()); log.debug("Calling {}", dispatcher.getClass().getName()); diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/RuntimeSupervisor.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/RuntimeSupervisor.java index b33539874..1de864ce0 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/RuntimeSupervisor.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/RuntimeSupervisor.java @@ -174,7 +174,7 @@ private Optional getRuntime(String interactionId) { /** * A runtime used for executing interactions. This class holds the instance of the class annotated with - * {@link Interaction Interaction} where commands, buttons, etc. live in. This runtime can only be used once per + * {@link Interaction Interaction} where commands, buttonContainers, etc. live in. This runtime can only be used once per * command execution. * * @since 4.0.0 diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/TypeAdapter.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/TypeAdapter.java index 09db79b82..bb6ece764 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/TypeAdapter.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/TypeAdapter.java @@ -4,15 +4,17 @@ import org.jetbrains.annotations.NotNull; import java.util.Optional; +import java.util.function.BiFunction; /** * Generic top level interface for type adapting. * - * @see com.github.kaktushose.jda.commands.annotations.Implementation * @param the type the adapter parses + * @see com.github.kaktushose.jda.commands.annotations.Implementation * @since 2.0.0 */ -public interface TypeAdapter { +@FunctionalInterface +public interface TypeAdapter extends BiFunction> { /** * Attempts to parse a String to the given type. @@ -21,19 +23,6 @@ public interface TypeAdapter { * @param context the {@link Context} * @return the parsed type or an empty Optional if the parsing fails */ - Optional parse(@NotNull String raw, @NotNull Context context); + Optional apply(@NotNull String raw, @NotNull Context context); - /** - * Sanitizes a String containing a raw mention. This will remove all markdown characters namely < @ # & ! > - * For instance: {@code <@!393843637437464588>} gets sanitized to {@code 393843637437464588} - * - * @param mention the raw String to sanitize - * @return the sanitized String - */ - default String sanitizeMention(@NotNull String mention) { - if (mention.matches("<[@#][&!]?([0-9]{4,})>")) { - return mention.replaceAll("<[@#][&!]?", "").replace(">", ""); - } - return mention; - } } diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/TypeAdapterRegistry.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/TypeAdapterRegistry.java index db0ddddd0..78c7b0c29 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/TypeAdapterRegistry.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/TypeAdapterRegistry.java @@ -1,7 +1,6 @@ package com.github.kaktushose.jda.commands.dispatching.adapter; import com.github.kaktushose.jda.commands.dispatching.adapter.impl.*; -import com.github.kaktushose.jda.commands.dispatching.interactions.commands.CommandEvent; import com.github.kaktushose.jda.commands.dispatching.interactions.commands.SlashCommandContext; import com.github.kaktushose.jda.commands.embeds.ErrorMessageFactory; import com.github.kaktushose.jda.commands.reflect.ParameterDefinition; @@ -138,12 +137,11 @@ public void adapt(@NotNull SlashCommandContext context) { ErrorMessageFactory messageFactory = context.getImplementationRegistry().getErrorMessageFactory(); log.debug("Type adapting arguments..."); - arguments.add(new CommandEvent(context)); for (int i = 0; i < command.getActualParameters().size(); i++) { ParameterDefinition parameter = command.getActualParameters().get(i); // if parameter is array don't parse - if (String[].class.isAssignableFrom(parameter.getType())) { + if (String[].class.isAssignableFrom(parameter.type())) { log.debug("First parameter is String array. Not adapting arguments"); arguments.add(input); break; @@ -162,24 +160,24 @@ public void adapt(@NotNull SlashCommandContext context) { // if the default value is an empty String (thus not present) add a null value to the argument list // else try to type adapt the default value - if (parameter.getDefaultValue() == null) { - arguments.add(DEFAULT_MAPPINGS.getOrDefault(parameter.getType(), null)); + if (parameter.defaultValue() == null) { + arguments.add(DEFAULT_MAPPINGS.getOrDefault(parameter.type(), null)); continue; } else { - raw = parameter.getDefaultValue(); + raw = parameter.defaultValue(); } } else { raw = input[i]; } - log.debug("Trying to adapt input \"{}\" to type {}", raw, parameter.getType().getName()); + log.debug("Trying to adapt input \"{}\" to type {}", raw, parameter.type().getName()); - Optional> adapter = get(parameter.getType()); + Optional> adapter = get(parameter.type()); if (adapter.isEmpty()) { throw new IllegalArgumentException("No type adapter found!"); } - Optional parsed = adapter.get().parse(raw, context); + Optional parsed = adapter.get().apply(raw, context); if (parsed.isEmpty()) { log.debug("Type adapting failed!"); context.setCancelled(messageFactory.getTypeAdaptingFailedMessage(context)); diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/AudioChannelAdapter.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/AudioChannelAdapter.java index 126ae0630..761277506 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/AudioChannelAdapter.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/AudioChannelAdapter.java @@ -1,11 +1,10 @@ package com.github.kaktushose.jda.commands.dispatching.adapter.impl; +import com.github.kaktushose.jda.commands.Helpers; import com.github.kaktushose.jda.commands.dispatching.adapter.TypeAdapter; import com.github.kaktushose.jda.commands.dispatching.interactions.Context; -import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.channel.Channel; import net.dv8tion.jda.api.entities.channel.middleman.AudioChannel; -import net.dv8tion.jda.api.entities.channel.middleman.GuildChannel; import org.jetbrains.annotations.NotNull; import java.util.Optional; @@ -25,29 +24,14 @@ public class AudioChannelAdapter implements TypeAdapter { * @return the parsed {@link AudioChannel} or an empty Optional if the parsing fails */ @Override - public Optional parse(@NotNull String raw, @NotNull Context context) { + public Optional apply(@NotNull String raw, @NotNull Context context) { Channel channel = context.getEvent().getChannel(); if (channel == null) { return Optional.empty(); } - GuildChannel guildChannel; - raw = sanitizeMention(raw); - - Guild guild = context.getEvent().getGuild(); - if (raw.matches("\\d+")) { - guildChannel = guild.getGuildChannelById(raw); - } else { - String finalRaw = raw; - guildChannel = guild.getChannels().stream().filter(it -> it.getName().equalsIgnoreCase(finalRaw)) - .findFirst().orElse(null); - } - if (guildChannel == null) { - return Optional.empty(); - } - if (!guildChannel.getType().isAudio()) { - return Optional.empty(); - } - return Optional.of((AudioChannel) guildChannel); + return Helpers.resolveGuildChannel(context, raw) + .filter(it -> it.getType().isAudio()) + .map(AudioChannel.class::cast); } } diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/BooleanAdapter.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/BooleanAdapter.java index a9a24ac15..f34be3cd1 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/BooleanAdapter.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/BooleanAdapter.java @@ -22,7 +22,7 @@ public class BooleanAdapter implements TypeAdapter { * @return the parsed boolean or an empty Optional if the parsing fails */ @Override - public Optional parse(@NotNull String raw, @NotNull Context context) { + public Optional apply(@NotNull String raw, @NotNull Context context) { if ("true".equalsIgnoreCase(raw) || "1".equals(raw)) { return Optional.of(true); } diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/ByteAdapter.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/ByteAdapter.java index 303c726af..6cff244ad 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/ByteAdapter.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/ByteAdapter.java @@ -21,7 +21,7 @@ public class ByteAdapter implements TypeAdapter { * @return the parsed Byte or an empty Optional if the parsing fails */ @Override - public Optional parse(@NotNull String raw, @NotNull Context context) { + public Optional apply(@NotNull String raw, @NotNull Context context) { try { return Optional.of(Byte.valueOf(raw)); } catch (NumberFormatException ignored) { diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/CharacterAdapter.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/CharacterAdapter.java index 3bf6f1a92..295379dd6 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/CharacterAdapter.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/CharacterAdapter.java @@ -21,7 +21,7 @@ public class CharacterAdapter implements TypeAdapter { * @return the parsed Char or an empty Optional if the parsing fails */ @Override - public Optional parse(@NotNull String raw, @NotNull Context context) { + public Optional apply(@NotNull String raw, @NotNull Context context) { if (raw.length() == 1) { return Optional.of(raw.charAt(0)); } diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/DoubleAdapter.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/DoubleAdapter.java index daf8ba35e..891ba7202 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/DoubleAdapter.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/DoubleAdapter.java @@ -21,7 +21,7 @@ public class DoubleAdapter implements TypeAdapter { * @return the parsed Double or an empty Optional if the parsing fails */ @Override - public Optional parse(@NotNull String raw, @NotNull Context context) { + public Optional apply(@NotNull String raw, @NotNull Context context) { try { return Optional.of(Double.valueOf(raw)); } catch (NumberFormatException ignored) { diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/FloatAdapter.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/FloatAdapter.java index 46917917d..5fd3b5c2e 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/FloatAdapter.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/FloatAdapter.java @@ -21,7 +21,7 @@ public class FloatAdapter implements TypeAdapter { * @return the parsed Float or an empty Optional if the parsing fails */ @Override - public Optional parse(@NotNull String raw, @NotNull Context context) { + public Optional apply(@NotNull String raw, @NotNull Context context) { try { return Optional.of(Float.valueOf(raw)); } catch (NumberFormatException ignored) { diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/GuildChannelAdapter.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/GuildChannelAdapter.java index b4e881646..aaa78ed8f 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/GuildChannelAdapter.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/GuildChannelAdapter.java @@ -1,8 +1,8 @@ package com.github.kaktushose.jda.commands.dispatching.adapter.impl; +import com.github.kaktushose.jda.commands.Helpers; import com.github.kaktushose.jda.commands.dispatching.adapter.TypeAdapter; import com.github.kaktushose.jda.commands.dispatching.interactions.Context; -import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.channel.Channel; import net.dv8tion.jda.api.entities.channel.middleman.GuildChannel; import org.jetbrains.annotations.NotNull; @@ -24,26 +24,12 @@ public class GuildChannelAdapter implements TypeAdapter { * @return the parsed {@link GuildChannel} or an empty Optional if the parsing fails */ @Override - public Optional parse(@NotNull String raw, @NotNull Context context) { + public Optional apply(@NotNull String raw, @NotNull Context context) { Channel channel = context.getEvent().getChannel(); if (channel == null) { return Optional.empty(); } - GuildChannel guildChannel; - raw = sanitizeMention(raw); - - Guild guild = context.getEvent().getGuild(); - if (raw.matches("\\d+")) { - guildChannel = guild.getGuildChannelById(raw); - } else { - String finalRaw = raw; - guildChannel = guild.getChannels().stream().filter(it -> it.getName().equalsIgnoreCase(finalRaw)) - .findFirst().orElse(null); - } - if (guildChannel == null) { - return Optional.empty(); - } - return Optional.of(guildChannel); + return Helpers.resolveGuildChannel(context, raw); } } diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/GuildMessageChannelAdapter.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/GuildMessageChannelAdapter.java index 9935563fd..917285bdd 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/GuildMessageChannelAdapter.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/GuildMessageChannelAdapter.java @@ -1,10 +1,9 @@ package com.github.kaktushose.jda.commands.dispatching.adapter.impl; +import com.github.kaktushose.jda.commands.Helpers; import com.github.kaktushose.jda.commands.dispatching.adapter.TypeAdapter; import com.github.kaktushose.jda.commands.dispatching.interactions.Context; -import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.channel.Channel; -import net.dv8tion.jda.api.entities.channel.middleman.GuildChannel; import net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel; import org.jetbrains.annotations.NotNull; @@ -25,29 +24,14 @@ public class GuildMessageChannelAdapter implements TypeAdapter parse(@NotNull String raw, @NotNull Context context) { + public Optional apply(@NotNull String raw, @NotNull Context context) { Channel channel = context.getEvent().getChannel(); if (channel == null) { return Optional.empty(); } - GuildChannel guildChannel; - raw = sanitizeMention(raw); - - Guild guild = context.getEvent().getGuild(); - if (raw.matches("\\d+")) { - guildChannel = guild.getGuildChannelById(raw); - } else { - String finalRaw = raw; - guildChannel = guild.getChannels().stream().filter(it -> it.getName().equalsIgnoreCase(finalRaw)) - .findFirst().orElse(null); - } - if (guildChannel == null) { - return Optional.empty(); - } - if (!guildChannel.getType().isMessage()) { - return Optional.empty(); - } - return Optional.of((GuildMessageChannel) guildChannel); + return Helpers.resolveGuildChannel(context, raw) + .filter(it -> it.getType().isMessage()) + .map(GuildMessageChannel.class::cast); } } diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/IntegerAdapter.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/IntegerAdapter.java index 31cb6d39f..af18275a0 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/IntegerAdapter.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/IntegerAdapter.java @@ -21,7 +21,7 @@ public class IntegerAdapter implements TypeAdapter { * @return the parsed Integer or an empty Optional if the parsing fails */ @Override - public Optional parse(@NotNull String raw, @NotNull Context context) { + public Optional apply(@NotNull String raw, @NotNull Context context) { try { return Optional.of((int) Double.parseDouble(raw)); } catch (NumberFormatException ignored) { diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/LongAdapter.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/LongAdapter.java index 54f585e46..7b753cf47 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/LongAdapter.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/LongAdapter.java @@ -21,7 +21,7 @@ public class LongAdapter implements TypeAdapter { * @return the parsed Long or an empty Optional if the parsing fails */ @Override - public Optional parse(@NotNull String raw, @NotNull Context context) { + public Optional apply(@NotNull String raw, @NotNull Context context) { try { return Optional.of((long) Double.parseDouble(raw)); } catch (NumberFormatException ignored) { diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/MemberAdapter.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/MemberAdapter.java index e50fa771a..e7faf5e64 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/MemberAdapter.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/MemberAdapter.java @@ -1,5 +1,6 @@ package com.github.kaktushose.jda.commands.dispatching.adapter.impl; +import com.github.kaktushose.jda.commands.Helpers; import com.github.kaktushose.jda.commands.dispatching.adapter.TypeAdapter; import com.github.kaktushose.jda.commands.dispatching.interactions.Context; import net.dv8tion.jda.api.entities.Guild; @@ -24,13 +25,13 @@ public class MemberAdapter implements TypeAdapter { * @return the parsed {@link Member} or an empty Optional if the parsing fails */ @Override - public Optional parse(@NotNull String raw, @NotNull Context context) { + public Optional apply(@NotNull String raw, @NotNull Context context) { if (context.getEvent().getGuild() == null) { return Optional.empty(); } Member member; - raw = sanitizeMention(raw); + raw = Helpers.sanitizeMention(raw); Guild guild = context.getEvent().getGuild(); if (raw.matches("\\d+")) { diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/NewsChannelAdapter.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/NewsChannelAdapter.java index 924b020e8..778e7f94a 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/NewsChannelAdapter.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/NewsChannelAdapter.java @@ -1,8 +1,8 @@ package com.github.kaktushose.jda.commands.dispatching.adapter.impl; +import com.github.kaktushose.jda.commands.Helpers; import com.github.kaktushose.jda.commands.dispatching.adapter.TypeAdapter; import com.github.kaktushose.jda.commands.dispatching.interactions.Context; -import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.channel.Channel; import net.dv8tion.jda.api.entities.channel.concrete.NewsChannel; import org.jetbrains.annotations.NotNull; @@ -24,24 +24,12 @@ public class NewsChannelAdapter implements TypeAdapter { * @return the parsed {@link NewsChannel} or an empty Optional if the parsing fails */ @Override - public Optional parse(@NotNull String raw, @NotNull Context context) { + public Optional apply(@NotNull String raw, @NotNull Context context) { Channel channel = context.getEvent().getChannel(); if (channel == null) { return Optional.empty(); } - NewsChannel newsChannel; - raw = sanitizeMention(raw); - - Guild guild = context.getEvent().getGuild(); - if (raw.matches("\\d+")) { - newsChannel = guild.getNewsChannelById(raw); - } else { - newsChannel = guild.getNewsChannelsByName(raw, true).stream().findFirst().orElse(null); - } - if (newsChannel == null) { - return Optional.empty(); - } - return Optional.of(newsChannel); + return Helpers.resolveGuildChannel(context, raw).filter(NewsChannel.class::isInstance).map(NewsChannel.class::cast); } } diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/RoleAdapter.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/RoleAdapter.java index 43af60261..7f0de03da 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/RoleAdapter.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/RoleAdapter.java @@ -1,5 +1,6 @@ package com.github.kaktushose.jda.commands.dispatching.adapter.impl; +import com.github.kaktushose.jda.commands.Helpers; import com.github.kaktushose.jda.commands.dispatching.adapter.TypeAdapter; import com.github.kaktushose.jda.commands.dispatching.interactions.Context; import net.dv8tion.jda.api.entities.Guild; @@ -23,13 +24,13 @@ public class RoleAdapter implements TypeAdapter { * @return the parsed {@link Role} or an empty Optional if the parsing fails */ @Override - public Optional parse(@NotNull String raw, @NotNull Context context) { + public Optional apply(@NotNull String raw, @NotNull Context context) { if (context.getEvent().getGuild() == null) { return Optional.empty(); } Role role; - raw = sanitizeMention(raw); + raw = Helpers.sanitizeMention(raw); Guild guild = context.getEvent().getGuild(); if (raw.matches("\\d+")) { diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/ShortAdapter.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/ShortAdapter.java index 03d4396b1..8a4d4fe34 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/ShortAdapter.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/ShortAdapter.java @@ -21,7 +21,7 @@ public class ShortAdapter implements TypeAdapter { * @return the parsed Short or an empty Optional if the parsing fails */ @Override - public Optional parse(@NotNull String raw, @NotNull Context context) { + public Optional apply(@NotNull String raw, @NotNull Context context) { try { return Optional.of(Short.valueOf(raw)); } catch (NumberFormatException ignored) { diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/StageChannelAdapter.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/StageChannelAdapter.java index 54e0fcb36..4e57998cd 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/StageChannelAdapter.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/StageChannelAdapter.java @@ -1,8 +1,8 @@ package com.github.kaktushose.jda.commands.dispatching.adapter.impl; +import com.github.kaktushose.jda.commands.Helpers; import com.github.kaktushose.jda.commands.dispatching.adapter.TypeAdapter; import com.github.kaktushose.jda.commands.dispatching.interactions.Context; -import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.channel.Channel; import net.dv8tion.jda.api.entities.channel.concrete.StageChannel; import org.jetbrains.annotations.NotNull; @@ -24,24 +24,12 @@ public class StageChannelAdapter implements TypeAdapter { * @return the parsed {@link StageChannel} or an empty Optional if the parsing fails */ @Override - public Optional parse(@NotNull String raw, @NotNull Context context) { + public Optional apply(@NotNull String raw, @NotNull Context context) { Channel channel = context.getEvent().getChannel(); if (channel == null) { return Optional.empty(); } - StageChannel stageChannel; - raw = sanitizeMention(raw); - - Guild guild = context.getEvent().getGuild(); - if (raw.matches("\\d+")) { - stageChannel = guild.getStageChannelById(raw); - } else { - stageChannel = guild.getStageChannelsByName(raw, true).stream().findFirst().orElse(null); - } - if (stageChannel == null) { - return Optional.empty(); - } - return Optional.of(stageChannel); + return Helpers.resolveGuildChannel(context, raw).filter(StageChannel.class::isInstance).map(StageChannel.class::cast); } } diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/TextChannelAdapter.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/TextChannelAdapter.java index 02618557f..38dd0a4a0 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/TextChannelAdapter.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/TextChannelAdapter.java @@ -1,8 +1,8 @@ package com.github.kaktushose.jda.commands.dispatching.adapter.impl; +import com.github.kaktushose.jda.commands.Helpers; import com.github.kaktushose.jda.commands.dispatching.adapter.TypeAdapter; import com.github.kaktushose.jda.commands.dispatching.interactions.Context; -import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; import org.jetbrains.annotations.NotNull; @@ -23,23 +23,11 @@ public class TextChannelAdapter implements TypeAdapter { * @return the parsed {@link TextChannel} or an empty Optional if the parsing fails */ @Override - public Optional parse(@NotNull String raw, @NotNull Context context) { + public Optional apply(@NotNull String raw, @NotNull Context context) { if (context.getEvent().getGuild() == null) { return Optional.empty(); } - TextChannel textChannel; - raw = sanitizeMention(raw); - - Guild guild = context.getEvent().getGuild(); - if (raw.matches("\\d+")) { - textChannel = guild.getTextChannelById(raw); - } else { - textChannel = guild.getTextChannelsByName(raw, true).stream().findFirst().orElse(null); - } - if (textChannel == null) { - return Optional.empty(); - } - return Optional.of(textChannel); + return Helpers.resolveGuildChannel(context, raw).filter(TextChannel.class::isInstance).map(TextChannel.class::cast); } } diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/ThreadChannelAdapter.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/ThreadChannelAdapter.java index 259575634..7a797d9c1 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/ThreadChannelAdapter.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/ThreadChannelAdapter.java @@ -1,8 +1,8 @@ package com.github.kaktushose.jda.commands.dispatching.adapter.impl; +import com.github.kaktushose.jda.commands.Helpers; import com.github.kaktushose.jda.commands.dispatching.adapter.TypeAdapter; import com.github.kaktushose.jda.commands.dispatching.interactions.Context; -import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.channel.Channel; import net.dv8tion.jda.api.entities.channel.concrete.ThreadChannel; import org.jetbrains.annotations.NotNull; @@ -24,24 +24,12 @@ public class ThreadChannelAdapter implements TypeAdapter { * @return the parsed {@link ThreadChannel} or an empty Optional if the parsing fails */ @Override - public Optional parse(@NotNull String raw, @NotNull Context context) { + public Optional apply(@NotNull String raw, @NotNull Context context) { Channel channel = context.getEvent().getChannel(); if (channel == null) { return Optional.empty(); } - ThreadChannel threadChannel; - raw = sanitizeMention(raw); - - Guild guild = context.getEvent().getGuild(); - if (raw.matches("\\d+")) { - threadChannel = guild.getThreadChannelById(raw); - } else { - threadChannel = guild.getThreadChannelsByName(raw, true).stream().findFirst().orElse(null); - } - if (threadChannel == null) { - return Optional.empty(); - } - return Optional.of(threadChannel); + return Helpers.resolveGuildChannel(context, raw).filter(ThreadChannel.class::isInstance).map(ThreadChannel.class::cast); } } diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/UserAdapter.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/UserAdapter.java index 75a07cde4..314f8cb59 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/UserAdapter.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/UserAdapter.java @@ -1,5 +1,6 @@ package com.github.kaktushose.jda.commands.dispatching.adapter.impl; +import com.github.kaktushose.jda.commands.Helpers; import com.github.kaktushose.jda.commands.dispatching.adapter.TypeAdapter; import com.github.kaktushose.jda.commands.dispatching.interactions.Context; import net.dv8tion.jda.api.JDA; @@ -24,9 +25,9 @@ public class UserAdapter implements TypeAdapter { * @return the parsed {@link User} or an empty Optional if the parsing fails */ @Override - public Optional parse(@NotNull String raw, @NotNull Context context) { + public Optional apply(@NotNull String raw, @NotNull Context context) { User user; - raw = sanitizeMention(raw); + raw = Helpers.sanitizeMention(raw); JDA jda = context.getEvent().getJDA(); if (raw.matches("\\d+")) { try { diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/VoiceChannelAdapter.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/VoiceChannelAdapter.java index be4f910e1..6a804e3a2 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/VoiceChannelAdapter.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/VoiceChannelAdapter.java @@ -1,8 +1,8 @@ package com.github.kaktushose.jda.commands.dispatching.adapter.impl; +import com.github.kaktushose.jda.commands.Helpers; import com.github.kaktushose.jda.commands.dispatching.adapter.TypeAdapter; import com.github.kaktushose.jda.commands.dispatching.interactions.Context; -import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.channel.Channel; import net.dv8tion.jda.api.entities.channel.concrete.VoiceChannel; import org.jetbrains.annotations.NotNull; @@ -24,24 +24,12 @@ public class VoiceChannelAdapter implements TypeAdapter { * @return the parsed {@link VoiceChannel} or an empty Optional if the parsing fails */ @Override - public Optional parse(@NotNull String raw, @NotNull Context context) { + public Optional apply(@NotNull String raw, @NotNull Context context) { Channel channel = context.getEvent().getChannel(); if (channel == null) { return Optional.empty(); } - VoiceChannel voiceChannel; - raw = sanitizeMention(raw); - - Guild guild = context.getEvent().getGuild(); - if (raw.matches("\\d+")) { - voiceChannel = guild.getVoiceChannelById(raw); - } else { - voiceChannel = guild.getVoiceChannelsByName(raw, true).stream().findFirst().orElse(null); - } - if (voiceChannel == null) { - return Optional.empty(); - } - return Optional.of(voiceChannel); + return Helpers.resolveGuildChannel(context, raw).filter(VoiceChannel.class::isInstance).map(VoiceChannel.class::cast); } } diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/Context.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/Context.java index 77bb2f12a..1959eac86 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/Context.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/Context.java @@ -1,9 +1,9 @@ package com.github.kaktushose.jda.commands.dispatching.interactions; -import com.github.kaktushose.jda.commands.JDACommands; import com.github.kaktushose.jda.commands.dispatching.KeyValueStore; import com.github.kaktushose.jda.commands.dispatching.RuntimeSupervisor.InteractionRuntime; import com.github.kaktushose.jda.commands.reflect.ImplementationRegistry; +import com.github.kaktushose.jda.commands.reflect.InteractionRegistry; import com.github.kaktushose.jda.commands.reflect.interactions.GenericInteractionDefinition; import net.dv8tion.jda.api.entities.Message; import net.dv8tion.jda.api.events.interaction.GenericInteractionCreateEvent; @@ -24,22 +24,22 @@ public class Context { protected final GenericInteractionCreateEvent event; protected MessageCreateData errorMessage; protected ImplementationRegistry registry; - protected JDACommands jdaCommands; protected boolean cancelled; protected boolean ephemeral; protected InteractionRuntime runtime; protected GenericInteractionDefinition interactionDefinition; + protected final InteractionRegistry interactionRegistry; + protected final ImplementationRegistry implementationRegistry; /** * Constructs a new GenericContext. * - * @param jdaCommands the corresponding {@link JDACommands} instance - * @param event the corresponding {@link GenericInteractionCreateEvent} + * @param event the corresponding {@link GenericInteractionCreateEvent} */ - public Context(GenericInteractionCreateEvent event, JDACommands jdaCommands) { + public Context(GenericInteractionCreateEvent event, InteractionRegistry interactionRegistry, ImplementationRegistry implementationRegistry) { this.event = event; - this.jdaCommands = jdaCommands; - this.registry = jdaCommands.getImplementationRegistry(); + this.interactionRegistry = interactionRegistry; + this.implementationRegistry = implementationRegistry; } public GenericInteractionCreateEvent getEvent() { @@ -56,16 +56,6 @@ public MessageCreateData getErrorMessage() { return errorMessage; } - /** - * Gets the corresponding {@link ImplementationRegistry} instance. - * - * @return the corresponding {@link ImplementationRegistry} instance - */ - @NotNull - public ImplementationRegistry getImplementationRegistry() { - return registry; - } - /** * Set the {@link ImplementationRegistry} instance. * @@ -78,28 +68,6 @@ public Context setImplementationRegistry(@NotNull ImplementationRegistry registr return this; } - /** - * Gets the corresponding {@link JDACommands} instance. - * - * @return the corresponding {@link JDACommands} instance - */ - @NotNull - public JDACommands getJdaCommands() { - return jdaCommands; - } - - /** - * Set the {@link JDACommands} instance. - * - * @param jdaCommands the {@link JDACommands} instance - * @return the current Context instance - */ - @NotNull - public Context setJdaCommands(@NotNull JDACommands jdaCommands) { - this.jdaCommands = jdaCommands; - return this; - } - /** * Whether the context should be cancelled. * @@ -210,4 +178,12 @@ public Context setKeyValueStore(KeyValueStore keyValueStore) { runtime.setKeyValueStore(keyValueStore); return this; } + + public ImplementationRegistry getImplementationRegistry() { + return implementationRegistry; + } + + public InteractionRegistry getInteractionRegistry() { + return interactionRegistry; + } } diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/GenericDispatcher.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/GenericDispatcher.java index c20a3a844..26c05690d 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/GenericDispatcher.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/GenericDispatcher.java @@ -1,6 +1,5 @@ package com.github.kaktushose.jda.commands.dispatching.interactions; -import com.github.kaktushose.jda.commands.JDACommands; import com.github.kaktushose.jda.commands.dispatching.RuntimeSupervisor; import com.github.kaktushose.jda.commands.dispatching.adapter.TypeAdapterRegistry; import com.github.kaktushose.jda.commands.dispatching.middleware.Middleware; @@ -20,7 +19,6 @@ public abstract class GenericDispatcher { private final static Logger log = LoggerFactory.getLogger(GenericDispatcher.class); - protected final JDACommands jdaCommands; protected final MiddlewareRegistry middlewareRegistry; protected final ImplementationRegistry implementationRegistry; protected final InteractionRegistry interactionRegistry; @@ -29,51 +27,42 @@ public abstract class GenericDispatcher { /** * Constructs a new GenericDispatcher. - * - * @param jdaCommands the corresponding {@link JDACommands} instance. */ - public GenericDispatcher(JDACommands jdaCommands) { - this.jdaCommands = jdaCommands; - middlewareRegistry = jdaCommands.getMiddlewareRegistry(); - implementationRegistry = jdaCommands.getImplementationRegistry(); - interactionRegistry = jdaCommands.getInteractionRegistry(); - adapterRegistry = jdaCommands.getAdapterRegistry(); - runtimeSupervisor = jdaCommands.getRuntimeSupervisor(); + public GenericDispatcher(MiddlewareRegistry middlewareRegistry, + ImplementationRegistry implementationRegistry, + InteractionRegistry interactionRegistry, + TypeAdapterRegistry adapterRegistry, + RuntimeSupervisor runtimeSupervisor) { + this.middlewareRegistry = middlewareRegistry; + this.implementationRegistry = implementationRegistry; + this.interactionRegistry = interactionRegistry; + this.adapterRegistry = adapterRegistry; + this.runtimeSupervisor = runtimeSupervisor; } protected void executeMiddlewares(Context context) { log.debug("Executing middlewares..."); for (Middleware middleware : middlewareRegistry.getMiddlewares(Priority.PERMISSIONS)) { - log.debug("Executing middleware {}", middleware.getClass().getSimpleName()); - middleware.execute(context); - if (context.isCancelled()) { - return; - } + if (executeMiddleware(context, middleware)) return; } for (Middleware middleware : middlewareRegistry.getMiddlewares(Priority.HIGH)) { - log.debug("Executing middleware {}", middleware.getClass().getSimpleName()); - middleware.execute(context); - if (context.isCancelled()) { - return; - } + if (executeMiddleware(context, middleware)) return; } for (Middleware middleware : middlewareRegistry.getMiddlewares(Priority.NORMAL)) { - log.debug("Executing middleware {}", middleware.getClass().getSimpleName()); - middleware.execute(context); - if (context.isCancelled()) { - return; - } + if (executeMiddleware(context, middleware)) return; } for (Middleware middleware : middlewareRegistry.getMiddlewares(Priority.LOW)) { - log.debug("Executing middleware {}", middleware.getClass().getSimpleName()); - middleware.execute(context); - if (context.isCancelled()) { - return; - } + if (executeMiddleware(context, middleware)) return; } } + private boolean executeMiddleware(Context context, Middleware middleware) { + log.debug("Executing middleware {}", middleware.getClass().getSimpleName()); + middleware.accept(context); + return context.isCancelled(); + } + /** * Dispatches a {@link Context}. * diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/GenericEvent.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/GenericEvent.java index 8c732a70b..40236932e 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/GenericEvent.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/GenericEvent.java @@ -1,7 +1,8 @@ package com.github.kaktushose.jda.commands.dispatching.interactions; -import com.github.kaktushose.jda.commands.JDACommands; -import com.github.kaktushose.jda.commands.reflect.interactions.GenericInteractionDefinition; +import com.github.kaktushose.jda.commands.reflect.InteractionRegistry; +import com.github.kaktushose.jda.commands.reflect.interactions.components.ButtonDefinition; +import com.github.kaktushose.jda.commands.reflect.interactions.components.menus.GenericSelectMenuDefinition; import net.dv8tion.jda.api.events.interaction.GenericInteractionCreateEvent; import net.dv8tion.jda.api.interactions.components.buttons.Button; import net.dv8tion.jda.api.interactions.components.selections.SelectMenu; @@ -15,30 +16,20 @@ * @see com.github.kaktushose.jda.commands.dispatching.interactions.modals.ModalEvent ModalEvent * @since 4.0.0 */ -public abstract class GenericEvent extends GenericInteractionCreateEvent { +public abstract class GenericEvent extends GenericInteractionCreateEvent { protected final Context context; - private final T definition; + private final InteractionRegistry interactionRegistry; /** * Constructs a new GenericEvent. * * @param context the underlying {@link Context} */ - @SuppressWarnings("unchecked") - protected GenericEvent(Context context) { + protected GenericEvent(Context context, InteractionRegistry interactionRegistry) { super(context.getEvent().getJDA(), context.getEvent().getResponseNumber(), context.getEvent().getInteraction()); - definition = (T) context.getInteractionDefinition(); this.context = context; - } - - /** - * Get the interaction object which describes the component that is executed. - * - * @return the underlying interaction object - */ - public T getInteractionDefinition() { - return definition; + this.interactionRegistry = interactionRegistry; } /** @@ -50,15 +41,6 @@ public Context getContext() { return context; } - /** - * Get the {@link JDACommands} object. - * - * @return the {@link JDACommands} object - */ - public JDACommands getJdaCommands() { - return context.getJdaCommands(); - } - /** * Gets a JDA {@link Button} to use it for message builders based on the jda-commands id. The returned button will * be linked to the runtime of this event. @@ -73,7 +55,16 @@ public JDACommands getJdaCommands() { * @return a JDA {@link Button} */ public Button getButton(String button) { - return getJdaCommands().getButton(button, context.getRuntime().getRuntimeId()); + if (!button.matches("[a-zA-Z]+\\.[a-zA-Z]+")) { + throw new IllegalArgumentException("Unknown Button"); + } + + String sanitizedId = button.replaceAll("\\.", ""); + ButtonDefinition buttonDefinition = interactionRegistry.getButtons().stream() + .filter(it -> it.getDefinitionId().equals(sanitizedId)) + .findFirst().orElseThrow(() -> new IllegalArgumentException("Unknown Button")); + + return buttonDefinition.toButton().withId(buttonDefinition.createCustomId(context.getRuntime().getRuntimeId())); } /** @@ -81,7 +72,7 @@ public Button getButton(String button) { * SelectMenu will be linked to the runtime of this event. * *

- * The id is made up of the simple class name and the method name. E.g. the id of a a select menu defined by a + * The id is made up of the simple class name and the method name. E.g. the id of a select menu defined by a * {@code onSelectMenu(ComponentEvent event)} method inside an {@code ExampleMenu} class would be * {@code ExampleMenu.onSelectMenu}. *

@@ -89,25 +80,17 @@ public Button getButton(String button) { * @param menu the id of the selectMenu * @return a JDA {@link SelectMenu} */ - public SelectMenu getSelectMenu(String menu) { - return getJdaCommands().getSelectMenu(menu, context.getRuntime().getRuntimeId()); - } + @SuppressWarnings("unchecked") + public T getSelectMenu(String menu) { + if (!menu.matches("[a-zA-Z]+\\.[a-zA-Z]+")) { + throw new IllegalArgumentException("Unknown Select Menu"); + } - /** - * Gets a JDA {@link SelectMenu} to use it for message builders based on the jda-commands id. The returned - * SelectMenu will be linked to the runtime of this event. - * - *

- * The id is made up of the simple class name and the method name. E.g. the id of a a select menu defined by a - * {@code onSelectMenu(ComponentEvent event)} method inside an {@code ExampleMenu} class would be - * {@code ExampleMenu.onSelectMenu}. - *

- * - * @param menu the id of the selectMenu - * @param clazz the subtype of {@link SelectMenu} - * @return a JDA {@link SelectMenu} - */ - public S getSelectMenu(String menu, Class clazz) { - return getJdaCommands().getSelectMenu(menu, getContext().getRuntime().getRuntimeId(), clazz); + String sanitizedId = menu.replaceAll("\\.", ""); + GenericSelectMenuDefinition selectMenuDefinition = interactionRegistry.getSelectMenus().stream() + .filter(it -> it.getDefinitionId().equals(sanitizedId)) + .findFirst().orElseThrow(() -> new IllegalArgumentException("Unknown Select Menu")); + + return (T) selectMenuDefinition.toSelectMenu(context.getRuntime().getRuntimeId(), true); } } diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/autocomplete/AutoCompleteDispatcher.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/autocomplete/AutoCompleteDispatcher.java index 9f78764aa..34ef20298 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/autocomplete/AutoCompleteDispatcher.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/autocomplete/AutoCompleteDispatcher.java @@ -1,9 +1,13 @@ package com.github.kaktushose.jda.commands.dispatching.interactions.autocomplete; -import com.github.kaktushose.jda.commands.JDACommands; +import com.github.kaktushose.jda.commands.dispatching.RuntimeSupervisor; +import com.github.kaktushose.jda.commands.dispatching.adapter.TypeAdapterRegistry; import com.github.kaktushose.jda.commands.dispatching.interactions.Context; import com.github.kaktushose.jda.commands.dispatching.interactions.GenericDispatcher; +import com.github.kaktushose.jda.commands.dispatching.middleware.MiddlewareRegistry; import com.github.kaktushose.jda.commands.dispatching.reply.ReplyContext; +import com.github.kaktushose.jda.commands.reflect.ImplementationRegistry; +import com.github.kaktushose.jda.commands.reflect.InteractionRegistry; import com.github.kaktushose.jda.commands.reflect.interactions.AutoCompleteDefinition; import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent; import org.slf4j.Logger; @@ -23,17 +27,21 @@ public class AutoCompleteDispatcher extends GenericDispatcher { /** * Constructs a new AutoCompleteDispatcher. * - * @param jdaCommands the corresponding {@link JDACommands} instance. + * @param middlewareRegistry + * @param implementationRegistry + * @param interactionRegistry + * @param adapterRegistry + * @param runtimeSupervisor */ - public AutoCompleteDispatcher(JDACommands jdaCommands) { - super(jdaCommands); + public AutoCompleteDispatcher(MiddlewareRegistry middlewareRegistry, ImplementationRegistry implementationRegistry, InteractionRegistry interactionRegistry, TypeAdapterRegistry adapterRegistry, RuntimeSupervisor runtimeSupervisor) { + super(middlewareRegistry, implementationRegistry, interactionRegistry, adapterRegistry, runtimeSupervisor); } @Override public void onEvent(Context context) { CommandAutoCompleteInteractionEvent event = (CommandAutoCompleteInteractionEvent) context.getEvent(); Optional optionalAutoComplete = interactionRegistry.getAutoCompletes().stream() - .filter(it -> it.getCommandNames().contains(event.getFullCommandName())) + .filter(it -> it.getCommandNames().stream().anyMatch(name -> event.getFullCommandName().startsWith(name))) .findFirst(); if (optionalAutoComplete.isEmpty()) { @@ -53,7 +61,7 @@ public void onEvent(Context context) { log.debug("Input matches auto complete: {}", autoComplete.getDefinitionId()); log.info("Executing auto complete {} for user {}", autoComplete.getMethod().getName(), event.getMember()); try { - autoComplete.getMethod().invoke(runtimeSupervisor.newRuntime(autoComplete).getInstance(), new AutoCompleteEvent(context)); + autoComplete.getMethod().invoke(runtimeSupervisor.newRuntime(autoComplete).getInstance(), new AutoCompleteEvent(context, interactionRegistry)); } catch (Exception exception) { throw new IllegalStateException("Auto complete execution failed!", exception); } diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/autocomplete/AutoCompleteEvent.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/autocomplete/AutoCompleteEvent.java index b4584161e..425738cdb 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/autocomplete/AutoCompleteEvent.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/autocomplete/AutoCompleteEvent.java @@ -2,7 +2,7 @@ import com.github.kaktushose.jda.commands.dispatching.interactions.Context; import com.github.kaktushose.jda.commands.dispatching.interactions.GenericEvent; -import com.github.kaktushose.jda.commands.reflect.interactions.AutoCompleteDefinition; +import com.github.kaktushose.jda.commands.reflect.InteractionRegistry; import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent; import net.dv8tion.jda.api.interactions.commands.Command; import net.dv8tion.jda.api.interactions.commands.CommandAutoCompleteInteraction; @@ -21,12 +21,12 @@ * @see GenericEvent * @since 4.0.0 */ -public class AutoCompleteEvent extends GenericEvent { +public class AutoCompleteEvent extends GenericEvent { private final CommandAutoCompleteInteractionEvent event; - protected AutoCompleteEvent(Context context) { - super(context); + protected AutoCompleteEvent(Context context, InteractionRegistry interactionRegistry) { + super(context, interactionRegistry); event = (CommandAutoCompleteInteractionEvent) context.getEvent(); } diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/commands/CommandDispatcher.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/commands/CommandDispatcher.java index 62f9288fa..8df5ef346 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/commands/CommandDispatcher.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/commands/CommandDispatcher.java @@ -1,11 +1,14 @@ package com.github.kaktushose.jda.commands.dispatching.interactions.commands; -import com.github.kaktushose.jda.commands.JDACommands; import com.github.kaktushose.jda.commands.dispatching.RuntimeSupervisor; +import com.github.kaktushose.jda.commands.dispatching.adapter.TypeAdapterRegistry; import com.github.kaktushose.jda.commands.dispatching.interactions.Context; import com.github.kaktushose.jda.commands.dispatching.interactions.GenericDispatcher; +import com.github.kaktushose.jda.commands.dispatching.middleware.MiddlewareRegistry; import com.github.kaktushose.jda.commands.dispatching.reply.ReplyContext; import com.github.kaktushose.jda.commands.embeds.ErrorMessageFactory; +import com.github.kaktushose.jda.commands.reflect.ImplementationRegistry; +import com.github.kaktushose.jda.commands.reflect.InteractionRegistry; import com.github.kaktushose.jda.commands.reflect.interactions.commands.GenericCommandDefinition; import com.github.kaktushose.jda.commands.reflect.interactions.commands.SlashCommandDefinition; import net.dv8tion.jda.api.events.interaction.command.GenericCommandInteractionEvent; @@ -31,12 +34,16 @@ public class CommandDispatcher extends GenericDispatcher { private static final Logger log = LoggerFactory.getLogger(CommandDispatcher.class); /** - * Constructs a new GenericDispatcher. + * Constructs a new CommandDispatcher. * - * @param jdaCommands the corresponding {@link JDACommands} instance. + * @param middlewareRegistry + * @param implementationRegistry + * @param interactionRegistry + * @param adapterRegistry + * @param runtimeSupervisor */ - public CommandDispatcher(JDACommands jdaCommands) { - super(jdaCommands); + public CommandDispatcher(MiddlewareRegistry middlewareRegistry, ImplementationRegistry implementationRegistry, InteractionRegistry interactionRegistry, TypeAdapterRegistry adapterRegistry, RuntimeSupervisor runtimeSupervisor) { + super(middlewareRegistry, implementationRegistry, interactionRegistry, adapterRegistry, runtimeSupervisor); } @Override @@ -68,10 +75,10 @@ public void onEvent(Context context) { Map options = slashContext.getOptionsAsMap(); List parameters = new ArrayList<>(); slashCommand.getActualParameters().forEach(param -> { - if (!options.containsKey(param.getName())) { + if (!options.containsKey(param.name())) { return; } - parameters.add(options.get(param.getName()).getAsString()); + parameters.add(options.get(param.name()).getAsString()); }); slashContext.setInput(parameters.toArray(new String[]{})); @@ -81,11 +88,12 @@ public void onEvent(Context context) { } arguments = slashContext.getArguments(); + arguments.addFirst(new CommandEvent(context, interactionRegistry)); } else { - arguments = new ArrayList<>() {{ - add(new CommandEvent(context)); - add(((GenericContextInteractionEvent) event).getTarget()); - }}; + arguments = List.of( + new CommandEvent(context, interactionRegistry), + ((GenericContextInteractionEvent) event).getTarget() + ); } executeMiddlewares(context); diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/commands/CommandEvent.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/commands/CommandEvent.java index 85d5b71f6..80537979a 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/commands/CommandEvent.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/commands/CommandEvent.java @@ -4,7 +4,7 @@ import com.github.kaktushose.jda.commands.dispatching.interactions.GenericEvent; import com.github.kaktushose.jda.commands.dispatching.reply.ModalReplyable; import com.github.kaktushose.jda.commands.dispatching.reply.ReplyContext; -import com.github.kaktushose.jda.commands.reflect.interactions.commands.GenericCommandDefinition; +import com.github.kaktushose.jda.commands.reflect.InteractionRegistry; import org.jetbrains.annotations.NotNull; /** @@ -15,7 +15,7 @@ * @see GenericEvent * @since 4.0.0 */ -public class CommandEvent extends GenericEvent implements ModalReplyable { +public class CommandEvent extends GenericEvent implements ModalReplyable { private final ReplyContext replyContext; @@ -24,8 +24,8 @@ public class CommandEvent extends GenericEvent impleme * * @param context the underlying {@link Context} */ - public CommandEvent(@NotNull Context context) { - super(context); + public CommandEvent(@NotNull Context context, InteractionRegistry interactionRegistry) { + super(context, interactionRegistry); replyContext = new ReplyContext(context); } diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/commands/SlashCommandContext.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/commands/SlashCommandContext.java index f0fee96e1..68bfa9c87 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/commands/SlashCommandContext.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/commands/SlashCommandContext.java @@ -1,7 +1,8 @@ package com.github.kaktushose.jda.commands.dispatching.interactions.commands; -import com.github.kaktushose.jda.commands.JDACommands; import com.github.kaktushose.jda.commands.dispatching.interactions.Context; +import com.github.kaktushose.jda.commands.reflect.ImplementationRegistry; +import com.github.kaktushose.jda.commands.reflect.InteractionRegistry; import com.github.kaktushose.jda.commands.reflect.interactions.commands.SlashCommandDefinition; import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import net.dv8tion.jda.api.interactions.commands.OptionMapping; @@ -27,11 +28,10 @@ public class SlashCommandContext extends Context { /** * Constructs a new CommandContext. * - * @param event the corresponding {@link SlashCommandInteractionEvent} - * @param jdaCommands the corresponding {@link JDACommands} instance + * @param event the corresponding {@link SlashCommandInteractionEvent} */ - public SlashCommandContext(SlashCommandInteractionEvent event, JDACommands jdaCommands) { - super(event, jdaCommands); + public SlashCommandContext(SlashCommandInteractionEvent event, InteractionRegistry interactionRegistry, ImplementationRegistry implementationRegistry) { + super(event, interactionRegistry, implementationRegistry); setOptions(event.getOptions()); } diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/components/ComponentDispatcher.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/components/ComponentDispatcher.java index ebb26ab1e..c6f14199f 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/components/ComponentDispatcher.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/components/ComponentDispatcher.java @@ -1,11 +1,14 @@ package com.github.kaktushose.jda.commands.dispatching.interactions.components; -import com.github.kaktushose.jda.commands.JDACommands; import com.github.kaktushose.jda.commands.dispatching.RuntimeSupervisor; +import com.github.kaktushose.jda.commands.dispatching.adapter.TypeAdapterRegistry; import com.github.kaktushose.jda.commands.dispatching.interactions.Context; import com.github.kaktushose.jda.commands.dispatching.interactions.GenericDispatcher; +import com.github.kaktushose.jda.commands.dispatching.middleware.MiddlewareRegistry; import com.github.kaktushose.jda.commands.dispatching.reply.ReplyContext; import com.github.kaktushose.jda.commands.embeds.ErrorMessageFactory; +import com.github.kaktushose.jda.commands.reflect.ImplementationRegistry; +import com.github.kaktushose.jda.commands.reflect.InteractionRegistry; import com.github.kaktushose.jda.commands.reflect.interactions.CustomId; import com.github.kaktushose.jda.commands.reflect.interactions.EphemeralInteractionDefinition; import com.github.kaktushose.jda.commands.reflect.interactions.components.ButtonDefinition; @@ -34,10 +37,14 @@ public class ComponentDispatcher extends GenericDispatcher { /** * Constructs a new ComponentDispatcher. * - * @param jdaCommands the corresponding {@link JDACommands} instance. + * @param middlewareRegistry + * @param implementationRegistry + * @param interactionRegistry + * @param adapterRegistry + * @param runtimeSupervisor */ - public ComponentDispatcher(JDACommands jdaCommands) { - super(jdaCommands); + public ComponentDispatcher(MiddlewareRegistry middlewareRegistry, ImplementationRegistry implementationRegistry, InteractionRegistry interactionRegistry, TypeAdapterRegistry adapterRegistry, RuntimeSupervisor runtimeSupervisor) { + super(middlewareRegistry, implementationRegistry, interactionRegistry, adapterRegistry, runtimeSupervisor); } @Override @@ -90,11 +97,11 @@ public void onEvent(Context context) { try { Class clazz = component.getClass(); if (EntitySelectMenuDefinition.class.isAssignableFrom(clazz)) { - component.getMethod().invoke(runtime.getInstance(), new ComponentEvent(context), ((EntitySelectInteractionEvent) event).getMentions()); + component.getMethod().invoke(runtime.getInstance(), new ComponentEvent(context, interactionRegistry), ((EntitySelectInteractionEvent) event).getMentions()); } else if (StringSelectMenuDefinition.class.isAssignableFrom(clazz)) { - component.getMethod().invoke(runtime.getInstance(), new ComponentEvent(context), ((StringSelectInteractionEvent) event).getValues()); + component.getMethod().invoke(runtime.getInstance(), new ComponentEvent(context, interactionRegistry), ((StringSelectInteractionEvent) event).getValues()); } else if (ButtonDefinition.class.isAssignableFrom(clazz)) { - component.getMethod().invoke(runtime.getInstance(), new ComponentEvent(context)); + component.getMethod().invoke(runtime.getInstance(), new ComponentEvent(context, interactionRegistry)); } else { throw new IllegalStateException("Unknown component type! Please report this error the the devs of jda-commands."); } diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/components/ComponentEvent.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/components/ComponentEvent.java index 854dfca6d..3b742398e 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/components/ComponentEvent.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/components/ComponentEvent.java @@ -4,7 +4,7 @@ import com.github.kaktushose.jda.commands.dispatching.interactions.GenericEvent; import com.github.kaktushose.jda.commands.dispatching.reply.ModalReplyable; import com.github.kaktushose.jda.commands.dispatching.reply.ReplyContext; -import com.github.kaktushose.jda.commands.reflect.interactions.components.GenericComponentDefinition; +import com.github.kaktushose.jda.commands.reflect.InteractionRegistry; import net.dv8tion.jda.api.utils.messages.MessageCreateData; import org.jetbrains.annotations.NotNull; @@ -18,7 +18,7 @@ * @see GenericEvent * @since 4.0.0 */ -public class ComponentEvent extends GenericEvent implements ModalReplyable { +public class ComponentEvent extends GenericEvent implements ModalReplyable { private final ReplyContext replyContext; @@ -27,8 +27,8 @@ public class ComponentEvent extends GenericEvent imp * * @param context the underlying {@link Context} */ - public ComponentEvent(@NotNull Context context) { - super(context); + public ComponentEvent(@NotNull Context context, InteractionRegistry interactionRegistry) { + super(context, interactionRegistry); replyContext = new ReplyContext(context); } diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/modals/ModalDispatcher.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/modals/ModalDispatcher.java index 6a89a32d8..533159f74 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/modals/ModalDispatcher.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/modals/ModalDispatcher.java @@ -1,11 +1,14 @@ package com.github.kaktushose.jda.commands.dispatching.interactions.modals; -import com.github.kaktushose.jda.commands.JDACommands; import com.github.kaktushose.jda.commands.dispatching.RuntimeSupervisor; +import com.github.kaktushose.jda.commands.dispatching.adapter.TypeAdapterRegistry; import com.github.kaktushose.jda.commands.dispatching.interactions.Context; import com.github.kaktushose.jda.commands.dispatching.interactions.GenericDispatcher; +import com.github.kaktushose.jda.commands.dispatching.middleware.MiddlewareRegistry; import com.github.kaktushose.jda.commands.dispatching.reply.ReplyContext; import com.github.kaktushose.jda.commands.embeds.ErrorMessageFactory; +import com.github.kaktushose.jda.commands.reflect.ImplementationRegistry; +import com.github.kaktushose.jda.commands.reflect.InteractionRegistry; import com.github.kaktushose.jda.commands.reflect.interactions.CustomId; import com.github.kaktushose.jda.commands.reflect.interactions.ModalDefinition; import net.dv8tion.jda.api.events.interaction.ModalInteractionEvent; @@ -31,10 +34,14 @@ public class ModalDispatcher extends GenericDispatcher { /** * Constructs a new ModalDispatcher. * - * @param jdaCommands the corresponding {@link JDACommands} instance. + * @param middlewareRegistry + * @param implementationRegistry + * @param interactionRegistry + * @param adapterRegistry + * @param runtimeSupervisor */ - public ModalDispatcher(JDACommands jdaCommands) { - super(jdaCommands); + public ModalDispatcher(MiddlewareRegistry middlewareRegistry, ImplementationRegistry implementationRegistry, InteractionRegistry interactionRegistry, TypeAdapterRegistry adapterRegistry, RuntimeSupervisor runtimeSupervisor) { + super(middlewareRegistry, implementationRegistry, interactionRegistry, adapterRegistry, runtimeSupervisor); } @Override @@ -83,7 +90,7 @@ public void onEvent(Context context) { try { context.setRuntime(runtime); List arguments = new ArrayList<>(); - arguments.add(new ModalEvent(context)); + arguments.add(new ModalEvent(context, interactionRegistry)); arguments.addAll(event.getValues().stream().map(ModalMapping::getAsString).collect(Collectors.toList())); modal.getMethod().invoke(runtime.getInstance(), arguments.toArray()); } catch (Exception exception) { diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/modals/ModalEvent.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/modals/ModalEvent.java index b16a100d6..077007b3d 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/modals/ModalEvent.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/modals/ModalEvent.java @@ -4,7 +4,7 @@ import com.github.kaktushose.jda.commands.dispatching.interactions.GenericEvent; import com.github.kaktushose.jda.commands.dispatching.reply.ReplyContext; import com.github.kaktushose.jda.commands.dispatching.reply.Replyable; -import com.github.kaktushose.jda.commands.reflect.interactions.ModalDefinition; +import com.github.kaktushose.jda.commands.reflect.InteractionRegistry; import org.jetbrains.annotations.NotNull; /** @@ -15,12 +15,12 @@ * @see GenericEvent * @since 4.0.0 */ -public class ModalEvent extends GenericEvent implements Replyable { +public class ModalEvent extends GenericEvent implements Replyable { private final ReplyContext replyContext; - protected ModalEvent(Context context) { - super(context); + protected ModalEvent(Context context, InteractionRegistry interactionRegistry) { + super(context, interactionRegistry); replyContext = new ReplyContext(context); } diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/middleware/Middleware.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/middleware/Middleware.java index 7e58022db..3660b336b 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/middleware/Middleware.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/middleware/Middleware.java @@ -3,6 +3,8 @@ import com.github.kaktushose.jda.commands.dispatching.interactions.Context; import net.dv8tion.jda.api.utils.messages.MessageCreateData; +import java.util.function.Consumer; + /** * Middlewares run just before an interaction event gets dispatched. They are used to perform additional checks or add * more info the {@link Context}. Either register them at the {@link MiddlewareRegistry} or use the @@ -13,7 +15,8 @@ * @see MiddlewareRegistry * @since 4.0.0 */ -public interface Middleware { +@FunctionalInterface +public interface Middleware extends Consumer { /** * Executes this middleware with the given {@link Context}. Use {@link Context#setCancelled(MessageCreateData)} @@ -21,6 +24,6 @@ public interface Middleware { * * @param context the {@link Context} of the current interaction event */ - void execute(Context context); + void accept(Context context); } diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/middleware/MiddlewareRegistry.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/middleware/MiddlewareRegistry.java index bcc3c7288..0cf968558 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/middleware/MiddlewareRegistry.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/middleware/MiddlewareRegistry.java @@ -22,7 +22,6 @@ public class MiddlewareRegistry { /** * Constructs a new MiddlewareRegistry. - * */ public MiddlewareRegistry() { middlewares = new HashMap<>(); @@ -37,8 +36,8 @@ public MiddlewareRegistry() { /** * Register {@link Middleware Middleware(s)} with the given {@link Priority}. * - * @param priority the {@link Priority} to register the {@link Middleware Middleware(s)} with - * @param first the first {@link Middleware} to register + * @param priority the {@link Priority} to register the {@link Middleware Middleware(s)} with + * @param first the first {@link Middleware} to register * @param middlewares additional {@link Middleware Middlewares} to register * @return this instance for fluent interface */ @@ -50,7 +49,7 @@ public MiddlewareRegistry register(Priority priority, Middleware first, Middlewa /** * Register {@link Middleware Middleware(s)} with the given {@link Priority}. * - * @param priority the {@link Priority} to register the {@link Middleware Middleware(s)} with + * @param priority the {@link Priority} to register the {@link Middleware Middleware(s)} with * @param middlewares the {@link Middleware Middleware(s)} to register * @return this instance for fluent interface */ @@ -63,8 +62,8 @@ public MiddlewareRegistry register(Priority priority, Collection mid /** * Unregister {@link Middleware Middleware(s)} with the given {@link Priority}. * - * @param priority the {@link Priority} to unregister the {@link Middleware Middleware(s)} with - * @param first the first {@link Middleware} to unregister + * @param priority the {@link Priority} to unregister the {@link Middleware Middleware(s)} with + * @param first the first {@link Middleware} to unregister * @param middlewares additional {@link Middleware Middlewares} to unregister * @return this instance for fluent interface */ @@ -76,7 +75,7 @@ public MiddlewareRegistry unregister(Priority priority, Middleware first, Middle /** * Unregister {@link Middleware Middleware(s)} with the given {@link Priority}. * - * @param priority the {@link Priority} to unregister the {@link Middleware Middleware(s)} with + * @param priority the {@link Priority} to unregister the {@link Middleware Middleware(s)} with * @param middlewares the {@link Middleware Middleware(s)} to unregister * @return this instance for fluent interface */ diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/middleware/impl/ConstraintMiddleware.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/middleware/impl/ConstraintMiddleware.java index 16e260cd6..c58799f31 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/middleware/impl/ConstraintMiddleware.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/middleware/impl/ConstraintMiddleware.java @@ -32,7 +32,7 @@ public class ConstraintMiddleware implements Middleware { * @param ctx the {@link Context} to filter */ @Override - public void execute(@NotNull Context ctx) { + public void accept(@NotNull Context ctx) { if (!SlashCommandInteractionEvent.class.isAssignableFrom(ctx.getEvent().getClass())) { return; } @@ -44,10 +44,10 @@ public void execute(@NotNull Context ctx) { for (int i = 1; i < arguments.size(); i++) { Object argument = arguments.get(i); ParameterDefinition parameter = parameters.get(i); - for (ConstraintDefinition constraint : parameter.getConstraints()) { - log.debug("Found constraint {} for parameter {}", constraint, parameter.getType().getName()); + for (ConstraintDefinition constraint : parameter.constraints()) { + log.debug("Found constraint {} for parameter {}", constraint, parameter.type().getName()); - boolean validated = constraint.getValidator().validate(argument, constraint.getAnnotation(), context); + boolean validated = constraint.validator().apply(argument, constraint.annotation(), context); if (!validated) { context.setCancelled( diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/middleware/impl/CooldownMiddleware.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/middleware/impl/CooldownMiddleware.java index ef3542910..638b4aa1a 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/middleware/impl/CooldownMiddleware.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/middleware/impl/CooldownMiddleware.java @@ -38,7 +38,7 @@ public CooldownMiddleware() { * @param context the {@link Context} to filter */ @Override - public void execute(@NotNull Context context) { + public void accept(@NotNull Context context) { if (!SlashCommandInteractionEvent.class.isAssignableFrom(context.getEvent().getClass())) { return; } @@ -68,7 +68,7 @@ public void execute(@NotNull Context context) { CooldownDefinition cooldown = command.getCooldown(); long startTime = System.currentTimeMillis(); - long duration = cooldown.getTimeUnit().toMillis(cooldown.getDelay()); + long duration = cooldown.timeUnit().toMillis(cooldown.delay()); activeCooldowns.get(id).add(new CooldownEntry(command, startTime, duration)); log.debug("Added new cooldown entry for this user"); } diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/middleware/impl/PermissionsMiddleware.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/middleware/impl/PermissionsMiddleware.java index d82f73702..13209a6ef 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/middleware/impl/PermissionsMiddleware.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/middleware/impl/PermissionsMiddleware.java @@ -34,7 +34,7 @@ public class PermissionsMiddleware implements Middleware { * @param context the {@link Context} to filter */ @Override - public void execute(@NotNull Context context) { + public void accept(@NotNull Context context) { log.debug("Checking permissions..."); PermissionsProvider provider = context.getImplementationRegistry().getPermissionsProvider(); GenericInteractionCreateEvent event = context.getEvent(); diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/reply/ModalReplyable.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/reply/ModalReplyable.java index 06b5f0f42..c49e6f403 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/reply/ModalReplyable.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/reply/ModalReplyable.java @@ -29,7 +29,7 @@ default void replyModal(String modal) { ); } - ModalDefinition modalDefinition = context.getJdaCommands().getInteractionRegistry().getModals().stream() + ModalDefinition modalDefinition = context.getInteractionRegistry().getModals().stream() .filter(it -> it.getDefinitionId().equals(String.format("%s%s", context.getInteractionDefinition().getMethod().getDeclaringClass().getSimpleName(), modal))) .findFirst() .orElseThrow(() -> new IllegalArgumentException("Unknown Modal")); diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/reply/Replyable.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/reply/Replyable.java index e844403c6..1faa089bd 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/reply/Replyable.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/reply/Replyable.java @@ -146,7 +146,7 @@ default void reply(@NotNull EmbedDTO embedDTO, @Nullable Consumer succe /** * Adds an {@link ActionRow} to the reply and adds the passed {@link Component Components} to it. - * For buttons, they must be defined in the same + * For buttonContainers, they must be defined in the same * {@link com.github.kaktushose.jda.commands.annotations.interactions.Interaction Interaction} as the referring * {@link SlashCommand Command}. * @@ -159,15 +159,15 @@ default Replyable with(@NotNull Component... components) { for (Component component : components) { if (component instanceof Buttons) { Buttons buttons = (Buttons) component; - buttons.getButtonContainer().forEach(container -> { - String id = String.format("%s%s", context.getInteractionDefinition().getMethod().getDeclaringClass().getSimpleName(), container.getName()); - context.getJdaCommands().getInteractionRegistry().getButtons() + buttons.buttonContainers().forEach(container -> { + String id = String.format("%s%s", context.getInteractionDefinition().getMethod().getDeclaringClass().getSimpleName(), container.name()); + context.getInteractionRegistry().getButtons() .stream() .filter(it -> it.getDefinitionId().equals(id)) .findFirst() .map(it -> { - Button jdaButton = it.toButton().withDisabled(!container.isEnabled()); - //only assign ids to non-link buttons + Button jdaButton = it.toButton().withDisabled(!container.enabled()); + //only assign ids to non-link buttonContainers if (jdaButton.getUrl() == null) { jdaButton = jdaButton.withId(it.createCustomId(context.getRuntime().getRuntimeId())); } @@ -177,9 +177,9 @@ default Replyable with(@NotNull Component... components) { } if (component instanceof SelectMenus) { SelectMenus menus = (SelectMenus) component; - menus.getSelectMenuContainer().forEach(container -> { + menus.selectMenuContainers().forEach(container -> { String id = String.format("%s%s", context.getInteractionDefinition().getMethod().getDeclaringClass().getSimpleName(), container.getName()); - context.getJdaCommands().getInteractionRegistry().getSelectMenus() + context.getInteractionRegistry().getSelectMenus() .stream() .filter(it -> it.getDefinitionId().equals(id)) .findFirst().map(it -> it.toSelectMenu(context.getRuntime().getRuntimeId(), container.isEnabled())) @@ -212,12 +212,12 @@ default KeyValueStore kv() { /** * Adds an {@link ActionRow} to the reply and adds the passed {@link Component Components} to it. - * The buttons must be defined in the same + * The buttonContainers must be defined in the same * {@link com.github.kaktushose.jda.commands.annotations.interactions.Interaction Interaction} as the referring - * {@link SlashCommand Command}. This will enable all buttons. To add - * disabled buttons, use {@link #with(Component...)}. + * {@link SlashCommand Command}. This will enable all buttonContainers. To add + * disabled buttonContainers, use {@link #with(Component...)}. * - * @param buttons the id of the buttons to add + * @param buttons the id of the buttonContainers to add * @return the current instance for fluent interface */ default Replyable withButtons(@NotNull String... buttons) { diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/reply/components/Buttons.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/reply/components/Buttons.java index d5e89e8ff..ddc8e0be0 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/reply/components/Buttons.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/reply/components/Buttons.java @@ -4,28 +4,27 @@ import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.List; /** - * {@link Component} implementation for buttons. This class can be used to add + * {@link Component} implementation for buttonContainers. This class can be used to add * {@link com.github.kaktushose.jda.commands.annotations.interactions.Button Buttons} to messages while defining their * state (enabled or disabled). * * @see Replyable#with(Component...) * @since 2.3.0 */ -public class Buttons implements Component { +public record Buttons(Collection buttonContainers) implements Component { - private final Collection buttons; - - private Buttons(Collection buttons) { - this.buttons = buttons; + public Buttons(Collection buttonContainers) { + this.buttonContainers = Collections.unmodifiableCollection(buttonContainers); } /** - * Add the buttons with the given ids to the reply message as enabled. + * Add the buttonContainers with the given ids to the reply message as enabled. * - * @param buttons the id of the buttons to add + * @param buttons the id of the buttonContainers to add * @return instance of this class used inside the * {@link com.github.kaktushose.jda.commands.dispatching.reply.ReplyContext} */ @@ -34,9 +33,9 @@ public static Buttons enabled(String... buttons) { } /** - * Add the buttons with the given ids to the reply message as disabled. + * Add the buttonContainers with the given ids to the reply message as disabled. * - * @param buttons the id of the buttons to add + * @param buttons the id of the buttonContainers to add * @return instance of this class used inside the * {@link com.github.kaktushose.jda.commands.dispatching.reply.ReplyContext} */ @@ -52,43 +51,9 @@ private static Buttons build(boolean enabled, String... buttons) { return new Buttons(result); } - /** - * Gets the {@link ButtonContainer}. - * - * @return the {@link ButtonContainer} - */ - public Collection getButtonContainer() { - return buttons; - } - /** * Contains information about a single {@link com.github.kaktushose.jda.commands.annotations.interactions.Button Button}. */ - public static class ButtonContainer { - private final String name; - private final boolean enabled; - - private ButtonContainer(String name, boolean enabled) { - this.name = name; - this.enabled = enabled; - } - - /** - * Gets the button id. - * - * @return the button id - */ - public String getName() { - return name; - } - - /** - * Whether the button is enabled or not. - * - * @return {@code true} if the button is enabled - */ - public boolean isEnabled() { - return enabled; - } + public record ButtonContainer(String name, boolean enabled) { } } diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/reply/components/Component.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/reply/components/Component.java index 21b2fc5a9..3e8ec4c33 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/reply/components/Component.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/reply/components/Component.java @@ -10,5 +10,5 @@ * @see SelectMenus * @since 2.3.0 */ -public interface Component { +public sealed interface Component permits Buttons, SelectMenus { } diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/reply/components/SelectMenus.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/reply/components/SelectMenus.java index 29db541ff..1ffac30c4 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/reply/components/SelectMenus.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/reply/components/SelectMenus.java @@ -4,6 +4,7 @@ import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.List; /** @@ -15,12 +16,10 @@ * @see Replyable#with(Component...) * @since 2.3.0 */ -public class SelectMenus implements Component { +public record SelectMenus(Collection selectMenuContainers) implements Component { - private final Collection selectMenus; - - private SelectMenus(Collection selectMenus) { - this.selectMenus = selectMenus; + public SelectMenus(Collection selectMenuContainers) { + this.selectMenuContainers = Collections.unmodifiableCollection(selectMenuContainers); } /** @@ -53,15 +52,6 @@ private static SelectMenus build(boolean enabled, String... menus) { return new SelectMenus(result); } - /** - * Gets the {@link SelectMenuContainer}. - * - * @return the {@link SelectMenuContainer} - */ - public Collection getSelectMenuContainer() { - return selectMenus; - } - /** * Contains information about a single select menu (either StringSelectMenu or EntitySelectMenu). */ diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/validation/Validator.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/validation/Validator.java index 365bebb46..7734c2a5f 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/validation/Validator.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/validation/Validator.java @@ -10,6 +10,7 @@ * @see com.github.kaktushose.jda.commands.annotations.constraints.Constraint Constraint * @since 2.0.0 */ +@FunctionalInterface public interface Validator { /** @@ -20,6 +21,6 @@ public interface Validator { * @param context the corresponding {@link Context} * @return {@code true} if the argument passes the constraints */ - boolean validate(@NotNull Object argument, @NotNull Object annotation, @NotNull Context context); + boolean apply(@NotNull Object argument, @NotNull Object annotation, @NotNull Context context); } diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/validation/impl/MaximumValidator.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/validation/impl/MaximumValidator.java index cbd98f296..6e7902469 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/validation/impl/MaximumValidator.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/validation/impl/MaximumValidator.java @@ -22,7 +22,7 @@ public class MaximumValidator implements Validator { * @return {@code true} if the argument is a number whose value is lower or equal to the specified maximum */ @Override - public boolean validate(@NotNull Object argument, @NotNull Object annotation, @NotNull Context context) { + public boolean apply(@NotNull Object argument, @NotNull Object annotation, @NotNull Context context) { Max max = (Max) annotation; return ((Number) argument).longValue() <= max.value(); } diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/validation/impl/MinimumValidator.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/validation/impl/MinimumValidator.java index 4aebc7d20..2f7982bb3 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/validation/impl/MinimumValidator.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/validation/impl/MinimumValidator.java @@ -23,7 +23,7 @@ public class MinimumValidator implements Validator { * @return {@code true} if the argument is a number whose value is greater or equal to the specified minimum */ @Override - public boolean validate(@NotNull Object argument, @NotNull Object annotation, @NotNull Context context) { + public boolean apply(@NotNull Object argument, @NotNull Object annotation, @NotNull Context context) { Min min = (Min) annotation; return ((Number) argument).longValue() >= min.value(); } diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/validation/impl/NotPermissionValidator.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/validation/impl/NotPermissionValidator.java index 027c47532..541b0236e 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/validation/impl/NotPermissionValidator.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/validation/impl/NotPermissionValidator.java @@ -29,7 +29,7 @@ public class NotPermissionValidator implements Validator { * permission */ @Override - public boolean validate(@NotNull Object argument, @NotNull Object annotation, @NotNull Context context) { + public boolean apply(@NotNull Object argument, @NotNull Object annotation, @NotNull Context context) { Set permissions = new HashSet<>(); NotPerm perm = (NotPerm) annotation; diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/validation/impl/NotRoleValidator.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/validation/impl/NotRoleValidator.java index 155ba5ff4..635f57d8a 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/validation/impl/NotRoleValidator.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/validation/impl/NotRoleValidator.java @@ -27,10 +27,10 @@ public class NotRoleValidator implements Validator { * @return {@code true} if the argument is a user or member that doesn't have the specified guild role */ @Override - public boolean validate(@NotNull Object argument, @NotNull Object annotation, @NotNull Context context) { + public boolean apply(@NotNull Object argument, @NotNull Object annotation, @NotNull Context context) { NotRole roleAnnotation = (NotRole) annotation; - Optional optional = new RoleAdapter().parse(roleAnnotation.value(), context); + Optional optional = new RoleAdapter().apply(roleAnnotation.value(), context); Member member = (Member) argument; return optional.filter(role -> member.getRoles().contains(role)).isEmpty(); diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/validation/impl/NotUserValidator.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/validation/impl/NotUserValidator.java index 4f6119cfc..8f10a0af9 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/validation/impl/NotUserValidator.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/validation/impl/NotUserValidator.java @@ -26,10 +26,10 @@ public class NotUserValidator implements Validator { * @return {@code true} if the argument isn't the specified user or member */ @Override - public boolean validate(@NotNull Object argument, @NotNull Object annotation, @NotNull Context context) { + public boolean apply(@NotNull Object argument, @NotNull Object annotation, @NotNull Context context) { Member member = (Member) argument; NotUser user = (NotUser) annotation; - Optional optional = new MemberAdapter().parse(user.value(), context); + Optional optional = new MemberAdapter().apply(user.value(), context); return optional.filter(member::equals).isEmpty(); } } diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/validation/impl/PermissionValidator.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/validation/impl/PermissionValidator.java index a3d24e8fe..b2bdef7ac 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/validation/impl/PermissionValidator.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/validation/impl/PermissionValidator.java @@ -29,7 +29,7 @@ public class PermissionValidator implements Validator { * permission */ @Override - public boolean validate(@NotNull Object argument, @NotNull Object annotation, @NotNull Context context) { + public boolean apply(@NotNull Object argument, @NotNull Object annotation, @NotNull Context context) { Set permissions = new HashSet<>(); Perm perm = (Perm) annotation; try { diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/validation/impl/RoleValidator.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/validation/impl/RoleValidator.java index de7134e92..517bdac63 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/validation/impl/RoleValidator.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/validation/impl/RoleValidator.java @@ -27,11 +27,11 @@ public class RoleValidator implements Validator { * @return {@code true} if the argument is a user or member that has the specified guild role */ @Override - public boolean validate(@NotNull Object argument, @NotNull Object annotation, @NotNull Context context) { + public boolean apply(@NotNull Object argument, @NotNull Object annotation, @NotNull Context context) { com.github.kaktushose.jda.commands.annotations.constraints.Role roleAnnotation = (com.github.kaktushose.jda.commands.annotations.constraints.Role) annotation; - Optional optional = new RoleAdapter().parse(roleAnnotation.value(), context); + Optional optional = new RoleAdapter().apply(roleAnnotation.value(), context); Member member = (Member) argument; return optional.filter(role -> member.getRoles().contains(role)).isPresent(); diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/validation/impl/UserValidator.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/validation/impl/UserValidator.java index 1d026e2ad..2df8a190e 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/validation/impl/UserValidator.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/validation/impl/UserValidator.java @@ -26,10 +26,10 @@ public class UserValidator implements Validator { * @return {@code true} if the argument is the specified user or member */ @Override - public boolean validate(@NotNull Object argument, @NotNull Object annotation, @NotNull Context context) { + public boolean apply(@NotNull Object argument, @NotNull Object annotation, @NotNull Context context) { Member member = (Member) argument; User user = (User) annotation; - Optional optional = new MemberAdapter().parse(user.value(), context); + Optional optional = new MemberAdapter().apply(user.value(), context); return optional.filter(member::equals).isPresent(); } } diff --git a/src/main/java/com/github/kaktushose/jda/commands/embeds/DefaultErrorMessageFactory.java b/src/main/java/com/github/kaktushose/jda/commands/embeds/DefaultErrorMessageFactory.java index bf2962fa5..feed5a152 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/embeds/DefaultErrorMessageFactory.java +++ b/src/main/java/com/github/kaktushose/jda/commands/embeds/DefaultErrorMessageFactory.java @@ -33,10 +33,10 @@ public MessageCreateData getTypeAdaptingFailedMessage(@NotNull SlashCommandConte List arguments = Arrays.asList(context.getInput()); command.getParameters().forEach(parameter -> { - if (CommandEvent.class.isAssignableFrom(parameter.getType())) { + if (CommandEvent.class.isAssignableFrom(parameter.type())) { return; } - String typeName = parameter.getType().getTypeName(); + String typeName = parameter.type().getTypeName(); if (typeName.contains(".")) { typeName = typeName.substring(typeName.lastIndexOf(".") + 1); } @@ -81,7 +81,7 @@ public MessageCreateData getConstraintFailedMessage(@NotNull Context context, @N return new MessageCreateBuilder().setEmbeds(new EmbedBuilder() .setColor(Color.ORANGE) .setTitle("Parameter Error") - .setDescription(String.format("```%s```", constraint.getMessage())) + .setDescription(String.format("```%s```", constraint.message())) .build() ).build(); } diff --git a/src/main/java/com/github/kaktushose/jda/commands/embeds/JsonErrorMessageFactory.java b/src/main/java/com/github/kaktushose/jda/commands/embeds/JsonErrorMessageFactory.java index be7186167..94964f3d9 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/embeds/JsonErrorMessageFactory.java +++ b/src/main/java/com/github/kaktushose/jda/commands/embeds/JsonErrorMessageFactory.java @@ -41,10 +41,10 @@ public MessageCreateData getTypeAdaptingFailedMessage(@NotNull SlashCommandConte List arguments = Arrays.asList(context.getInput()); command.getParameters().forEach(parameter -> { - if (CommandEvent.class.isAssignableFrom(parameter.getType())) { + if (CommandEvent.class.isAssignableFrom(parameter.type())) { return; } - String typeName = parameter.getType().getTypeName(); + String typeName = parameter.type().getTypeName(); if (typeName.contains(".")) { typeName = typeName.substring(typeName.lastIndexOf(".") + 1); } @@ -86,7 +86,7 @@ public MessageCreateData getConstraintFailedMessage(@NotNull Context context, @N return super.getConstraintFailedMessage(context, constraint); } return embedCache.getEmbed("constraintFailed") - .injectValue("message", constraint.getMessage()) + .injectValue("message", constraint.message()) .toMessageCreateData(); } @@ -120,8 +120,8 @@ public MessageCreateData getCommandExecutionFailedMessage(@NotNull Context conte return super.getCommandExecutionFailedMessage(context, exception); } String error = String.format("```The user \"%s\" attempted to execute an \"%s\" interaction at %s, " + - "but a \"%s\" occurred. " + - "Please refer to the logs for further information.```", + "but a \"%s\" occurred. " + + "Please refer to the logs for further information.```", context.getEvent().getUser().toString(), context.getEvent().getInteraction().getType(), new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(System.currentTimeMillis()), diff --git a/src/main/java/com/github/kaktushose/jda/commands/reflect/ConstraintDefinition.java b/src/main/java/com/github/kaktushose/jda/commands/reflect/ConstraintDefinition.java index 002f52ede..91c63ed48 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/reflect/ConstraintDefinition.java +++ b/src/main/java/com/github/kaktushose/jda/commands/reflect/ConstraintDefinition.java @@ -1,7 +1,6 @@ package com.github.kaktushose.jda.commands.reflect; import com.github.kaktushose.jda.commands.dispatching.validation.Validator; -import org.jetbrains.annotations.NotNull; /** * Representation of parameter constraint. @@ -10,56 +9,5 @@ * @see Validator * @since 2.0.0 */ -public class ConstraintDefinition { - - private final Validator validator; - private final String message; - private final Object annotation; - - /** - * Constructs a new ConstraintDefinition. - * - * @param validator the {@link Validator} to use - * @param message the message to display if the constraint fails - * @param annotation an instance of the annotation declaring the constraint - */ - public ConstraintDefinition(@NotNull Validator validator, @NotNull String message, @NotNull Object annotation) { - this.validator = validator; - this.message = message; - this.annotation = annotation; - } - - /** - * Gets the {@link Validator}. - * - * @return the {@link Validator} - */ - public Validator getValidator() { - return validator; - } - - /** - * Gets the message to display if the constraint fails. - * - * @return the message to display if the constraint fails - */ - public String getMessage() { - return message; - } - - /** - * Gets an instance of the annotation declaring the constraint. - * - * @return an instance of the annotation declaring the constraint - */ - public Object getAnnotation() { - return annotation; - } - - @Override - public String toString() { - return "{" + - "validator=" + validator.getClass().getName() + - ", message='" + message + "'}"; - } +public record ConstraintDefinition(Validator validator, String message, Object annotation) { } diff --git a/src/main/java/com/github/kaktushose/jda/commands/reflect/CooldownDefinition.java b/src/main/java/com/github/kaktushose/jda/commands/reflect/CooldownDefinition.java index b5049b85a..646a5ea3d 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/reflect/CooldownDefinition.java +++ b/src/main/java/com/github/kaktushose/jda/commands/reflect/CooldownDefinition.java @@ -12,16 +12,10 @@ * @see Cooldown * @since 2.0.0 */ -public class CooldownDefinition { - - private long delay; - private TimeUnit timeUnit; - - private CooldownDefinition(long delay, TimeUnit timeUnit) { - this.delay = delay; - this.timeUnit = timeUnit; - } - +public record CooldownDefinition( + long delay, + TimeUnit timeUnit +) { /** * Builds a new CooldownDefinition. * @@ -35,59 +29,4 @@ public static CooldownDefinition build(@Nullable Cooldown cooldown) { } return new CooldownDefinition(cooldown.value(), cooldown.timeUnit()); } - - /** - * Sets the corresponding {@link Cooldown} annotation. - * - * @param cooldown the new {@link Cooldown} annotation to use - */ - public void set(@Nullable CooldownDefinition cooldown) { - if (cooldown == null) { - delay = 0; - return; - } - delay = cooldown.delay; - timeUnit = cooldown.timeUnit; - } - - /** - * Gets the cooldown delay. - * - * @return the cooldown delay - */ - public long getDelay() { - return delay; - } - - /** - * Sets the cooldown delay - * - * @param delay the new delay - */ - public void setDelay(long delay) { - this.delay = delay; - } - - /** - * Gets the {@link TimeUnit} of the cooldown. - * - * @return the {@link TimeUnit} of the cooldown - */ - public TimeUnit getTimeUnit() { - return timeUnit; - } - - /** - * Sets the {@link TimeUnit} of the cooldown. - * - * @param timeUnit the new {@link TimeUnit} - */ - public void setTimeUnit(TimeUnit timeUnit) { - this.timeUnit = timeUnit; - } - - @Override - public String toString() { - return "{" + "delay=" + delay + ", timeUnit=" + timeUnit + '}'; - } } diff --git a/src/main/java/com/github/kaktushose/jda/commands/reflect/InteractionControllerDefinition.java b/src/main/java/com/github/kaktushose/jda/commands/reflect/InteractionControllerDefinition.java index d43d0abc7..9f04d4f6b 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/reflect/InteractionControllerDefinition.java +++ b/src/main/java/com/github/kaktushose/jda/commands/reflect/InteractionControllerDefinition.java @@ -5,17 +5,14 @@ import com.github.kaktushose.jda.commands.dependency.DependencyInjector; import com.github.kaktushose.jda.commands.dispatching.validation.ValidatorRegistry; import com.github.kaktushose.jda.commands.reflect.interactions.AutoCompleteDefinition; +import com.github.kaktushose.jda.commands.reflect.interactions.GenericInteractionDefinition; import com.github.kaktushose.jda.commands.reflect.interactions.ModalDefinition; import com.github.kaktushose.jda.commands.reflect.interactions.commands.ContextCommandDefinition; -import com.github.kaktushose.jda.commands.reflect.interactions.commands.GenericCommandDefinition; import com.github.kaktushose.jda.commands.reflect.interactions.commands.SlashCommandDefinition; import com.github.kaktushose.jda.commands.reflect.interactions.components.ButtonDefinition; import com.github.kaktushose.jda.commands.reflect.interactions.components.menus.EntitySelectMenuDefinition; -import com.github.kaktushose.jda.commands.reflect.interactions.components.menus.GenericSelectMenuDefinition; import com.github.kaktushose.jda.commands.reflect.interactions.components.menus.StringSelectMenuDefinition; -import net.dv8tion.jda.api.interactions.commands.Command; import net.dv8tion.jda.api.interactions.commands.localization.LocalizationFunction; -import net.dv8tion.jda.api.interactions.components.selections.SelectMenu; import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -24,33 +21,17 @@ import java.lang.reflect.Method; import java.util.Optional; import java.util.*; -import java.util.stream.Collectors; /** * Representation of an interaction controller. * * @since 2.0.0 */ -public class InteractionControllerDefinition { +public record InteractionControllerDefinition( + Set definitions +) { private static final Logger log = LoggerFactory.getLogger(InteractionControllerDefinition.class); - private final List commands; - private final List buttons; - private final List> selectMenus; - private final List autoCompletes; - private final List modals; - - private InteractionControllerDefinition(List commands, - List buttons, - List> selectMenus, - List autoCompletes, - List modals) { - this.commands = commands; - this.buttons = buttons; - this.selectMenus = selectMenus; - this.autoCompletes = autoCompletes; - this.modals = modals; - } /** * Builds a new ControllerDefinition. @@ -61,197 +42,111 @@ private InteractionControllerDefinition(List commands, * @param localizationFunction the {@link LocalizationFunction} to use * @return an {@link Optional} holding the ControllerDefinition */ - public static Optional build(@NotNull Class interactionClass, - @NotNull ValidatorRegistry validatorRegistry, - @NotNull DependencyInjector dependencyInjector, - @NotNull LocalizationFunction localizationFunction) { + public static InteractionControllerDefinition build(@NotNull Class interactionClass, + @NotNull ValidatorRegistry validatorRegistry, + @NotNull DependencyInjector dependencyInjector, + @NotNull LocalizationFunction localizationFunction) { Interaction interaction = interactionClass.getAnnotation(Interaction.class); - if (!interaction.isActive()) { - log.warn("Interaction class {} is set inactive. Skipping the controller and its commands", interactionClass.getName()); - return Optional.empty(); - } - - List fields = new ArrayList<>(); - for (Field field : interactionClass.getDeclaredFields()) { - if (!field.isAnnotationPresent(Inject.class)) { - continue; - } - fields.add(field); - } + List fields = Arrays.stream(interactionClass.getDeclaredFields()) + .filter(field -> field.isAnnotationPresent(Inject.class)) + .toList(); dependencyInjector.registerDependencies(interactionClass, fields); - final Set permissions = new HashSet<>(); - // index controller level permissions - if (interactionClass.isAnnotationPresent(Permissions.class)) { - Permissions permission = interactionClass.getAnnotation(Permissions.class); - permissions.addAll(Arrays.stream(permission.value()).collect(Collectors.toSet())); - } + Permissions permission = interactionClass.getAnnotation(Permissions.class); + final Set permissions = permission != null + ? Set.of(permission.value()) + : Set.of(); // get controller level cooldown and use it if no command level cooldown is present - CooldownDefinition cooldown = null; - if (interactionClass.isAnnotationPresent(Cooldown.class)) { - cooldown = CooldownDefinition.build(interactionClass.getAnnotation(Cooldown.class)); - } + Cooldown cooldownAnn = interactionClass.getAnnotation(Cooldown.class); + CooldownDefinition cooldown = cooldownAnn != null + ? CooldownDefinition.build(cooldownAnn) + : null; + + + Collection autoCompleteDefinitions = autoCompleteDefinitions(interactionClass); // index interactions - List commands = new ArrayList<>(); - List buttons = new ArrayList<>(); - List> selectMenus = new ArrayList<>(); - List autoCompletes = new ArrayList<>(); - List modals = new ArrayList<>(); - for (Method method : interactionClass.getDeclaredMethods()) { + Set interactionDefinitions = interactionDefinitions( + interactionClass, + validatorRegistry, + localizationFunction, + interaction, + permissions, + cooldown, + autoCompleteDefinitions + ); + + // validate auto completes + List commandDefinitions = interactionDefinitions.stream() + .filter(SlashCommandDefinition.class::isInstance) + .map(SlashCommandDefinition.class::cast) + .toList(); + + autoCompleteDefinitions.stream() + .map(AutoCompleteDefinition::getCommandNames) + .flatMap(Collection::stream) + .filter(name -> commandDefinitions.stream().noneMatch(command -> command.getName().startsWith(name))) + .forEach(s -> log.warn("No Command found for auto complete {}", s)); + + return new InteractionControllerDefinition(interactionDefinitions); + } + private static Collection autoCompleteDefinitions(Class interactionClass) { + return Arrays.stream(interactionClass.getDeclaredMethods()) + .filter(method -> method.isAnnotationPresent(AutoComplete.class)) + .map(AutoCompleteDefinition::build) + .flatMap(Optional::stream) + .toList(); + } + + private static Set interactionDefinitions(Class clazz, + ValidatorRegistry validatorRegistry, + LocalizationFunction localizationFunction, + Interaction interaction, + Set permissions, + CooldownDefinition cooldown, + Collection autocompletes) { + Set definitions = new HashSet<>(autocompletes); + for (Method method : clazz.getDeclaredMethods()) { + final MethodBuildContext context = new MethodBuildContext( + validatorRegistry, + localizationFunction, + interaction, + permissions, + cooldown, + method, + autocompletes + ); + + Optional definition = Optional.empty(); // index commands if (method.isAnnotationPresent(SlashCommand.class)) { - Optional optional = SlashCommandDefinition.build(method, validatorRegistry, localizationFunction); - if (optional.isEmpty()) { - continue; - } - SlashCommandDefinition commandDefinition = optional.get(); - commandDefinition.getPermissions().addAll(permissions); - if (commandDefinition.getCooldown().getDelay() == 0) { - commandDefinition.getCooldown().set(cooldown); - } - if (interaction.ephemeral()) { - commandDefinition.setEphemeral(true); - } - commands.add(commandDefinition); + definition = SlashCommandDefinition.build(context); } if (method.isAnnotationPresent(ContextCommand.class)) { - Optional optional = ContextCommandDefinition.build(method, localizationFunction); - if (optional.isEmpty()) { - continue; - } - ContextCommandDefinition commandDefinition = optional.get(); - commandDefinition.getPermissions().addAll(permissions); - if (interaction.ephemeral()) { - commandDefinition.setEphemeral(true); - } - commands.add(commandDefinition); + definition = ContextCommandDefinition.build(context); } // index components if (method.isAnnotationPresent(Button.class)) { - ButtonDefinition.build(method).ifPresent(button -> { - if (interaction.ephemeral()) { - button.setEphemeral(true); - } - button.getPermissions().addAll(permissions); - buttons.add(button); - }); + definition = ButtonDefinition.build(context); } if (method.isAnnotationPresent(EntitySelectMenu.class)) { - EntitySelectMenuDefinition.build(method).ifPresent(menu -> { - if (interaction.ephemeral()) { - menu.setEphemeral(true); - } - menu.getPermissions().addAll(permissions); - selectMenus.add(menu); - }); + definition = EntitySelectMenuDefinition.build(context); } if (method.isAnnotationPresent(StringSelectMenu.class)) { - StringSelectMenuDefinition.build(method).ifPresent(menu -> { - if (interaction.ephemeral()) { - menu.setEphemeral(true); - } - menu.getPermissions().addAll(permissions); - selectMenus.add(menu); - }); + definition = StringSelectMenuDefinition.build(context); } //index modals if (method.isAnnotationPresent(Modal.class)) { - ModalDefinition.build(method).ifPresent(modal -> { - if (interaction.ephemeral()) { - modal.setEphemeral(true); - } - modal.getPermissions().addAll(permissions); - modals.add(modal); - }); + definition = ModalDefinition.build(method); } - } - - //loop again and index auto complete - for (Method method : interactionClass.getDeclaredMethods()) { - if (method.isAnnotationPresent(AutoComplete.class)) { - AutoCompleteDefinition.build( - method, - autoCompletes.stream().flatMap(it -> it.getCommandNames().stream()).collect(Collectors.toList()) - ).ifPresent(autoComplete -> { - autoCompletes.add(autoComplete); - Set commandNames = new HashSet<>(); - autoComplete.getCommandNames().forEach(name -> commands.stream() - .filter(it -> it.getCommandType() == Command.Type.SLASH) - .filter(command -> command.getName().startsWith(name)) - .forEach(command -> { - ((SlashCommandDefinition) command).setAutoComplete(true); - commandNames.add(command.getName()); - }) - ); - autoComplete.setCommandNames(commandNames); - }); - } + definition.ifPresent(definitions::add); } - - return Optional.of(new InteractionControllerDefinition(commands, buttons, selectMenus, autoCompletes, modals)); + return definitions; } - - /** - * Gets a possibly-empty list of all {@link GenericCommandDefinition CommandDefinitions}. - * - * @return a possibly-empty list of all {@link GenericCommandDefinition CommandDefinitions} - */ - public List getCommands() { - return commands; - } - - /** - * Gets a possibly-empty list of all buttons. - * - * @return a possibly-empty list of all buttons - */ - public List getButtons() { - return buttons; - } - - /** - * Gets a possibly-empty list of all select menus. - * - * @return a possibly-empty list of all select menus - */ - public List> getSelectMenus() { - return selectMenus; - } - - /** - * Gets a possibly-empty list of all {@link AutoCompleteDefinition AutoCompleteDefinitions}. - * - * @return a possibly-empty list of all {@link AutoCompleteDefinition AutoCompleteDefinitions} - */ - public Collection getAutoCompletes() { - return autoCompletes; - } - - /** - * Gets a possibly-empty list of all {@link ModalDefinition ModalDefinitions}. - * - * @return a possibly-empty list of all {@link ModalDefinition ModalDefinitions} - */ - public List getModals() { - return modals; - } - - @Override - public String toString() { - return "ControllerDefinition{" + - "commands=" + commands + - ", buttons=" + buttons + - ", selectMenus=" + selectMenus + - ", autoCompletes=" + autoCompletes + - ", modals=" + modals + - '}'; - } - } diff --git a/src/main/java/com/github/kaktushose/jda/commands/reflect/InteractionRegistry.java b/src/main/java/com/github/kaktushose/jda/commands/reflect/InteractionRegistry.java index 5d41a5f4a..b50f04e7c 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/reflect/InteractionRegistry.java +++ b/src/main/java/com/github/kaktushose/jda/commands/reflect/InteractionRegistry.java @@ -4,13 +4,12 @@ import com.github.kaktushose.jda.commands.dependency.DependencyInjector; import com.github.kaktushose.jda.commands.dispatching.validation.ValidatorRegistry; import com.github.kaktushose.jda.commands.reflect.interactions.AutoCompleteDefinition; +import com.github.kaktushose.jda.commands.reflect.interactions.GenericInteractionDefinition; import com.github.kaktushose.jda.commands.reflect.interactions.ModalDefinition; -import com.github.kaktushose.jda.commands.reflect.interactions.commands.ContextCommandDefinition; import com.github.kaktushose.jda.commands.reflect.interactions.commands.GenericCommandDefinition; import com.github.kaktushose.jda.commands.reflect.interactions.commands.SlashCommandDefinition; import com.github.kaktushose.jda.commands.reflect.interactions.components.ButtonDefinition; import com.github.kaktushose.jda.commands.reflect.interactions.components.menus.GenericSelectMenuDefinition; -import net.dv8tion.jda.api.interactions.commands.Command; import net.dv8tion.jda.api.interactions.commands.localization.LocalizationFunction; import net.dv8tion.jda.api.interactions.components.selections.SelectMenu; import org.jetbrains.annotations.NotNull; @@ -22,29 +21,22 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.Collections; +import java.util.Collection; import java.util.HashSet; -import java.util.Optional; import java.util.Set; -import java.util.stream.Collectors; /** * Central registry for all {@link SlashCommandDefinition CommandDefinitions}. * * @since 2.0.0 */ -public class InteractionRegistry { +public final class InteractionRegistry { private final static Logger log = LoggerFactory.getLogger(InteractionRegistry.class); private final ValidatorRegistry validatorRegistry; private final DependencyInjector dependencyInjector; private final LocalizationFunction localizationFunction; - private final Set controllers; - private final Set commands; - private final Set buttons; - private final Set> selectMenus; - private final Set autoCompletes; - private final Set modals; + private final Set definitions = new HashSet<>(); /** * Constructs a new CommandRegistry. @@ -59,12 +51,6 @@ public InteractionRegistry(@NotNull ValidatorRegistry validatorRegistry, this.validatorRegistry = validatorRegistry; this.dependencyInjector = dependencyInjector; this.localizationFunction = localizationFunction; - controllers = new HashSet<>(); - commands = new HashSet<>(); - buttons = new HashSet<>(); - selectMenus = new HashSet<>(); - autoCompletes = new HashSet<>(); - modals = new HashSet<>(); } /** @@ -92,82 +78,56 @@ public void index(@NotNull Class clazz, @NotNull String... packages) { for (Class aClass : controllerSet) { log.debug("Found interaction controller {}", aClass.getName()); - Optional optional = InteractionControllerDefinition.build( + InteractionControllerDefinition controller = InteractionControllerDefinition.build( aClass, validatorRegistry, dependencyInjector, localizationFunction ); - if (optional.isEmpty()) { - log.warn("Unable to index the interaction controller!"); - continue; - } - - InteractionControllerDefinition controller = optional.get(); - controllers.add(controller); - commands.addAll(controller.getCommands()); - buttons.addAll(controller.getButtons()); - selectMenus.addAll(controller.getSelectMenus()); - autoCompletes.addAll(controller.getAutoCompletes()); - modals.addAll(controller.getModals()); + definitions.addAll(controller.definitions()); log.debug("Registered interaction controller {}", controller); } log.debug("Successfully registered {} interaction controller(s) with a total of {} interaction(s)!", - controllers.size(), - commands.size() + buttons.size()); + controllerSet.size(), + definitions.size()); } /** - * Gets a possibly-empty list of all {@link InteractionControllerDefinition ControllerDefinitions}. - * - * @return a possibly-empty list of all {@link InteractionControllerDefinition ControllerDefinitions} - */ - public Set getInteractionControllers() { - return Collections.unmodifiableSet(controllers); - } - - /** - * Gets a possibly-empty list of all {@link GenericCommandDefinition CommandDefinitions}, this includes both - * {@link SlashCommandDefinition} and {@link ContextCommandDefinition}. + * Gets a possibly-empty list of all {@link GenericCommandDefinition CommandDefinitions}. * * @return a possibly-empty list of all {@link GenericCommandDefinition CommandDefinitions} */ - public Set getCommands() { - return Collections.unmodifiableSet(commands); - } - - /** - * Gets a possibly-empty list of all {@link SlashCommandDefinition SlashCommandDefinitions}. - * - * @return a possibly-empty list of all {@link SlashCommandDefinition SlashCommandDefinitions} - */ - public Set getSlashCommands() { - return commands.stream().filter(it -> (it.getCommandType() == Command.Type.SLASH)) - .map(it -> (SlashCommandDefinition) it) - .collect(Collectors.toUnmodifiableSet()); + public Collection getCommands() { + return definitions.stream() + .filter(GenericCommandDefinition.class::isInstance) + .map(GenericCommandDefinition.class::cast) + .toList(); } /** - * Gets a possibly-empty list of all {@link ContextCommandDefinition ContextCommandDefinitions}. + * Gets a possibly-empty list of all buttonContainers. * - * @return a possibly-empty list of all {@link ContextCommandDefinition ContextCommandDefinitions} + * @return a possibly-empty list of all buttonContainers */ - public Set getContextCommands() { - return commands.stream().filter(it -> - (it.getCommandType() == Command.Type.USER) || it.getCommandType() == Command.Type.MESSAGE) - .map(it -> (ContextCommandDefinition) it) - .collect(Collectors.toUnmodifiableSet()); + public Collection getButtons() { + return definitions.stream() + .filter(ButtonDefinition.class::isInstance) + .map(ButtonDefinition.class::cast) + .toList(); } /** - * Gets a possibly-empty list of all {@link ButtonDefinition ButtonDefinitions}. + * Gets a possibly-empty list of all select menus. * - * @return a possibly-empty list of all {@link ButtonDefinition ButtonDefinitions} + * @return a possibly-empty list of all select menus */ - public Set getButtons() { - return Collections.unmodifiableSet(buttons); + public Collection> getSelectMenus() { + return definitions.stream() + .filter(GenericSelectMenuDefinition.class::isInstance) + .>map(GenericSelectMenuDefinition.class::cast) + .toList(); } /** @@ -175,8 +135,11 @@ public Set getButtons() { * * @return a possibly-empty list of all {@link AutoCompleteDefinition AutoCompleteDefinitions} */ - public Set getAutoCompletes() { - return Collections.unmodifiableSet(autoCompletes); + public Collection getAutoCompletes() { + return definitions.stream() + .filter(AutoCompleteDefinition.class::isInstance) + .map(AutoCompleteDefinition.class::cast) + .toList(); } /** @@ -184,11 +147,10 @@ public Set getAutoCompletes() { * * @return a possibly-empty list of all {@link ModalDefinition ModalDefinitions} */ - public Set> getSelectMenus() { - return Collections.unmodifiableSet(selectMenus); - } - - public Set getModals() { - return Collections.unmodifiableSet(modals); + public Collection getModals() { + return definitions.stream() + .filter(ModalDefinition.class::isInstance) + .map(ModalDefinition.class::cast) + .toList(); } } diff --git a/src/main/java/com/github/kaktushose/jda/commands/reflect/MethodBuildContext.java b/src/main/java/com/github/kaktushose/jda/commands/reflect/MethodBuildContext.java new file mode 100644 index 000000000..0ca916293 --- /dev/null +++ b/src/main/java/com/github/kaktushose/jda/commands/reflect/MethodBuildContext.java @@ -0,0 +1,22 @@ +package com.github.kaktushose.jda.commands.reflect; + +import com.github.kaktushose.jda.commands.annotations.interactions.Interaction; +import com.github.kaktushose.jda.commands.dispatching.validation.ValidatorRegistry; +import com.github.kaktushose.jda.commands.reflect.interactions.AutoCompleteDefinition; +import net.dv8tion.jda.api.interactions.commands.localization.LocalizationFunction; +import org.jetbrains.annotations.NotNull; + +import java.lang.reflect.Method; +import java.util.Collection; +import java.util.Set; + +public record MethodBuildContext( + @NotNull ValidatorRegistry validatorRegistry, + @NotNull LocalizationFunction localizationFunction, + Interaction interaction, + Set permissions, + CooldownDefinition cooldownDefinition, + Method method, + Collection autoCompleteDefinitions +) { +} diff --git a/src/main/java/com/github/kaktushose/jda/commands/reflect/ParameterDefinition.java b/src/main/java/com/github/kaktushose/jda/commands/reflect/ParameterDefinition.java index 616df8a20..1e9eee659 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/reflect/ParameterDefinition.java +++ b/src/main/java/com/github/kaktushose/jda/commands/reflect/ParameterDefinition.java @@ -20,7 +20,6 @@ import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.interactions.commands.build.OptionData; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import java.lang.annotation.Annotation; import java.lang.reflect.InvocationTargetException; @@ -28,6 +27,8 @@ import java.lang.reflect.Parameter; import java.util.*; +import static java.util.Map.entry; + /** * Representation of a command parameter. * @@ -36,89 +37,65 @@ * @see Param * @since 2.0.0 */ -public class ParameterDefinition { - - private static final Map, Class> TYPE_MAPPINGS = new HashMap<>() { - { - put(byte.class, Byte.class); - put(short.class, Short.class); - put(int.class, Integer.class); - put(long.class, Long.class); - put(double.class, Double.class); - put(float.class, Float.class); - put(boolean.class, Boolean.class); - put(char.class, Character.class); - } - }; - - private static final Map, OptionType> OPTION_TYPE_MAPPINGS = new HashMap<>() { - { - put(Byte.class, OptionType.STRING); - put(Short.class, OptionType.STRING); - put(Integer.class, OptionType.INTEGER); - put(Long.class, OptionType.NUMBER); - put(Double.class, OptionType.NUMBER); - put(Float.class, OptionType.NUMBER); - put(Boolean.class, OptionType.BOOLEAN); - put(Character.class, OptionType.STRING); - put(String.class, OptionType.STRING); - put(String[].class, OptionType.STRING); - put(User.class, OptionType.USER); - put(Member.class, OptionType.USER); - put(GuildChannel.class, OptionType.CHANNEL); - put(GuildMessageChannel.class, OptionType.CHANNEL); - put(ThreadChannel.class, OptionType.CHANNEL); - put(TextChannel.class, OptionType.CHANNEL); - put(NewsChannel.class, OptionType.CHANNEL); - put(AudioChannel.class, OptionType.CHANNEL); - put(VoiceChannel.class, OptionType.CHANNEL); - put(StageChannel.class, OptionType.CHANNEL); - put(Role.class, OptionType.ROLE); - } - }; - - private static final Map, List> CHANNEL_TYPE_RESTRICTIONS = new HashMap<>() { - { - put(GuildMessageChannel.class, Collections.singletonList(ChannelType.TEXT)); - put(ThreadChannel.class, Arrays.asList( +public record ParameterDefinition( + Class type, + boolean isOptional, + String defaultValue, + boolean isPrimitive, + String name, + String description, + List choices, + List constraints +) { + + private static final Map, Class> TYPE_MAPPINGS = Map.ofEntries( + entry(byte.class, Byte.class), + entry(short.class, Short.class), + entry(int.class, Integer.class), + entry(long.class, Long.class), + entry(double.class, Double.class), + entry(float.class, Float.class), + entry(boolean.class, Boolean.class), + entry(char.class, Character.class) + ); + + private static final Map, OptionType> OPTION_TYPE_MAPPINGS = Map.ofEntries( + entry(Byte.class, OptionType.STRING), + entry(Short.class, OptionType.STRING), + entry(Integer.class, OptionType.INTEGER), + entry(Long.class, OptionType.NUMBER), + entry(Double.class, OptionType.NUMBER), + entry(Float.class, OptionType.NUMBER), + entry(Boolean.class, OptionType.BOOLEAN), + entry(Character.class, OptionType.STRING), + entry(String.class, OptionType.STRING), + entry(String[].class, OptionType.STRING), + entry(User.class, OptionType.USER), + entry(Member.class, OptionType.USER), + entry(GuildChannel.class, OptionType.CHANNEL), + entry(GuildMessageChannel.class, OptionType.CHANNEL), + entry(ThreadChannel.class, OptionType.CHANNEL), + entry(TextChannel.class, OptionType.CHANNEL), + entry(NewsChannel.class, OptionType.CHANNEL), + entry(AudioChannel.class, OptionType.CHANNEL), + entry(VoiceChannel.class, OptionType.CHANNEL), + entry(StageChannel.class, OptionType.CHANNEL), + entry(Role.class, OptionType.ROLE) + ); + + private static final Map, List> CHANNEL_TYPE_RESTRICTIONS = Map.ofEntries( + entry(GuildMessageChannel.class, Collections.singletonList(ChannelType.TEXT)), + entry(TextChannel.class, Collections.singletonList(ChannelType.TEXT)), + entry(NewsChannel.class, Collections.singletonList(ChannelType.NEWS)), + entry(AudioChannel.class, Collections.singletonList(ChannelType.VOICE)), + entry(VoiceChannel.class, Collections.singletonList(ChannelType.VOICE)), + entry(StageChannel.class, Collections.singletonList(ChannelType.STAGE)), + entry(ThreadChannel.class, Arrays.asList( ChannelType.GUILD_NEWS_THREAD, ChannelType.GUILD_PUBLIC_THREAD, ChannelType.GUILD_PRIVATE_THREAD - )); - put(TextChannel.class, Collections.singletonList(ChannelType.TEXT)); - put(NewsChannel.class, Collections.singletonList(ChannelType.NEWS)); - put(AudioChannel.class, Collections.singletonList(ChannelType.VOICE)); - put(VoiceChannel.class, Collections.singletonList(ChannelType.VOICE)); - put(StageChannel.class, Collections.singletonList(ChannelType.STAGE)); - } - }; - - private final Class type; - private final boolean isOptional; - private final String defaultValue; - private final boolean isPrimitive; - private final String name; - private final String description; - private final List choices; - private final List constraints; - - private ParameterDefinition(@NotNull Class type, - boolean isOptional, - @Nullable String defaultValue, - boolean isPrimitive, - @NotNull String name, - @NotNull String description, - @NotNull List choices, - @NotNull List constraints) { - this.type = type; - this.isOptional = isOptional; - this.defaultValue = defaultValue; - this.isPrimitive = isPrimitive; - this.name = name; - this.description = description; - this.choices = choices; - this.constraints = constraints; - } + )) + ); /** * Builds a new ParameterDefinition. @@ -235,104 +212,15 @@ public OptionData toOptionData(boolean isAutoComplete) { } constraints.stream().filter(constraint -> - constraint.getAnnotation() instanceof Min - ).findFirst().ifPresent(constraint -> optionData.setMinValue(((Min) constraint.getAnnotation()).value())); + constraint.annotation() instanceof Min + ).findFirst().ifPresent(constraint -> optionData.setMinValue(((Min) constraint.annotation()).value())); constraints.stream().filter(constraint -> - constraint.getAnnotation() instanceof Max - ).findFirst().ifPresent(constraint -> optionData.setMaxValue(((Max) constraint.getAnnotation()).value())); + constraint.annotation() instanceof Max + ).findFirst().ifPresent(constraint -> optionData.setMaxValue(((Max) constraint.annotation()).value())); java.util.Optional.ofNullable(CHANNEL_TYPE_RESTRICTIONS.get(type)).ifPresent(optionData::setChannelTypes); return optionData; } - - /** - * Gets the type of the parameter. - * - * @return the type of the parameter - */ - @NotNull - public Class getType() { - return type; - } - - /** - * Whether the parameter is optional. - * - * @return {@code true} if the parameter is optional - */ - public boolean isOptional() { - return isOptional; - } - - /** - * Gets a possibly-null default value to use if the parameter is optional. - * - * @return a possibly-null default value - */ - @Nullable - public String getDefaultValue() { - return defaultValue; - } - - /** - * Whether the type of the parameter is a primitive. - * - * @return {@code true} if the type of the parameter is a primitive - */ - public boolean isPrimitive() { - return isPrimitive; - } - - /** - * Gets a possibly-empty list of {@link ConstraintDefinition ConstraintDefinitions}. - * - * @return a possibly-empty list of {@link ConstraintDefinition ConstraintDefinitions} - */ - @NotNull - public List getConstraints() { - return constraints; - } - - /** - * Gets the parameter name. - * - * @return the parameter name - */ - @NotNull - public String getName() { - return name; - } - - /** - * Gets the parameter description. Only used for slash commands. - * - * @return the parameter description - */ - @NotNull - public String getDescription() { - return description; - } - - /** - * Gets the parameter choices. Only used for slash commands. - * - * @return the parameter choices - */ - public List getChoices() { - return choices; - } - - @Override - public String toString() { - return "{" + - type.getName() + - ", isOptional=" + isOptional + - ", defaultValue='" + defaultValue + '\'' + - ", isPrimitive=" + isPrimitive + - ", name='" + name + '\'' + - ", constraints=" + constraints + - '}'; - } } diff --git a/src/main/java/com/github/kaktushose/jda/commands/reflect/TextInputDefinition.java b/src/main/java/com/github/kaktushose/jda/commands/reflect/TextInputDefinition.java index 386024e7f..d3260b7dd 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/reflect/TextInputDefinition.java +++ b/src/main/java/com/github/kaktushose/jda/commands/reflect/TextInputDefinition.java @@ -16,31 +16,15 @@ * @see com.github.kaktushose.jda.commands.annotations.interactions.Modal Modal * @since 4.0.0 */ -public class TextInputDefinition { - - private final String label; - private final String placeholder; - private final String defaultValue; - private final int minValue; - private final int maxValue; - private final TextInputStyle style; - private final boolean required; - - private TextInputDefinition(String label, - String placeholder, - String defaultValue, - int minValue, - int maxValue, - TextInputStyle style, - boolean required) { - this.label = label; - this.placeholder = placeholder; - this.defaultValue = defaultValue; - this.minValue = minValue; - this.maxValue = maxValue; - this.style = style; - this.required = required; - } +public record TextInputDefinition( + String label, + String placeholder, + String defaultValue, + int minValue, + int maxValue, + TextInputStyle style, + boolean required +) { /** * Builds a new TextInputDefinition. @@ -94,71 +78,4 @@ public net.dv8tion.jda.api.interactions.components.text.TextInput toTextInput() return textInput.build(); } - - /** - * Gets the label of the TextInput. - * - * @return the label of the TextInput - */ - public String getLabel() { - return label; - } - - /** - * Gets the placeholder of the TextInput. - * - * @return the placeholder of the TextInput - */ - public String getPlaceholder() { - return placeholder; - } - - /** - * Gets the minimum value of the TextInput. - * - * @return the minimum value of the TextInput - */ - public int getMinValue() { - return minValue; - } - - /** - * Gets the maximum value of the TextInput. - * - * @return the maximum value of the TextInput - */ - public int getMaxValue() { - return maxValue; - } - - /** - * Gets the {@link TextInputStyle} of the TextInput. - * - * @return the {@link TextInputStyle} of the TextInput - */ - public TextInputStyle getStyle() { - return style; - } - - /** - * Whether this TextInput is required. - * - * @return {@code true} if this TextInput is required - */ - public boolean isRequired() { - return required; - } - - @Override - public String toString() { - return "TextInputDefinition{" + - "label='" + label + '\'' + - ", placeholder='" + placeholder + '\'' + - ", defaultValue='" + defaultValue + '\'' + - ", minValue=" + minValue + - ", maxValue=" + maxValue + - ", style=" + style + - ", required=" + required + - '}'; - } } diff --git a/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/AutoCompleteDefinition.java b/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/AutoCompleteDefinition.java index 74a998128..6566bcdc9 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/AutoCompleteDefinition.java +++ b/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/AutoCompleteDefinition.java @@ -1,5 +1,6 @@ package com.github.kaktushose.jda.commands.reflect.interactions; +import com.github.kaktushose.jda.commands.Helpers; import com.github.kaktushose.jda.commands.annotations.interactions.AutoComplete; import com.github.kaktushose.jda.commands.annotations.interactions.Interaction; import com.github.kaktushose.jda.commands.dispatching.interactions.autocomplete.AutoCompleteEvent; @@ -7,7 +8,6 @@ import java.lang.reflect.Method; import java.util.HashSet; -import java.util.List; import java.util.Optional; import java.util.Set; @@ -17,11 +17,11 @@ * @see AutoComplete * @since 4.0.0 */ -public class AutoCompleteDefinition extends GenericInteractionDefinition { +public final class AutoCompleteDefinition extends GenericInteractionDefinition { - private Set commands; + private final Set commands; - protected AutoCompleteDefinition(Method method, Set commands) { + private AutoCompleteDefinition(Method method, Set commands) { super(method, new HashSet<>()); this.commands = commands; } @@ -32,39 +32,22 @@ protected AutoCompleteDefinition(Method method, Set commands) { * @param method the {@link Method} of the AutoComplete * @return an {@link Optional} holding the AutoCompleteDefinition */ - public static Optional build(@NotNull Method method, List autoCompletes) { + public static Optional build(@NotNull Method method) { if (!method.isAnnotationPresent(AutoComplete.class) || !method.getDeclaringClass().isAnnotationPresent(Interaction.class)) { return Optional.empty(); } - if (method.getParameters().length != 1) { - log.error("An error has occurred! Skipping auto complete {}.{}:", - method.getDeclaringClass().getSimpleName(), - method.getName(), - new IllegalArgumentException("Invalid amount of parameters!")); + if (Helpers.isIncorrectParameterAmount(method, 1)) { return Optional.empty(); } - if (!AutoCompleteEvent.class.isAssignableFrom(method.getParameters()[0].getType())) { - log.error("An error has occurred! Skipping auto complete {}.{}:", - method.getDeclaringClass().getSimpleName(), - method.getName(), - new IllegalArgumentException(String.format("First parameter must be of type %s", AutoCompleteEvent.class.getSimpleName()))); + if (Helpers.isIncorrectParameterType(method, 0, AutoCompleteEvent.class)) { return Optional.empty(); } AutoComplete autoComplete = method.getAnnotation(AutoComplete.class); Set values = Set.of(autoComplete.value()); - if (autoCompletes.stream().anyMatch(values::contains)) { - log.error("An error has occurred! Skipping auto complete {}.{}:", - method.getDeclaringClass().getSimpleName(), - method.getName(), - new IllegalStateException(String.format("There is already an auto complete handler registered " + - "for at least one of the following commands: %s", values))); - return Optional.empty(); - } - return Optional.of(new AutoCompleteDefinition(method, values)); } @@ -77,21 +60,12 @@ public Set getCommandNames() { return commands; } - /** - * Set the command names this AutoComplete can handle - * - * @param commands a set of command names - */ - public void setCommandNames(Set commands) { - this.commands = commands; - } - @Override public String toString() { return "AutoCompleteDefinition{" + - "commands=" + commands + - ", id='" + definitionId + "'" + - ", method=" + method + - '}'; + "commands=" + commands + + ", id='" + definitionId + "'" + + ", method=" + method + + '}'; } } diff --git a/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/EphemeralInteractionDefinition.java b/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/EphemeralInteractionDefinition.java index 4e802ed15..0b26b1541 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/EphemeralInteractionDefinition.java +++ b/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/EphemeralInteractionDefinition.java @@ -1,5 +1,8 @@ package com.github.kaktushose.jda.commands.reflect.interactions; +import com.github.kaktushose.jda.commands.reflect.interactions.commands.GenericCommandDefinition; +import com.github.kaktushose.jda.commands.reflect.interactions.components.GenericComponentDefinition; + import java.lang.reflect.Method; import java.util.Set; @@ -8,7 +11,7 @@ * * @since 4.0.0 */ -public abstract class EphemeralInteractionDefinition extends GenericInteractionDefinition { +public abstract sealed class EphemeralInteractionDefinition extends GenericInteractionDefinition permits ModalDefinition, GenericCommandDefinition, GenericComponentDefinition { protected boolean ephemeral; diff --git a/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/GenericInteractionDefinition.java b/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/GenericInteractionDefinition.java index f5454f78d..8f5d411be 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/GenericInteractionDefinition.java +++ b/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/GenericInteractionDefinition.java @@ -13,7 +13,7 @@ * * @since 4.0.0 */ -public abstract class GenericInteractionDefinition { +public sealed abstract class GenericInteractionDefinition permits AutoCompleteDefinition, EphemeralInteractionDefinition { protected static final Logger log = LoggerFactory.getLogger(GenericInteractionDefinition.class); diff --git a/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/ModalDefinition.java b/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/ModalDefinition.java index 6db693c88..05b486136 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/ModalDefinition.java +++ b/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/ModalDefinition.java @@ -1,5 +1,6 @@ package com.github.kaktushose.jda.commands.reflect.interactions; +import com.github.kaktushose.jda.commands.Helpers; import com.github.kaktushose.jda.commands.annotations.interactions.Interaction; import com.github.kaktushose.jda.commands.annotations.interactions.Modal; import com.github.kaktushose.jda.commands.annotations.interactions.Permissions; @@ -18,12 +19,12 @@ * @see Modal * @since 4.0.0 */ -public class ModalDefinition extends EphemeralInteractionDefinition implements CustomId { +public final class ModalDefinition extends EphemeralInteractionDefinition implements CustomId { private final String title; private final List textInputs; - protected ModalDefinition(Method method, Set permissions, boolean ephemeral, String title, List textInputs) { + private ModalDefinition(Method method, Set permissions, boolean ephemeral, String title, List textInputs) { super(method, permissions, ephemeral); this.title = title; this.textInputs = textInputs; @@ -45,15 +46,11 @@ public static Optional build(@NotNull Method method) { log.error("An error has occurred! Skipping Modal {}.{}:", method.getDeclaringClass().getSimpleName(), method.getName(), - new IllegalArgumentException("Invalid amount of parameters!")); + new IllegalArgumentException("Invalid amount of parameters! Modals need between 1 and 5 TextInputs")); return Optional.empty(); } - if (!ModalEvent.class.isAssignableFrom(method.getParameters()[0].getType())) { - log.error("An error has occurred! Skipping Modal {}.{}:", - method.getDeclaringClass().getSimpleName(), - method.getName(), - new IllegalArgumentException(String.format("First parameter must be of type %s!", ModalEvent.class.getSimpleName()))); + if (Helpers.isIncorrectParameterType(method, 0, ModalEvent.class)) { return Optional.empty(); } @@ -134,12 +131,12 @@ public String createCustomId(String runtimeId) { @Override public String toString() { return "ModalDefinition{" + - "title='" + title + '\'' + - ", textInputs=" + textInputs + - ", ephemeral=" + ephemeral + - ", id='" + definitionId + '\'' + - ", method=" + method + - ", permissions=" + permissions + - '}'; + "title='" + title + '\'' + + ", textInputs=" + textInputs + + ", ephemeral=" + ephemeral + + ", id='" + definitionId + '\'' + + ", method=" + method + + ", permissions=" + permissions + + '}'; } } diff --git a/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/commands/ContextCommandDefinition.java b/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/commands/ContextCommandDefinition.java index e92dee003..fe7a41ce6 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/commands/ContextCommandDefinition.java +++ b/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/commands/ContextCommandDefinition.java @@ -1,9 +1,10 @@ package com.github.kaktushose.jda.commands.reflect.interactions.commands; +import com.github.kaktushose.jda.commands.Helpers; import com.github.kaktushose.jda.commands.annotations.interactions.ContextCommand; import com.github.kaktushose.jda.commands.annotations.interactions.Interaction; -import com.github.kaktushose.jda.commands.annotations.interactions.Permissions; import com.github.kaktushose.jda.commands.annotations.interactions.SlashCommand; +import com.github.kaktushose.jda.commands.reflect.MethodBuildContext; import net.dv8tion.jda.api.Permission; import net.dv8tion.jda.api.interactions.commands.Command; import net.dv8tion.jda.api.interactions.commands.DefaultMemberPermissions; @@ -14,7 +15,6 @@ import java.lang.reflect.Method; import java.util.Arrays; -import java.util.HashSet; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; @@ -25,7 +25,7 @@ * @see ContextCommand * @since 4.0.0 */ -public class ContextCommandDefinition extends GenericCommandDefinition { +public final class ContextCommandDefinition extends GenericCommandDefinition { public ContextCommandDefinition(Method method, boolean ephemeral, @@ -40,24 +40,14 @@ public ContextCommandDefinition(Method method, super(method, ephemeral, name, permissions, isGuildOnly, isNSFW, commandType, enabledPermissions, scope, localizationFunction); } - public static Optional build(@NotNull Method method, @NotNull LocalizationFunction localizationFunction) { + public static Optional build(@NotNull MethodBuildContext context) { + Method method = context.method(); if (!method.isAnnotationPresent(ContextCommand.class) || !method.getDeclaringClass().isAnnotationPresent(Interaction.class)) { return Optional.empty(); } ContextCommand command = method.getAnnotation(ContextCommand.class); - if (!command.isActive()) { - log.debug("Command {} is set inactive. Skipping this command!", method.getName()); - return Optional.empty(); - } - - Set permissions = new HashSet<>(); - if (method.isAnnotationPresent(Permissions.class)) { - Permissions permission = method.getAnnotation(Permissions.class); - permissions = new HashSet<>(Arrays.asList(permission.value())); - } - Set enabledFor = Arrays.stream(command.enabledFor()).collect(Collectors.toSet()); if (enabledFor.size() == 1 && enabledFor.contains(net.dv8tion.jda.api.Permission.UNKNOWN)) { enabledFor.clear(); @@ -65,15 +55,15 @@ public static Optional build(@NotNull Method method, @ return Optional.of(new ContextCommandDefinition( method, - command.ephemeral(), + Helpers.ephemeral(context, command.ephemeral()), command.value(), - permissions, + Helpers.permissions(context), command.isGuildOnly(), command.isNSFW(), command.type(), enabledFor, command.scope(), - localizationFunction + context.localizationFunction() )); } @@ -90,17 +80,17 @@ public CommandData toCommandData() { @Override public String toString() { return "ContextCommandDefinition{" + - "id='" + definitionId + '\'' + - ", method=" + method + - ", name='" + name + '\'' + - ", permissions=" + permissions + - ", isGuildOnly=" + isGuildOnly + - ", isNSFW=" + isNSFW + - ", commandType=" + commandType + - ", enabledPermissions=" + enabledPermissions + - ", scope=" + scope + - ", localizationFunction=" + localizationFunction + - ", ephemeral=" + ephemeral + - '}'; + "id='" + definitionId + '\'' + + ", method=" + method + + ", name='" + name + '\'' + + ", permissions=" + permissions + + ", isGuildOnly=" + isGuildOnly + + ", isNSFW=" + isNSFW + + ", commandType=" + commandType + + ", enabledPermissions=" + enabledPermissions + + ", scope=" + scope + + ", localizationFunction=" + localizationFunction + + ", ephemeral=" + ephemeral + + '}'; } } diff --git a/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/commands/GenericCommandDefinition.java b/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/commands/GenericCommandDefinition.java index 402d8ea72..2345e04e5 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/commands/GenericCommandDefinition.java +++ b/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/commands/GenericCommandDefinition.java @@ -18,7 +18,7 @@ * @see ContextCommandDefinition * @since 4.0.0 */ -public abstract class GenericCommandDefinition extends EphemeralInteractionDefinition implements Comparable { +public abstract sealed class GenericCommandDefinition extends EphemeralInteractionDefinition implements Comparable permits ContextCommandDefinition, SlashCommandDefinition { protected final String name; protected final boolean isGuildOnly; @@ -120,18 +120,18 @@ public SlashCommand.CommandScope getCommandScope() { @Override public String toString() { return "GenericCommandDefinition{" + - "id='" + definitionId + '\'' + - ", method=" + method + - ", name='" + name + '\'' + - ", permissions=" + permissions + - ", isGuildOnly=" + isGuildOnly + - ", isNSFW=" + isNSFW + - ", commandType=" + commandType + - ", enabledPermissions=" + enabledPermissions + - ", scope=" + scope + - ", localizationFunction=" + localizationFunction + - ", ephemeral=" + ephemeral + - '}'; + "id='" + definitionId + '\'' + + ", method=" + method + + ", name='" + name + '\'' + + ", permissions=" + permissions + + ", isGuildOnly=" + isGuildOnly + + ", isNSFW=" + isNSFW + + ", commandType=" + commandType + + ", enabledPermissions=" + enabledPermissions + + ", scope=" + scope + + ", localizationFunction=" + localizationFunction + + ", ephemeral=" + ephemeral + + '}'; } @Override diff --git a/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/commands/SlashCommandDefinition.java b/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/commands/SlashCommandDefinition.java index beead7442..35f04a035 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/commands/SlashCommandDefinition.java +++ b/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/commands/SlashCommandDefinition.java @@ -1,13 +1,14 @@ package com.github.kaktushose.jda.commands.reflect.interactions.commands; +import com.github.kaktushose.jda.commands.Helpers; import com.github.kaktushose.jda.commands.annotations.interactions.Cooldown; import com.github.kaktushose.jda.commands.annotations.interactions.Interaction; -import com.github.kaktushose.jda.commands.annotations.interactions.Permissions; import com.github.kaktushose.jda.commands.annotations.interactions.SlashCommand; import com.github.kaktushose.jda.commands.dispatching.interactions.commands.CommandEvent; -import com.github.kaktushose.jda.commands.dispatching.validation.ValidatorRegistry; import com.github.kaktushose.jda.commands.reflect.CooldownDefinition; +import com.github.kaktushose.jda.commands.reflect.MethodBuildContext; import com.github.kaktushose.jda.commands.reflect.ParameterDefinition; +import com.github.kaktushose.jda.commands.reflect.interactions.AutoCompleteDefinition; import net.dv8tion.jda.api.Permission; import net.dv8tion.jda.api.interactions.commands.Command; import net.dv8tion.jda.api.interactions.commands.DefaultMemberPermissions; @@ -15,12 +16,9 @@ import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData; import net.dv8tion.jda.api.interactions.commands.build.SubcommandData; import net.dv8tion.jda.api.interactions.commands.localization.LocalizationFunction; -import org.jetbrains.annotations.NotNull; import java.lang.reflect.Method; -import java.lang.reflect.Parameter; import java.util.*; -import java.util.stream.Collectors; /** * Representation of a slash command. @@ -28,12 +26,12 @@ * @see SlashCommand * @since 2.0.0 */ -public class SlashCommandDefinition extends GenericCommandDefinition { +public final class SlashCommandDefinition extends GenericCommandDefinition { private final String description; private final List parameters; private final CooldownDefinition cooldown; - private boolean isAutoComplete; + private final boolean isAutoComplete; public SlashCommandDefinition(Method method, boolean ephemeral, @@ -59,15 +57,10 @@ public SlashCommandDefinition(Method method, /** * Builds a new CommandDefinition. * - * @param method the {@link Method} of the command - * @param validatorRegistry the corresponding {@link ValidatorRegistry} - * @param localizationFunction the {@link LocalizationFunction} to use * @return an {@link Optional} holding the CommandDefinition */ - public static Optional build(@NotNull Method method, - @NotNull ValidatorRegistry validatorRegistry, - @NotNull LocalizationFunction localizationFunction) { - + public static Optional build(MethodBuildContext context) { + Method method = context.method(); if (!method.isAnnotationPresent(SlashCommand.class) || !method.getDeclaringClass().isAnnotationPresent(Interaction.class)) { return Optional.empty(); } @@ -75,33 +68,19 @@ public static Optional build(@NotNull Method method, SlashCommand command = method.getAnnotation(SlashCommand.class); Interaction interaction = method.getDeclaringClass().getAnnotation(Interaction.class); - if (!command.isActive()) { - log.debug("Command {} is set inactive. Skipping this command!", method.getName()); - return Optional.empty(); - } - - Set permissions = new HashSet<>(); - if (method.isAnnotationPresent(Permissions.class)) { - Permissions permission = method.getAnnotation(Permissions.class); - permissions = new HashSet<>(Arrays.asList(permission.value())); - } + String name = String.join(" ", interaction.value(), command.value()) + .replaceAll(" +", " ") + .trim(); - String label = interaction.value() + " " + command.value(); - while (label.contains(" ")) { - label = label.replaceAll(" {2}", " "); - } - label = label.trim(); - - if (label.isEmpty()) { + if (name.isEmpty()) { logError("Labels must not be empty!", method); return Optional.empty(); } // build parameter definitions - List parameters = new ArrayList<>(); - for (Parameter parameter : method.getParameters()) { - parameters.add(ParameterDefinition.build(parameter, validatorRegistry)); - } + List parameters = Arrays.stream(method.getParameters()) + .map(parameter -> ParameterDefinition.build(parameter, context.validatorRegistry())) + .toList(); if (parameters.isEmpty()) { logError(String.format("First parameter must be of type %s!", CommandEvent.class.getSimpleName()), method); @@ -109,12 +88,11 @@ public static Optional build(@NotNull Method method, } // validate parameter definitions - for (int i = 0; i < parameters.size(); i++) { - ParameterDefinition parameter = parameters.get(i); - Class type = parameter.getType(); + for (ParameterDefinition parameter : parameters) { + Class type = parameter.type(); // first argument must be a CommandEvent - if (i == 0) { + if (parameter == parameters.getFirst()) { if (!CommandEvent.class.isAssignableFrom(type)) { logError(String.format("First parameter must be of type %s!", CommandEvent.class.getSimpleName()), method); return Optional.empty(); @@ -130,26 +108,34 @@ public static Optional build(@NotNull Method method, } } - Set enabledFor = Arrays.stream(command.enabledFor()).collect(Collectors.toSet()); - if (enabledFor.size() == 1 && enabledFor.contains(net.dv8tion.jda.api.Permission.UNKNOWN)) { - enabledFor.clear(); + Set enabledFor = Set.of(command.enabledFor()); + if (enabledFor.size() == 1 && enabledFor.contains(Permission.UNKNOWN)) { + enabledFor = Set.of(); + } + + CooldownDefinition cooldownDefinition = CooldownDefinition.build(method.getAnnotation(Cooldown.class)); + if (cooldownDefinition.delay() == 0 && context.cooldownDefinition() != null) { + cooldownDefinition = context.cooldownDefinition(); } return Optional.of(new SlashCommandDefinition( method, - command.ephemeral(), - label, - permissions, + Helpers.ephemeral(context, command.ephemeral()), + name, + Helpers.permissions(context), command.isGuildOnly(), command.isNSFW(), Command.Type.SLASH, enabledFor, command.scope(), - localizationFunction, + context.localizationFunction(), command.desc(), parameters, - CooldownDefinition.build(method.getAnnotation(Cooldown.class)), - false + cooldownDefinition, + context.autoCompleteDefinitions().stream() + .map(AutoCompleteDefinition::getCommandNames) + .flatMap(Collection::stream) + .anyMatch(name::startsWith) )); } @@ -176,7 +162,7 @@ public SlashCommandData toCommandData() { .setLocalizationFunction(localizationFunction) .setDefaultPermissions(DefaultMemberPermissions.enabledFor(enabledPermissions)); parameters.forEach(parameter -> { - if (CommandEvent.class.isAssignableFrom(parameter.getType())) { + if (CommandEvent.class.isAssignableFrom(parameter.type())) { return; } command.addOptions(parameter.toOptionData(isAutoComplete)); @@ -197,7 +183,7 @@ public SubcommandData toSubCommandData(String label) { ); parameters.forEach(parameter -> { - if (CommandEvent.class.isAssignableFrom(parameter.getType())) { + if (CommandEvent.class.isAssignableFrom(parameter.type())) { return; } command.addOptions(parameter.toOptionData(isAutoComplete)); @@ -244,12 +230,12 @@ public CooldownDefinition getCooldown() { } /** - * Whether this command has a cooldown. More formally, checks if {@link CooldownDefinition#getDelay()} > 0. + * Whether this command has a cooldown. More formally, checks if {@link CooldownDefinition#delay()} > 0. * * @return {@code true} if this command has a cooldown. */ public boolean hasCooldown() { - return getCooldown().getDelay() > 0; + return getCooldown().delay() > 0; } /** @@ -261,35 +247,24 @@ public boolean isAutoComplete() { return isAutoComplete; } - /** - * Whether this command supports auto complete. - * - * @param autoComplete whether this can command support auto complete - * @return this instance for fluent interface - */ - public SlashCommandDefinition setAutoComplete(boolean autoComplete) { - this.isAutoComplete = autoComplete; - return this; - } - @Override public String toString() { return "SlashCommandDefinition{" + - "id='" + definitionId + '\'' + - ", method=" + method + - ", name='" + name + '\'' + - ", description='" + description + '\'' + - ", parameters=" + parameters + - ", cooldown=" + cooldown + - ", isAutoComplete=" + isAutoComplete + - ", permissions=" + permissions + - ", isGuildOnly=" + isGuildOnly + - ", isNSFW=" + isNSFW + - ", commandType=" + commandType + - ", enabledPermissions=" + enabledPermissions + - ", scope=" + scope + - ", localizationFunction=" + localizationFunction + - ", ephemeral=" + ephemeral + - '}'; + "id='" + definitionId + '\'' + + ", method=" + method + + ", name='" + name + '\'' + + ", description='" + description + '\'' + + ", parameters=" + parameters + + ", cooldown=" + cooldown + + ", isAutoComplete=" + isAutoComplete + + ", permissions=" + permissions + + ", isGuildOnly=" + isGuildOnly + + ", isNSFW=" + isNSFW + + ", commandType=" + commandType + + ", enabledPermissions=" + enabledPermissions + + ", scope=" + scope + + ", localizationFunction=" + localizationFunction + + ", ephemeral=" + ephemeral + + '}'; } } diff --git a/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/components/ButtonDefinition.java b/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/components/ButtonDefinition.java index d5782da36..b5b3e2911 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/components/ButtonDefinition.java +++ b/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/components/ButtonDefinition.java @@ -1,16 +1,15 @@ package com.github.kaktushose.jda.commands.reflect.interactions.components; +import com.github.kaktushose.jda.commands.Helpers; import com.github.kaktushose.jda.commands.annotations.interactions.Button; import com.github.kaktushose.jda.commands.annotations.interactions.Interaction; -import com.github.kaktushose.jda.commands.annotations.interactions.Permissions; import com.github.kaktushose.jda.commands.dispatching.interactions.components.ComponentEvent; +import com.github.kaktushose.jda.commands.reflect.MethodBuildContext; import net.dv8tion.jda.api.entities.emoji.Emoji; import net.dv8tion.jda.api.interactions.components.buttons.ButtonStyle; import org.jetbrains.annotations.NotNull; import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.HashSet; import java.util.Optional; import java.util.Set; @@ -20,20 +19,20 @@ * @see Button * @since 2.3.0 */ -public class ButtonDefinition extends GenericComponentDefinition { +public final class ButtonDefinition extends GenericComponentDefinition { private final String label; private final Emoji emoji; private final String link; private final ButtonStyle style; - protected ButtonDefinition(Method method, - Set permissions, - boolean ephemeral, - String label, - Emoji emoji, - String link, - ButtonStyle style) { + private ButtonDefinition(Method method, + Set permissions, + boolean ephemeral, + String label, + Emoji emoji, + String link, + ButtonStyle style) { super(method, permissions, ephemeral); this.label = label; this.emoji = emoji; @@ -44,38 +43,23 @@ protected ButtonDefinition(Method method, /** * Builds a new ButtonDefinition. * - * @param method the {@link Method} of the button * @return an {@link Optional} holding the ButtonDefinition */ - public static Optional build(@NotNull Method method) { + public static Optional build(MethodBuildContext context) { + Method method = context.method(); if (!method.isAnnotationPresent(Button.class) || !method.getDeclaringClass().isAnnotationPresent(Interaction.class)) { return Optional.empty(); } - if (method.getParameters().length != 1) { - log.error("An error has occurred! Skipping Button {}.{}:", - method.getDeclaringClass().getSimpleName(), - method.getName(), - new IllegalArgumentException("Invalid amount of parameters!")); + if (Helpers.isIncorrectParameterAmount(method, 1)) { return Optional.empty(); } - if (!ComponentEvent.class.isAssignableFrom(method.getParameters()[0].getType())) { - log.error("An error has occurred! Skipping Button {}.{}:", - method.getDeclaringClass().getSimpleName(), - method.getName(), - new IllegalArgumentException(String.format("First parameter must be of type %s!", ComponentEvent.class.getSimpleName()))); + if (Helpers.isIncorrectParameterType(method, 0, ComponentEvent.class)) { return Optional.empty(); } Button button = method.getAnnotation(Button.class); - - Set permissions = new HashSet<>(); - if (method.isAnnotationPresent(Permissions.class)) { - Permissions permission = method.getAnnotation(Permissions.class); - permissions = new HashSet<>(Arrays.asList(permission.value())); - } - Emoji emoji; String emojiString = button.emoji(); if (emojiString.isEmpty()) { @@ -86,8 +70,8 @@ public static Optional build(@NotNull Method method) { return Optional.of(new ButtonDefinition( method, - permissions, - button.ephemeral(), + Helpers.permissions(context), + Helpers.ephemeral(context, button.ephemeral()), button.value(), emoji, button.link(), @@ -159,14 +143,14 @@ public String getDisplayName() { @Override public String toString() { return "ButtonDefinition{" + - "label='" + label + '\'' + - ", emoji=" + emoji + - ", link='" + link + '\'' + - ", style=" + style + - ", ephemeral=" + ephemeral + - ", permissions=" + permissions + - ", id='" + definitionId + '\'' + - ", method=" + method + - '}'; + "label='" + label + '\'' + + ", emoji=" + emoji + + ", link='" + link + '\'' + + ", style=" + style + + ", ephemeral=" + ephemeral + + ", permissions=" + permissions + + ", id='" + definitionId + '\'' + + ", method=" + method + + '}'; } } diff --git a/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/components/GenericComponentDefinition.java b/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/components/GenericComponentDefinition.java index a3c177e91..a965afa70 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/components/GenericComponentDefinition.java +++ b/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/components/GenericComponentDefinition.java @@ -2,6 +2,7 @@ import com.github.kaktushose.jda.commands.reflect.interactions.CustomId; import com.github.kaktushose.jda.commands.reflect.interactions.EphemeralInteractionDefinition; +import com.github.kaktushose.jda.commands.reflect.interactions.components.menus.GenericSelectMenuDefinition; import java.lang.reflect.Method; import java.util.Set; @@ -13,7 +14,7 @@ * @see com.github.kaktushose.jda.commands.reflect.interactions.components.menus.GenericSelectMenuDefinition GenericSelectMenuDefinition * @since 4.0.0 */ -public abstract class GenericComponentDefinition extends EphemeralInteractionDefinition implements CustomId { +public abstract sealed class GenericComponentDefinition extends EphemeralInteractionDefinition implements CustomId permits ButtonDefinition, GenericSelectMenuDefinition { protected GenericComponentDefinition(Method method, Set permissions, boolean ephemeral) { super(method, permissions, ephemeral); diff --git a/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/components/menus/EntitySelectMenuDefinition.java b/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/components/menus/EntitySelectMenuDefinition.java index c95e93e0a..520c8500a 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/components/menus/EntitySelectMenuDefinition.java +++ b/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/components/menus/EntitySelectMenuDefinition.java @@ -1,16 +1,19 @@ package com.github.kaktushose.jda.commands.reflect.interactions.components.menus; +import com.github.kaktushose.jda.commands.Helpers; import com.github.kaktushose.jda.commands.annotations.interactions.EntitySelectMenu; import com.github.kaktushose.jda.commands.annotations.interactions.Interaction; -import com.github.kaktushose.jda.commands.annotations.interactions.Permissions; import com.github.kaktushose.jda.commands.dispatching.interactions.components.ComponentEvent; +import com.github.kaktushose.jda.commands.reflect.MethodBuildContext; import net.dv8tion.jda.api.entities.channel.ChannelType; import net.dv8tion.jda.api.interactions.components.selections.EntitySelectMenu.DefaultValue; import net.dv8tion.jda.api.interactions.components.selections.EntitySelectMenu.SelectTarget; -import org.jetbrains.annotations.NotNull; import java.lang.reflect.Method; -import java.util.*; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; /** * Representation of a {@link net.dv8tion.jda.api.interactions.components.selections.EntitySelectMenu EntitySelectMenu}. @@ -18,21 +21,21 @@ * @see EntitySelectMenu * @since 4.0.0 */ -public class EntitySelectMenuDefinition extends GenericSelectMenuDefinition { +public final class EntitySelectMenuDefinition extends GenericSelectMenuDefinition { private final Set selectTargets; private final Set defaultValues; private final Set channelTypes; - protected EntitySelectMenuDefinition(Method method, - Set permissions, - boolean ephemeral, - Set selectTargets, - Set defaultValues, - Set channelTypes, - String placeholder, - int minValue, - int maxValue) { + private EntitySelectMenuDefinition(Method method, + Set permissions, + boolean ephemeral, + Set selectTargets, + Set defaultValues, + Set channelTypes, + String placeholder, + int minValue, + int maxValue) { super(method, permissions, ephemeral, placeholder, minValue, maxValue); this.selectTargets = selectTargets; this.defaultValues = defaultValues; @@ -42,40 +45,23 @@ protected EntitySelectMenuDefinition(Method method, /** * Builds a new EntitySelectMenuDefinition. * - * @param method the {@link Method} of the button * @return an {@link Optional} holding the EntitySelectMenuDefinition */ - public static Optional build(@NotNull Method method) { + public static Optional build(MethodBuildContext context) { + Method method = context.method(); if (!method.isAnnotationPresent(EntitySelectMenu.class) || !method.getDeclaringClass().isAnnotationPresent(Interaction.class)) { return Optional.empty(); } - if (method.getParameters().length != 2) { - log.error("An error has occurred! Skipping Button {}.{}:", - method.getDeclaringClass().getSimpleName(), - method.getName(), - new IllegalArgumentException("Invalid amount of parameters!")); + if (Helpers.isIncorrectParameterAmount(method, 2)) { return Optional.empty(); } - if (!ComponentEvent.class.isAssignableFrom(method.getParameters()[0].getType()) && - !List.class.isAssignableFrom(method.getParameters()[1].getType())) { - log.error("An error has occurred! Skipping Button {}.{}:", - method.getDeclaringClass().getSimpleName(), - method.getName(), - new IllegalArgumentException(String.format("First parameter must be of type %s, second parameter of type %s!", - ComponentEvent.class.getSimpleName(), - List.class.getSimpleName() - ))); + if (Helpers.isIncorrectParameterType(method, 0, ComponentEvent.class) || + Helpers.isIncorrectParameterType(method, 1, List.class)) { return Optional.empty(); } - Set permissions = new HashSet<>(); - if (method.isAnnotationPresent(Permissions.class)) { - Permissions permission = method.getAnnotation(Permissions.class); - permissions = new HashSet<>(Arrays.asList(permission.value())); - } - EntitySelectMenu selectMenu = method.getAnnotation(EntitySelectMenu.class); Set defaultValueSet = new HashSet<>(); @@ -94,8 +80,8 @@ public static Optional build(@NotNull Method method) return Optional.of(new EntitySelectMenuDefinition( method, - permissions, - selectMenu.ephemeral(), + Helpers.permissions(context), + Helpers.ephemeral(context, selectMenu.ephemeral()), Set.of(selectMenu.value()), defaultValueSet, new HashSet<>(Set.of(selectMenu.channelTypes())), @@ -152,16 +138,16 @@ public Set getChannelTypes() { @Override public String toString() { return "EntitySelectMenuDefinition{" + - "selectTargets=" + selectTargets + - ", defaultValues=" + defaultValues + - ", channelTypes=" + channelTypes + - ", placeholder='" + placeholder + '\'' + - ", minValue=" + minValue + - ", maxValue=" + maxValue + - ", permissions=" + permissions + - ", ephemeral=" + ephemeral + - ", id='" + definitionId + '\'' + - ", method=" + method + - '}'; + "selectTargets=" + selectTargets + + ", defaultValues=" + defaultValues + + ", channelTypes=" + channelTypes + + ", placeholder='" + placeholder + '\'' + + ", minValue=" + minValue + + ", maxValue=" + maxValue + + ", permissions=" + permissions + + ", ephemeral=" + ephemeral + + ", id='" + definitionId + '\'' + + ", method=" + method + + '}'; } } diff --git a/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/components/menus/GenericSelectMenuDefinition.java b/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/components/menus/GenericSelectMenuDefinition.java index 96dc990d7..e0a638ce8 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/components/menus/GenericSelectMenuDefinition.java +++ b/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/components/menus/GenericSelectMenuDefinition.java @@ -13,7 +13,7 @@ * @see StringSelectMenuDefinition * @since 4.0.0 */ -public abstract class GenericSelectMenuDefinition extends GenericComponentDefinition { +public abstract sealed class GenericSelectMenuDefinition extends GenericComponentDefinition permits EntitySelectMenuDefinition, StringSelectMenuDefinition { protected final String placeholder; protected final int minValue; diff --git a/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/components/menus/SelectOptionDefinition.java b/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/components/menus/SelectOptionDefinition.java index b466307b2..d714a9874 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/components/menus/SelectOptionDefinition.java +++ b/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/components/menus/SelectOptionDefinition.java @@ -3,7 +3,7 @@ import com.github.kaktushose.jda.commands.annotations.interactions.SelectOption; import net.dv8tion.jda.api.entities.emoji.Emoji; -import java.util.Optional; +import javax.annotation.Nullable; /** * Representation of a {@link net.dv8tion.jda.api.interactions.components.selections.SelectOption SelectOption}. @@ -11,21 +11,13 @@ * @see SelectOption * @since 4.0.0 */ -public class SelectOptionDefinition { - - private final String value; - private final String label; - private final String description; - private final Emoji emoji; - private final boolean isDefault; - - protected SelectOptionDefinition(String value, String label, String description, Emoji emoji, boolean isDefault) { - this.value = value; - this.label = label; - this.description = description; - this.emoji = emoji; - this.isDefault = isDefault; - } +public record SelectOptionDefinition( + String value, + String label, + String description, + @Nullable Emoji emoji, + boolean isDefault +) { /** * Builds a new SelectOptionDefinition. @@ -41,6 +33,7 @@ public static SelectOptionDefinition build(SelectOption option) { } else { emoji = Emoji.fromFormatted(emojiString); } + return new SelectOptionDefinition(option.value(), option.label(), option.description(), emoji, option.isDefault()); } @@ -49,60 +42,4 @@ public net.dv8tion.jda.api.interactions.components.selections.SelectOption toSel .withDescription(description) .withEmoji(emoji); } - - /** - * Gets the value of the SelectOption. - * - * @return the value - */ - public String getValue() { - return value; - } - - /** - * Gets the label of the SelectOption. - * - * @return the label - */ - public String getLabel() { - return label; - } - - /** - * Gets the description of the SelectOption. - * - * @return the description - */ - public String getDescription() { - return description; - } - - /** - * Gets the {@link Emoji} of the SelectOption. - * - * @return an {@link Optional} holding the {@link Emoji} - */ - public Optional getEmoji() { - return Optional.ofNullable(emoji); - } - - /** - * Gets whether this SelectOption is a default option. - * - * @return {@code true} if this SelectOption is a default option - */ - public boolean isDefault() { - return isDefault; - } - - @Override - public String toString() { - return "SelectOptionDefinition{" + - "value='" + value + '\'' + - ", label='" + label + '\'' + - ", description='" + description + '\'' + - ", emoji=" + emoji + - ", isDefault=" + isDefault + - '}'; - } } diff --git a/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/components/menus/StringSelectMenuDefinition.java b/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/components/menus/StringSelectMenuDefinition.java index b9cca3601..e22c0c5f4 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/components/menus/StringSelectMenuDefinition.java +++ b/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/components/menus/StringSelectMenuDefinition.java @@ -1,14 +1,17 @@ package com.github.kaktushose.jda.commands.reflect.interactions.components.menus; +import com.github.kaktushose.jda.commands.Helpers; import com.github.kaktushose.jda.commands.annotations.interactions.Interaction; -import com.github.kaktushose.jda.commands.annotations.interactions.Permissions; import com.github.kaktushose.jda.commands.annotations.interactions.SelectOption; import com.github.kaktushose.jda.commands.annotations.interactions.StringSelectMenu; import com.github.kaktushose.jda.commands.dispatching.interactions.components.ComponentEvent; -import org.jetbrains.annotations.NotNull; +import com.github.kaktushose.jda.commands.reflect.MethodBuildContext; import java.lang.reflect.Method; -import java.util.*; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; import java.util.stream.Collectors; /** @@ -17,17 +20,17 @@ * @see StringSelectMenu * @since 4.0.0 */ -public class StringSelectMenuDefinition extends GenericSelectMenuDefinition { +public final class StringSelectMenuDefinition extends GenericSelectMenuDefinition { private final Set selectOptions; - protected StringSelectMenuDefinition(Method method, - Set permissions, - boolean ephemeral, - Set selectOptions, - String placeholder, - int minValue, - int maxValue) { + private StringSelectMenuDefinition(Method method, + Set permissions, + boolean ephemeral, + Set selectOptions, + String placeholder, + int minValue, + int maxValue) { super(method, permissions, ephemeral, placeholder, minValue, maxValue); this.selectOptions = selectOptions; } @@ -35,40 +38,23 @@ protected StringSelectMenuDefinition(Method method, /** * Builds a new StringSelectMenuDefinition. * - * @param method the {@link Method} of the button * @return an {@link Optional} holding the StringSelectMenuDefinition */ - public static Optional build(@NotNull Method method) { + public static Optional build(MethodBuildContext context) { + Method method = context.method(); if (!method.isAnnotationPresent(StringSelectMenu.class) || !method.getDeclaringClass().isAnnotationPresent(Interaction.class)) { return Optional.empty(); } - if (method.getParameters().length != 2) { - log.error("An error has occurred! Skipping Button {}.{}:", - method.getDeclaringClass().getSimpleName(), - method.getName(), - new IllegalArgumentException("Invalid amount of parameters!")); + if (Helpers.isIncorrectParameterAmount(method, 2)) { return Optional.empty(); } - if (!ComponentEvent.class.isAssignableFrom(method.getParameters()[0].getType()) && - !List.class.isAssignableFrom(method.getParameters()[1].getType())) { - log.error("An error has occurred! Skipping Button {}.{}:", - method.getDeclaringClass().getSimpleName(), - method.getName(), - new IllegalArgumentException(String.format("First parameter must be of type %s, second parameter of type %s!", - ComponentEvent.class.getSimpleName(), - List.class.getSimpleName() - ))); + if (Helpers.isIncorrectParameterType(method, 0, ComponentEvent.class) || + Helpers.isIncorrectParameterType(method, 1, List.class)) { return Optional.empty(); } - Set permissions = new HashSet<>(); - if (method.isAnnotationPresent(Permissions.class)) { - Permissions permission = method.getAnnotation(Permissions.class); - permissions = new HashSet<>(Arrays.asList(permission.value())); - } - StringSelectMenu selectMenu = method.getAnnotation(StringSelectMenu.class); Set selectOptions = new HashSet<>(); @@ -78,8 +64,8 @@ public static Optional build(@NotNull Method method) return Optional.of(new StringSelectMenuDefinition( method, - permissions, - selectMenu.ephemeral(), + Helpers.permissions(context), + Helpers.ephemeral(context, selectMenu.ephemeral()), selectOptions, selectMenu.value(), selectMenu.minValue(), @@ -113,14 +99,14 @@ public Set getSelectOptions() { @Override public String toString() { return "StringSelectMenuDefinition{" + - "selectOptions=" + selectOptions + - ", placeholder='" + placeholder + '\'' + - ", minValue=" + minValue + - ", maxValue=" + maxValue + - ", ephemeral=" + ephemeral + - ", permissions=" + permissions + - ", id='" + definitionId + '\'' + - ", method=" + method + - '}'; + "selectOptions=" + selectOptions + + ", placeholder='" + placeholder + '\'' + + ", minValue=" + minValue + + ", maxValue=" + maxValue + + ", ephemeral=" + ephemeral + + ", permissions=" + permissions + + ", id='" + definitionId + '\'' + + ", method=" + method + + '}'; } } diff --git a/src/main/java/com/github/kaktushose/jda/commands/scope/DefaultGuildScopeProvider.java b/src/main/java/com/github/kaktushose/jda/commands/scope/DefaultGuildScopeProvider.java index 6707df8cf..d7ffab805 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/scope/DefaultGuildScopeProvider.java +++ b/src/main/java/com/github/kaktushose/jda/commands/scope/DefaultGuildScopeProvider.java @@ -14,8 +14,7 @@ public class DefaultGuildScopeProvider implements GuildScopeProvider { @Override - public Set getGuildsForCommand(CommandData commandData) { + public Set apply(CommandData commandData) { return Collections.emptySet(); } - } diff --git a/src/main/java/com/github/kaktushose/jda/commands/scope/GuildScopeProvider.java b/src/main/java/com/github/kaktushose/jda/commands/scope/GuildScopeProvider.java index 9ac1daa4b..15c1cf0b1 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/scope/GuildScopeProvider.java +++ b/src/main/java/com/github/kaktushose/jda/commands/scope/GuildScopeProvider.java @@ -3,6 +3,7 @@ import net.dv8tion.jda.api.interactions.commands.build.CommandData; import java.util.Set; +import java.util.function.Function; /** * Interface for declaring on which Guilds a guild scoped command should be registered. @@ -11,7 +12,8 @@ * @see DefaultGuildScopeProvider * @since 4.0.0 */ -public interface GuildScopeProvider { +@FunctionalInterface +public interface GuildScopeProvider extends Function> { /** * Gets a Set of guild ids the provided command should be registered for. @@ -19,6 +21,6 @@ public interface GuildScopeProvider { * @param commandData a copy of the {@link CommandData} to register * @return a Set of guild ids the provided command should be registered for */ - Set getGuildsForCommand(CommandData commandData); + Set apply(CommandData commandData); } diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java new file mode 100644 index 000000000..33a136ec6 --- /dev/null +++ b/src/main/java/module-info.java @@ -0,0 +1,9 @@ +module jda.commands { + requires com.google.gson; + requires jsr305; + requires net.dv8tion.jda; + requires org.jetbrains.annotations; + requires org.reflections; + requires java.desktop; + requires org.slf4j; +} \ No newline at end of file diff --git a/src/test/java/adapting/CustomTypeAdapter.java b/src/test/java/adapting/CustomTypeAdapter.java index 8bee3ee15..cabfc90f5 100644 --- a/src/test/java/adapting/CustomTypeAdapter.java +++ b/src/test/java/adapting/CustomTypeAdapter.java @@ -9,7 +9,7 @@ public class CustomTypeAdapter implements TypeAdapter { @Override - public Optional parse(@NotNull String raw, @NotNull Context context) { + public Optional apply(@NotNull String raw, @NotNull Context context) { return Optional.of(new CustomType()); } } diff --git a/src/test/java/adapting/TypeAdapterRegistryTest.java b/src/test/java/adapting/TypeAdapterRegistryTest.java index de210f666..9f23c24ef 100644 --- a/src/test/java/adapting/TypeAdapterRegistryTest.java +++ b/src/test/java/adapting/TypeAdapterRegistryTest.java @@ -1,13 +1,19 @@ package adapting; -import adapting.mock.JDACommandsMock; import adapting.mock.SlashCommandInteractionEventMock; import adapting.mock.TypeAdapterRegistryTestController; +import com.github.kaktushose.jda.commands.annotations.interactions.Interaction; +import com.github.kaktushose.jda.commands.dependency.DefaultDependencyInjector; import com.github.kaktushose.jda.commands.dispatching.adapter.TypeAdapterRegistry; import com.github.kaktushose.jda.commands.dispatching.adapter.impl.IntegerAdapter; -import com.github.kaktushose.jda.commands.dispatching.interactions.commands.SlashCommandContext; import com.github.kaktushose.jda.commands.dispatching.interactions.commands.CommandEvent; +import com.github.kaktushose.jda.commands.dispatching.interactions.commands.SlashCommandContext; +import com.github.kaktushose.jda.commands.dispatching.middleware.MiddlewareRegistry; import com.github.kaktushose.jda.commands.dispatching.validation.ValidatorRegistry; +import com.github.kaktushose.jda.commands.reflect.CooldownDefinition; +import com.github.kaktushose.jda.commands.reflect.ImplementationRegistry; +import com.github.kaktushose.jda.commands.reflect.InteractionRegistry; +import com.github.kaktushose.jda.commands.reflect.MethodBuildContext; import com.github.kaktushose.jda.commands.reflect.interactions.commands.SlashCommandDefinition; import net.dv8tion.jda.api.interactions.commands.localization.ResourceBundleLocalizationFunction; import org.junit.jupiter.api.BeforeAll; @@ -15,6 +21,7 @@ import org.junit.jupiter.api.Test; import java.lang.reflect.Method; +import java.util.Set; import static org.junit.jupiter.api.Assertions.*; @@ -73,7 +80,7 @@ public void adapt_withStringArray_ShouldNotAdapt() throws NoSuchMethodException registry.adapt(context); - assertArrayEquals(new String[]{"a", "b", "c"}, (String[]) context.getArguments().get(1)); + assertArrayEquals(new String[]{"a", "b", "c"}, (String[]) context.getArguments().getFirst()); } @Test @@ -98,7 +105,7 @@ public void adapt_withOptionalWithDefaultNull_ShouldAddNull() throws NoSuchMetho registry.adapt(context); - assertNull(context.getArguments().get(1)); + assertNull(context.getArguments().getFirst()); } @Test @@ -107,7 +114,7 @@ public void adapt_withOptionalWithDefault_ShouldAddDefault() throws NoSuchMethod registry.adapt(context); - assertEquals(TypeAdapterRegistryTestController.OPTIONAL_DEFAULT, context.getArguments().get(1)); + assertEquals(TypeAdapterRegistryTestController.OPTIONAL_DEFAULT, context.getArguments().getFirst()); } @Test @@ -129,15 +136,34 @@ public void adapt_withWrongArgument_ShouldCancel() throws NoSuchMethodException private SlashCommandDefinition buildCommand(String name, Class... parameterTypes) throws NoSuchMethodException { Method method = controller.getMethod(name, parameterTypes); - SlashCommandDefinition command = SlashCommandDefinition.build(method, validator, ResourceBundleLocalizationFunction.empty().build()).orElse(null); + SlashCommandDefinition command = SlashCommandDefinition.build(new MethodBuildContext( + validator, + ResourceBundleLocalizationFunction.empty().build(), + controller.getAnnotation(Interaction.class), + Set.of(), + CooldownDefinition.build(null), + method, + Set.of() + )).orElse(null); assertNotNull(command); return command; } - private SlashCommandContext buildContext(SlashCommandDefinition command, String... input) { + public static SlashCommandContext buildContext(SlashCommandDefinition command, String... input) { SlashCommandContext context = new SlashCommandContext( new SlashCommandInteractionEventMock(), - new JDACommandsMock()); + new InteractionRegistry( + new ValidatorRegistry(), + new DefaultDependencyInjector(), + ResourceBundleLocalizationFunction.empty().build() + ), + new ImplementationRegistry( + new DefaultDependencyInjector(), + new MiddlewareRegistry(), + new TypeAdapterRegistry(), + new ValidatorRegistry() + ) + ); context.setInput(input); context.setCommand(command); return context; diff --git a/src/test/java/adapting/TypeAdapterTest.java b/src/test/java/adapting/TypeAdapterTest.java index 0a07b06b9..ace397e3b 100644 --- a/src/test/java/adapting/TypeAdapterTest.java +++ b/src/test/java/adapting/TypeAdapterTest.java @@ -1,9 +1,8 @@ package adapting; import adapting.mock.GuildMock; -import adapting.mock.JDACommandsMock; import adapting.mock.JDAMock; -import adapting.mock.SlashCommandInteractionEventMock; +import com.github.kaktushose.jda.commands.Helpers; import com.github.kaktushose.jda.commands.dispatching.adapter.impl.*; import com.github.kaktushose.jda.commands.dispatching.interactions.commands.SlashCommandContext; import org.junit.jupiter.api.BeforeAll; @@ -17,310 +16,308 @@ public class TypeAdapterTest { @BeforeAll public static void setup() { - context = new SlashCommandContext(new SlashCommandInteractionEventMock(), new JDACommandsMock()); + context = TypeAdapterRegistryTest.buildContext(null, ""); } @Test public void booleanAdapter_withZero_ShouldReturnFalse() { BooleanAdapter adapter = new BooleanAdapter(); - assertFalse(adapter.parse("0", context).orElse(true)); + assertFalse(adapter.apply("0", context).orElse(true)); } @Test public void booleanAdapter_withFalse_ShouldReturnFalse() { BooleanAdapter adapter = new BooleanAdapter(); - assertFalse(adapter.parse("False", context).orElse(true)); - assertFalse(adapter.parse("false", context).orElse(true)); - assertFalse(adapter.parse("fAlSe", context).orElse(true)); + assertFalse(adapter.apply("False", context).orElse(true)); + assertFalse(adapter.apply("false", context).orElse(true)); + assertFalse(adapter.apply("fAlSe", context).orElse(true)); } @Test public void booleanAdapter_withOne_ShouldReturnTrue() { BooleanAdapter adapter = new BooleanAdapter(); - assertTrue(adapter.parse("1", context).orElse(false)); + assertTrue(adapter.apply("1", context).orElse(false)); } @Test public void booleanAdapter_withTrue_ShouldReturnTrue() { BooleanAdapter adapter = new BooleanAdapter(); - assertTrue(adapter.parse("True", context).orElse(false)); - assertTrue(adapter.parse("true", context).orElse(false)); - assertTrue(adapter.parse("tRuE", context).orElse(false)); + assertTrue(adapter.apply("True", context).orElse(false)); + assertTrue(adapter.apply("true", context).orElse(false)); + assertTrue(adapter.apply("tRuE", context).orElse(false)); } @Test public void booleanAdapter_withNonBoolean_ShouldBeEmpty() { BooleanAdapter adapter = new BooleanAdapter(); - assertFalse(adapter.parse("text", context).isPresent()); + assertFalse(adapter.apply("text", context).isPresent()); } @Test public void byteAdapter_withNumber_ShouldBePresent() { ByteAdapter adapter = new ByteAdapter(); - assertEquals(Byte.MIN_VALUE, adapter.parse(String.valueOf(Byte.MIN_VALUE), context).orElseThrow()); - assertEquals(Byte.MAX_VALUE, adapter.parse(String.valueOf(Byte.MAX_VALUE), context).orElseThrow()); + assertEquals(Byte.MIN_VALUE, adapter.apply(String.valueOf(Byte.MIN_VALUE), context).orElseThrow()); + assertEquals(Byte.MAX_VALUE, adapter.apply(String.valueOf(Byte.MAX_VALUE), context).orElseThrow()); } @Test public void byteAdapter_withNonNumeric_ShouldBeEmpty() { ByteAdapter adapter = new ByteAdapter(); - assertFalse(adapter.parse("text", context).isPresent()); + assertFalse(adapter.apply("text", context).isPresent()); } @Test public void characterAdapter_withOneChar_ShouldBePresent() { CharacterAdapter adapter = new CharacterAdapter(); - assertTrue(adapter.parse("c", context).isPresent()); + assertTrue(adapter.apply("c", context).isPresent()); } @Test public void characterAdapter_withMultipleChars_ShouldBeEmpty() { CharacterAdapter adapter = new CharacterAdapter(); - assertFalse(adapter.parse("chars", context).isPresent()); + assertFalse(adapter.apply("chars", context).isPresent()); } @Test public void doubleAdapter_withNumber_ShouldReturnDouble() { DoubleAdapter adapter = new DoubleAdapter(); - assertEquals(Double.MIN_VALUE, adapter.parse(String.valueOf(Double.MIN_VALUE), context).orElseThrow()); - assertEquals(Double.MIN_EXPONENT, adapter.parse(String.valueOf(Double.MIN_EXPONENT), context).orElseThrow()); - assertEquals(Double.MAX_VALUE, adapter.parse(String.valueOf(Double.MAX_VALUE), context).orElseThrow()); - assertEquals(Double.MAX_EXPONENT, adapter.parse(String.valueOf(Double.MAX_EXPONENT), context).orElseThrow()); + assertEquals(Double.MIN_VALUE, adapter.apply(String.valueOf(Double.MIN_VALUE), context).orElseThrow()); + assertEquals(Double.MIN_EXPONENT, adapter.apply(String.valueOf(Double.MIN_EXPONENT), context).orElseThrow()); + assertEquals(Double.MAX_VALUE, adapter.apply(String.valueOf(Double.MAX_VALUE), context).orElseThrow()); + assertEquals(Double.MAX_EXPONENT, adapter.apply(String.valueOf(Double.MAX_EXPONENT), context).orElseThrow()); } @Test public void doubleAdapter_withNonNumeric_ShouldBeEmpty() { DoubleAdapter adapter = new DoubleAdapter(); - assertFalse(adapter.parse("text", context).isPresent()); + assertFalse(adapter.apply("text", context).isPresent()); } @Test public void floatAdapter_withNumber_ShouldReturnDouble() { FloatAdapter adapter = new FloatAdapter(); - assertEquals(Float.MIN_VALUE, adapter.parse(String.valueOf(Float.MIN_VALUE), context).orElseThrow()); - assertEquals(Float.MIN_EXPONENT, adapter.parse(String.valueOf(Float.MIN_EXPONENT), context).orElseThrow()); - assertEquals(Float.MAX_VALUE, adapter.parse(String.valueOf(Float.MAX_VALUE), context).orElseThrow()); - assertEquals(Float.MAX_EXPONENT, adapter.parse(String.valueOf(Float.MAX_EXPONENT), context).orElseThrow()); + assertEquals(Float.MIN_VALUE, adapter.apply(String.valueOf(Float.MIN_VALUE), context).orElseThrow()); + assertEquals(Float.MIN_EXPONENT, adapter.apply(String.valueOf(Float.MIN_EXPONENT), context).orElseThrow()); + assertEquals(Float.MAX_VALUE, adapter.apply(String.valueOf(Float.MAX_VALUE), context).orElseThrow()); + assertEquals(Float.MAX_EXPONENT, adapter.apply(String.valueOf(Float.MAX_EXPONENT), context).orElseThrow()); } @Test public void floatAdapter_withNonNumeric_ShouldBeEmpty() { FloatAdapter adapter = new FloatAdapter(); - assertFalse(adapter.parse("text", context).isPresent()); + assertFalse(adapter.apply("text", context).isPresent()); } @Test public void integerAdapter_withNumber_ShouldReturnDouble() { IntegerAdapter adapter = new IntegerAdapter(); - assertEquals(Integer.MIN_VALUE, adapter.parse(String.valueOf(Integer.MIN_VALUE), context).orElseThrow()); - assertEquals(Integer.MAX_VALUE, adapter.parse(String.valueOf(Integer.MAX_VALUE), context).orElseThrow()); + assertEquals(Integer.MIN_VALUE, adapter.apply(String.valueOf(Integer.MIN_VALUE), context).orElseThrow()); + assertEquals(Integer.MAX_VALUE, adapter.apply(String.valueOf(Integer.MAX_VALUE), context).orElseThrow()); } @Test public void integerAdapter_withNonNumeric_ShouldBeEmpty() { IntegerAdapter adapter = new IntegerAdapter(); - assertFalse(adapter.parse("text", context).isPresent()); + assertFalse(adapter.apply("text", context).isPresent()); } @Test public void longAdapter_withNumber_ShouldReturnDouble() { LongAdapter adapter = new LongAdapter(); - assertEquals(Long.MIN_VALUE, adapter.parse(String.valueOf(Long.MIN_VALUE), context).orElseThrow()); - assertEquals(Long.MAX_VALUE, adapter.parse(String.valueOf(Long.MAX_VALUE), context).orElseThrow()); + assertEquals(Long.MIN_VALUE, adapter.apply(String.valueOf(Long.MIN_VALUE), context).orElseThrow()); + assertEquals(Long.MAX_VALUE, adapter.apply(String.valueOf(Long.MAX_VALUE), context).orElseThrow()); } @Test public void longAdapter_withNonNumeric_ShouldBeEmpty() { LongAdapter adapter = new LongAdapter(); - assertFalse(adapter.parse("text", context).isPresent()); + assertFalse(adapter.apply("text", context).isPresent()); } @Test public void shortAdapter_withNumber_ShouldReturnDouble() { ShortAdapter adapter = new ShortAdapter(); - assertEquals(Short.MIN_VALUE, adapter.parse(String.valueOf(Short.MIN_VALUE), context).orElseThrow()); - assertEquals(Short.MAX_VALUE, adapter.parse(String.valueOf(Short.MAX_VALUE), context).orElseThrow()); + assertEquals(Short.MIN_VALUE, adapter.apply(String.valueOf(Short.MIN_VALUE), context).orElseThrow()); + assertEquals(Short.MAX_VALUE, adapter.apply(String.valueOf(Short.MAX_VALUE), context).orElseThrow()); } @Test public void shortAdapter_withNonNumeric_ShouldBeEmpty() { ShortAdapter adapter = new ShortAdapter(); - assertFalse(adapter.parse("text", context).isPresent()); + assertFalse(adapter.apply("text", context).isPresent()); } @Test public void adapter_withExistingId_ShouldReturnUser() { UserAdapter adapter = new UserAdapter(); - assertEquals(JDAMock.USER, adapter.parse(String.valueOf(JDAMock.USER.getIdLong()), context).orElseThrow()); + assertEquals(JDAMock.USER, adapter.apply(String.valueOf(JDAMock.USER.getIdLong()), context).orElseThrow()); } @Test public void adapter_withNonExistingId_ShouldBeEmpty() { UserAdapter adapter = new UserAdapter(); - assertFalse(adapter.parse("1234567890", context).isPresent()); + assertFalse(adapter.apply("1234567890", context).isPresent()); } @Test public void adapter_withExistingName_ShouldReturnUser() { UserAdapter adapter = new UserAdapter(); - assertEquals(JDAMock.USER, adapter.parse(JDAMock.USER.getName(), context).orElseThrow()); + assertEquals(JDAMock.USER, adapter.apply(JDAMock.USER.getName(), context).orElseThrow()); } @Test public void adapter_withNonExistingName_ShouldBeEmpty() { UserAdapter adapter = new UserAdapter(); - assertFalse(adapter.parse("thispersondoesnotexist.com", context).isPresent()); + assertFalse(adapter.apply("thispersondoesnotexist.com", context).isPresent()); } @Test public void memberAdapter_withExistingId_ShouldReturnMember() { MemberAdapter adapter = new MemberAdapter(); - assertEquals(GuildMock.MEMBER, adapter.parse(String.valueOf(GuildMock.MEMBER.getIdLong()), context).orElseThrow()); + assertEquals(GuildMock.MEMBER, adapter.apply(String.valueOf(GuildMock.MEMBER.getIdLong()), context).orElseThrow()); } @Test public void memberAdapter_withNonExistingId_ShouldBeEmpty() { MemberAdapter adapter = new MemberAdapter(); - assertFalse(adapter.parse("1234567890", context).isPresent()); + assertFalse(adapter.apply("1234567890", context).isPresent()); } @Test public void memberAdapter_withExistingName_ShouldReturnMember() { MemberAdapter adapter = new MemberAdapter(); - assertEquals(GuildMock.MEMBER, adapter.parse(GuildMock.MEMBER.getNickname(), context).orElseThrow()); + assertEquals(GuildMock.MEMBER, adapter.apply(GuildMock.MEMBER.getNickname(), context).orElseThrow()); } @Test public void memberAdapter_withNonExistingName_ShouldBeEmpty() { MemberAdapter adapter = new MemberAdapter(); - assertFalse(adapter.parse("thispersondoesnotexist.com", context).isPresent()); + assertFalse(adapter.apply("thispersondoesnotexist.com", context).isPresent()); } @Test public void memberAdapter_inNotGuildContext_ShouldBeEmpty() { MemberAdapter adapter = new MemberAdapter(); - assertFalse(adapter.parse("name", context).isPresent()); + assertFalse(adapter.apply("name", context).isPresent()); } @Test public void roleAdapter_withExistingId_ShouldReturnMember() { RoleAdapter adapter = new RoleAdapter(); - assertEquals(GuildMock.ROLE, adapter.parse(String.valueOf(GuildMock.ROLE.getIdLong()), context).orElseThrow()); + assertEquals(GuildMock.ROLE, adapter.apply(String.valueOf(GuildMock.ROLE.getIdLong()), context).orElseThrow()); } @Test public void roleAdapter_withNonExistingId_ShouldBeEmpty() { RoleAdapter adapter = new RoleAdapter(); - assertFalse(adapter.parse("1234567890", context).isPresent()); + assertFalse(adapter.apply("1234567890", context).isPresent()); } @Test public void roleAdapter_withExistingName_ShouldReturnMember() { RoleAdapter adapter = new RoleAdapter(); - assertEquals(GuildMock.ROLE, adapter.parse(GuildMock.ROLE.getName(), context).orElseThrow()); + assertEquals(GuildMock.ROLE, adapter.apply(GuildMock.ROLE.getName(), context).orElseThrow()); } @Test public void roleAdapter_withNonExistingName_ShouldBeEmpty() { RoleAdapter adapter = new RoleAdapter(); - assertFalse(adapter.parse("thispersondoesnotexist.com", context).isPresent()); + assertFalse(adapter.apply("thispersondoesnotexist.com", context).isPresent()); } @Test public void roleAdapter_inNotGuildContext_ShouldBeEmpty() { RoleAdapter adapter = new RoleAdapter(); - assertFalse(adapter.parse("name", context).isPresent()); + assertFalse(adapter.apply("name", context).isPresent()); } @Test public void textChannelAdapter_withExistingId_ShouldReturnMember() { TextChannelAdapter adapter = new TextChannelAdapter(); - assertEquals(GuildMock.TEXT_CHANNEL, adapter.parse(String.valueOf(GuildMock.TEXT_CHANNEL.getIdLong()), context).orElseThrow()); + assertEquals(GuildMock.TEXT_CHANNEL, adapter.apply(String.valueOf(GuildMock.TEXT_CHANNEL.getIdLong()), context).orElseThrow()); } @Test public void textChannelAdapter_withNonExistingId_ShouldBeEmpty() { TextChannelAdapter adapter = new TextChannelAdapter(); - assertFalse(adapter.parse("1234567890", context).isPresent()); + assertFalse(adapter.apply("1234567890", context).isPresent()); } @Test public void textChannelAdapter_withExistingName_ShouldReturnMember() { TextChannelAdapter adapter = new TextChannelAdapter(); - assertEquals(GuildMock.TEXT_CHANNEL, adapter.parse(GuildMock.TEXT_CHANNEL.getName(), context).orElseThrow()); + assertEquals(GuildMock.TEXT_CHANNEL, adapter.apply(GuildMock.TEXT_CHANNEL.getName(), context).orElseThrow()); } @Test public void textChannelAdapter_withNonExistingName_ShouldBeEmpty() { TextChannelAdapter adapter = new TextChannelAdapter(); - assertFalse(adapter.parse("thispersondoesnotexist.com", context).isPresent()); + assertFalse(adapter.apply("thispersondoesnotexist.com", context).isPresent()); } @Test public void textChannelAdapter_inNotGuildContext_ShouldBeEmpty() { TextChannelAdapter adapter = new TextChannelAdapter(); - assertFalse(adapter.parse("name", context).isPresent()); + assertFalse(adapter.apply("name", context).isPresent()); } @Test public void sanitizeMention__() { - MemberAdapter memberAdapter = new MemberAdapter(); - - assertEquals("1234", memberAdapter.sanitizeMention("<@1234>")); - assertEquals("1234", memberAdapter.sanitizeMention("<@!1234>")); - assertEquals("1234", memberAdapter.sanitizeMention("<#1234>")); - assertEquals("1234", memberAdapter.sanitizeMention("<@&1234>")); - - assertEquals("<@1234", memberAdapter.sanitizeMention("<@1234")); - assertEquals("<@!1234", memberAdapter.sanitizeMention("<@!1234")); - assertEquals("<#1234", memberAdapter.sanitizeMention("<#1234")); - assertEquals("<@&1234", memberAdapter.sanitizeMention("<@&1234")); - - assertEquals("<@text>", memberAdapter.sanitizeMention("<@text>")); - assertEquals("<@!text>", memberAdapter.sanitizeMention("<@!text>")); - assertEquals("<#text>", memberAdapter.sanitizeMention("<#text>")); - assertEquals("<@&text>", memberAdapter.sanitizeMention("<@&text>")); - - assertEquals("<1234>", memberAdapter.sanitizeMention("<1234>")); - assertEquals("<1234", memberAdapter.sanitizeMention("<1234")); - assertEquals("1234>", memberAdapter.sanitizeMention("1234>")); + assertEquals("1234", Helpers.sanitizeMention("<@1234>")); + assertEquals("1234", Helpers.sanitizeMention("<@!1234>")); + assertEquals("1234", Helpers.sanitizeMention("<#1234>")); + assertEquals("1234", Helpers.sanitizeMention("<@&1234>")); + + assertEquals("<@1234", Helpers.sanitizeMention("<@1234")); + assertEquals("<@!1234", Helpers.sanitizeMention("<@!1234")); + assertEquals("<#1234", Helpers.sanitizeMention("<#1234")); + assertEquals("<@&1234", Helpers.sanitizeMention("<@&1234")); + + assertEquals("<@text>", Helpers.sanitizeMention("<@text>")); + assertEquals("<@!text>", Helpers.sanitizeMention("<@!text>")); + assertEquals("<#text>", Helpers.sanitizeMention("<#text>")); + assertEquals("<@&text>", Helpers.sanitizeMention("<@&text>")); + + assertEquals("<1234>", Helpers.sanitizeMention("<1234>")); + assertEquals("<1234", Helpers.sanitizeMention("<1234")); + assertEquals("1234>", Helpers.sanitizeMention("1234>")); } } diff --git a/src/test/java/adapting/mock/GuildMock.java b/src/test/java/adapting/mock/GuildMock.java index 6e6340ea2..11eafa5c7 100644 --- a/src/test/java/adapting/mock/GuildMock.java +++ b/src/test/java/adapting/mock/GuildMock.java @@ -624,10 +624,18 @@ public SnowflakeCacheView getMediaChannelCache() { return null; } + @Override + public @Nullable GuildChannel getGuildChannelById(long id) { + if (id == TEXT_CHANNEL.getIdLong()) { + return TEXT_CHANNEL; + } + return null; + } + @NotNull @Override public List getChannels(boolean b) { - return null; + return List.of(TEXT_CHANNEL); } @Nullable diff --git a/src/test/java/adapting/mock/JDACommandsMock.java b/src/test/java/adapting/mock/JDACommandsMock.java index d4dff2f2a..052cd48c8 100644 --- a/src/test/java/adapting/mock/JDACommandsMock.java +++ b/src/test/java/adapting/mock/JDACommandsMock.java @@ -1,19 +1,25 @@ package adapting.mock; -import com.github.kaktushose.jda.commands.JDACommands; -import com.github.kaktushose.jda.commands.dependency.DefaultDependencyInjector; +import com.github.kaktushose.jda.commands.JDAContext; +import com.github.kaktushose.jda.commands.SlashCommandUpdater; +import com.github.kaktushose.jda.commands.dependency.DependencyInjector; +import com.github.kaktushose.jda.commands.dispatching.DispatcherSupervisor; +import com.github.kaktushose.jda.commands.dispatching.RuntimeSupervisor; import com.github.kaktushose.jda.commands.dispatching.adapter.TypeAdapterRegistry; import com.github.kaktushose.jda.commands.dispatching.middleware.MiddlewareRegistry; import com.github.kaktushose.jda.commands.dispatching.validation.ValidatorRegistry; -import com.github.kaktushose.jda.commands.reflect.ImplementationRegistry; +import com.github.kaktushose.jda.commands.reflect.InteractionRegistry; -public class JDACommandsMock extends JDACommands { - public JDACommandsMock() { - } - - @Override - public ImplementationRegistry getImplementationRegistry() { - return new ImplementationRegistry(new DefaultDependencyInjector(), new MiddlewareRegistry(), new TypeAdapterRegistry(), new ValidatorRegistry()); - } +public record JDACommandsMock( + JDAContext jdaContext, + DispatcherSupervisor dispatcherSupervisor, + MiddlewareRegistry middlewareRegistry, + TypeAdapterRegistry adapterRegistry, + ValidatorRegistry validatorRegistry, + DependencyInjector dependencyInjector, + InteractionRegistry interactionRegistry, + SlashCommandUpdater updater, + RuntimeSupervisor runtimeSupervisor +) { } diff --git a/src/test/java/commands/CommandDefinitionTestController.java b/src/test/java/commands/CommandDefinitionTestController.java index 8cbe29770..ed1ac08c6 100644 --- a/src/test/java/commands/CommandDefinitionTestController.java +++ b/src/test/java/commands/CommandDefinitionTestController.java @@ -51,11 +51,6 @@ public void optionalAfterOptional(CommandEvent event, @Optional String s, @Optio } - @SlashCommand(value = "k", isActive = false) - public void inactive() { - - } - @SlashCommand("m") @Cooldown(value = 0, timeUnit = TimeUnit.MILLISECONDS) public void zeroCooldown(CommandEvent event) { diff --git a/src/test/java/commands/SlashCommandDefinitionTest.java b/src/test/java/commands/SlashCommandDefinitionTest.java index 447ed0737..eab854146 100644 --- a/src/test/java/commands/SlashCommandDefinitionTest.java +++ b/src/test/java/commands/SlashCommandDefinitionTest.java @@ -1,10 +1,12 @@ package commands; -import com.github.kaktushose.jda.commands.dispatching.interactions.commands.CommandEvent; +import com.github.kaktushose.jda.commands.annotations.interactions.Interaction; import com.github.kaktushose.jda.commands.dispatching.adapter.TypeAdapterRegistry; +import com.github.kaktushose.jda.commands.dispatching.interactions.commands.CommandEvent; import com.github.kaktushose.jda.commands.dispatching.validation.ValidatorRegistry; +import com.github.kaktushose.jda.commands.reflect.CooldownDefinition; +import com.github.kaktushose.jda.commands.reflect.MethodBuildContext; import com.github.kaktushose.jda.commands.reflect.interactions.commands.SlashCommandDefinition; -import net.dv8tion.jda.api.interactions.commands.localization.LocalizationFunction; import net.dv8tion.jda.api.interactions.commands.localization.ResourceBundleLocalizationFunction; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -12,14 +14,13 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Optional; +import java.util.Set; import java.util.concurrent.TimeUnit; import static org.junit.jupiter.api.Assertions.*; public class SlashCommandDefinitionTest { - private static final LocalizationFunction LOCALIZATION_FUNCTION = ResourceBundleLocalizationFunction.empty().build(); - private static Class controller; private static ValidatorRegistry validator; @@ -34,95 +35,107 @@ public static void setup() { adapter.unregister(UnsupportedType.class); } + public static MethodBuildContext getBuildContext(Method method) { + return new MethodBuildContext( + validator, + ResourceBundleLocalizationFunction.empty().build(), + controller.getAnnotation(Interaction.class), + Set.of(), + CooldownDefinition.build(null), + method, + Set.of() + ); + } + @Test public void method_withoutAnnotation_ShouldReturnEmpty() throws NoSuchMethodException { Method method = controller.getDeclaredMethod("noAnnotation"); - assertEquals(Optional.empty(), SlashCommandDefinition.build(method, validator, LOCALIZATION_FUNCTION)); + assertEquals(Optional.empty(), SlashCommandDefinition.build(getBuildContext(method))); } @Test public void method_withoutArgs_ShouldReturnEmpty() throws NoSuchMethodException { Method method = controller.getDeclaredMethod("noArgs"); - assertEquals(Optional.empty(), SlashCommandDefinition.build(method, validator, LOCALIZATION_FUNCTION)); + assertEquals(Optional.empty(), SlashCommandDefinition.build(getBuildContext(method))); } @Test public void method_withoutCommandEvent_ShouldReturnEmpty() throws NoSuchMethodException { Method method = controller.getDeclaredMethod("noCommandEvent", int.class); - assertEquals(Optional.empty(), SlashCommandDefinition.build(method, validator, LOCALIZATION_FUNCTION)); + assertEquals(Optional.empty(), SlashCommandDefinition.build(getBuildContext(method))); } @Test public void method_withWrongCommandEvent_ShouldReturnEmpty() throws NoSuchMethodException { Method method = controller.getDeclaredMethod("wrongCommandEvent", int.class, CommandEvent.class); - assertEquals(Optional.empty(), SlashCommandDefinition.build(method, validator, LOCALIZATION_FUNCTION)); + assertEquals(Optional.empty(), SlashCommandDefinition.build(getBuildContext(method))); } @Test public void method_withCommandEvent_ShouldWork() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { Method method = controller.getDeclaredMethod("commandEvent", CommandEvent.class); - SlashCommandDefinition definition = SlashCommandDefinition.build(method, validator, LOCALIZATION_FUNCTION).orElse(null); + SlashCommandDefinition definition = SlashCommandDefinition.build(getBuildContext(method)).orElse(null); assertNotNull(definition); assertEquals(definition.getMethod(), method); assertEquals(1, definition.getParameters().size()); - assertEquals(CommandEvent.class, definition.getParameters().get(0).getType()); + assertEquals(CommandEvent.class, definition.getParameters().get(0).type()); } @Test public void method_withUnsupportedType_ShouldWork() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { Method method = controller.getDeclaredMethod("unsupported", CommandEvent.class, UnsupportedType.class); - SlashCommandDefinition definition = SlashCommandDefinition.build(method, validator, LOCALIZATION_FUNCTION).orElse(null); + SlashCommandDefinition definition = SlashCommandDefinition.build(getBuildContext(method)).orElse(null); assertNotNull(definition); assertEquals(definition.getMethod(), method); assertEquals(2, definition.getParameters().size()); - assertEquals(CommandEvent.class, definition.getParameters().get(0).getType()); - assertEquals(UnsupportedType.class, definition.getParameters().get(1).getType()); + assertEquals(CommandEvent.class, definition.getParameters().get(0).type()); + assertEquals(UnsupportedType.class, definition.getParameters().get(1).type()); } @Test public void method_withStringArray_ShouldWork() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { Method method = controller.getDeclaredMethod("arrayArgument", CommandEvent.class, String[].class); - SlashCommandDefinition definition = SlashCommandDefinition.build(method, validator, LOCALIZATION_FUNCTION).orElse(null); + SlashCommandDefinition definition = SlashCommandDefinition.build(getBuildContext(method)).orElse(null); assertNotNull(definition); assertEquals(definition.getMethod(), method); assertEquals(2, definition.getParameters().size()); - assertEquals(CommandEvent.class, definition.getParameters().get(0).getType()); - assertEquals(String[].class, definition.getParameters().get(1).getType()); + assertEquals(CommandEvent.class, definition.getParameters().get(0).type()); + assertEquals(String[].class, definition.getParameters().get(1).type()); } @Test public void method_withArgumentsAfterArray_ShouldReturnEmpty() throws NoSuchMethodException { Method method = controller.getDeclaredMethod("argsAfterArray", CommandEvent.class, String[].class, int.class); - assertEquals(Optional.empty(), SlashCommandDefinition.build(method, validator, LOCALIZATION_FUNCTION)); + assertEquals(Optional.empty(), SlashCommandDefinition.build(getBuildContext(method))); } @Test public void method_withArgumentsAfterOptional_ShouldWork() throws NoSuchMethodException { Method method = controller.getDeclaredMethod("argsAfterOptional", CommandEvent.class, String.class, int.class); - SlashCommandDefinition definition = SlashCommandDefinition.build(method, validator, LOCALIZATION_FUNCTION).orElse(null); + SlashCommandDefinition definition = SlashCommandDefinition.build(getBuildContext(method)).orElse(null); assertNotNull(definition); assertEquals(definition.getMethod(), method); assertEquals(3, definition.getParameters().size()); - assertEquals(CommandEvent.class, definition.getParameters().get(0).getType()); + assertEquals(CommandEvent.class, definition.getParameters().get(0).type()); assertTrue(definition.getParameters().get(1).isOptional()); assertFalse(definition.getParameters().get(2).isOptional()); } @@ -130,29 +143,22 @@ public void method_withArgumentsAfterOptional_ShouldWork() throws NoSuchMethodEx @Test public void method_withOptionalAfterOptional_ShouldWork() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { Method method = controller.getDeclaredMethod("optionalAfterOptional", CommandEvent.class, String.class, int.class); - SlashCommandDefinition definition = SlashCommandDefinition.build(method, validator, LOCALIZATION_FUNCTION).orElse(null); + SlashCommandDefinition definition = SlashCommandDefinition.build(getBuildContext(method)).orElse(null); assertNotNull(definition); assertEquals(definition.getMethod(), method); assertEquals(3, definition.getParameters().size()); - assertEquals(CommandEvent.class, definition.getParameters().get(0).getType()); - assertEquals(String.class, definition.getParameters().get(1).getType()); - assertEquals(Integer.class, definition.getParameters().get(2).getType()); - } - - @Test - public void command_isInactive_ShouldReturnEmpty() throws NoSuchMethodException { - Method method = controller.getDeclaredMethod("inactive"); - - assertEquals(Optional.empty(), SlashCommandDefinition.build(method, validator, LOCALIZATION_FUNCTION)); + assertEquals(CommandEvent.class, definition.getParameters().get(0).type()); + assertEquals(String.class, definition.getParameters().get(1).type()); + assertEquals(Integer.class, definition.getParameters().get(2).type()); } @Test public void cooldown_zeroTimeUnits_ShouldNotBeSet() throws NoSuchMethodException { Method method = controller.getDeclaredMethod("zeroCooldown", CommandEvent.class); - SlashCommandDefinition definition = SlashCommandDefinition.build(method, validator, LOCALIZATION_FUNCTION).orElse(null); + SlashCommandDefinition definition = SlashCommandDefinition.build(getBuildContext(method)).orElse(null); assertNotNull(definition); @@ -162,19 +168,19 @@ public void cooldown_zeroTimeUnits_ShouldNotBeSet() throws NoSuchMethodException @Test public void cooldown_tenMilliseconds_ShouldWork() throws NoSuchMethodException { Method method = controller.getDeclaredMethod("cooldown", CommandEvent.class); - SlashCommandDefinition definition = SlashCommandDefinition.build(method, validator, LOCALIZATION_FUNCTION).orElse(null); + SlashCommandDefinition definition = SlashCommandDefinition.build(getBuildContext(method)).orElse(null); assertNotNull(definition); assertTrue(definition.hasCooldown()); - assertEquals(10, definition.getCooldown().getDelay()); - assertEquals(TimeUnit.MILLISECONDS, definition.getCooldown().getTimeUnit()); + assertEquals(10, definition.getCooldown().delay()); + assertEquals(TimeUnit.MILLISECONDS, definition.getCooldown().timeUnit()); } @Test public void permission_oneString_ShouldWork() throws NoSuchMethodException { Method method = controller.getDeclaredMethod("permission", CommandEvent.class); - SlashCommandDefinition definition = SlashCommandDefinition.build(method, validator, LOCALIZATION_FUNCTION).orElse(null); + SlashCommandDefinition definition = SlashCommandDefinition.build(getBuildContext(method)).orElse(null); assertNotNull(definition); diff --git a/src/test/java/controller/InteractionControllerDefinitionTest.java b/src/test/java/controller/InteractionControllerDefinitionTest.java index e42070feb..98346f961 100644 --- a/src/test/java/controller/InteractionControllerDefinitionTest.java +++ b/src/test/java/controller/InteractionControllerDefinitionTest.java @@ -1,13 +1,12 @@ package controller; import com.github.kaktushose.jda.commands.dependency.DefaultDependencyInjector; -import com.github.kaktushose.jda.commands.dispatching.interactions.commands.CommandEvent; import com.github.kaktushose.jda.commands.dispatching.adapter.TypeAdapterRegistry; +import com.github.kaktushose.jda.commands.dispatching.interactions.commands.CommandEvent; import com.github.kaktushose.jda.commands.dispatching.validation.ValidatorRegistry; -import com.github.kaktushose.jda.commands.reflect.interactions.commands.SlashCommandDefinition; import com.github.kaktushose.jda.commands.reflect.InteractionControllerDefinition; +import com.github.kaktushose.jda.commands.reflect.interactions.commands.SlashCommandDefinition; import commands.UnsupportedType; -import net.dv8tion.jda.api.interactions.commands.Command; import net.dv8tion.jda.api.interactions.commands.localization.LocalizationFunction; import net.dv8tion.jda.api.interactions.commands.localization.ResourceBundleLocalizationFunction; import org.junit.jupiter.api.BeforeAll; @@ -43,11 +42,17 @@ public static void setup() { public void command_NoValues_ShouldAdoptControllerValues() throws NoSuchMethodException { Method method = controller.getDeclaredMethod("adopt", CommandEvent.class); - InteractionControllerDefinition controllerDefinition = InteractionControllerDefinition.build(controller, validators, dependencyInjector, LOCALIZATION_FUNCTION).orElse(null); + InteractionControllerDefinition controllerDefinition = InteractionControllerDefinition.build( + controller, + validators, + dependencyInjector, + LOCALIZATION_FUNCTION + ); + assertNotNull(controllerDefinition); - SlashCommandDefinition definition = controllerDefinition.getCommands().stream() - .filter(it -> it.getCommandType() == Command.Type.SLASH) - .map(it -> (SlashCommandDefinition) it) + SlashCommandDefinition definition = controllerDefinition.definitions().stream() + .filter(SlashCommandDefinition.class::isInstance) + .map(SlashCommandDefinition.class::cast) .filter(c -> c.getMethod().equals(method)) .findFirst().orElse(null); assertNotNull(definition); @@ -56,8 +61,8 @@ public void command_NoValues_ShouldAdoptControllerValues() throws NoSuchMethodEx assertTrue(definition.getName().contains("super")); assertTrue(definition.hasCooldown()); - assertEquals(10, definition.getCooldown().getDelay()); - assertEquals(TimeUnit.MILLISECONDS, definition.getCooldown().getTimeUnit()); + assertEquals(10, definition.getCooldown().delay()); + assertEquals(TimeUnit.MILLISECONDS, definition.getCooldown().timeUnit()); assertEquals(1, definition.getPermissions().size()); assertTrue(definition.getPermissions().contains("superPermission")); @@ -67,11 +72,17 @@ public void command_NoValues_ShouldAdoptControllerValues() throws NoSuchMethodEx public void command_OwnValues_ShouldCombineOrOverride() throws NoSuchMethodException { Method method = controller.getDeclaredMethod("combine", CommandEvent.class); - InteractionControllerDefinition controllerDefinition = InteractionControllerDefinition.build(controller, validators, dependencyInjector, LOCALIZATION_FUNCTION).orElse(null); + InteractionControllerDefinition controllerDefinition = InteractionControllerDefinition.build( + controller, + validators, + dependencyInjector, + LOCALIZATION_FUNCTION + ); + assertNotNull(controllerDefinition); - SlashCommandDefinition definition = controllerDefinition.getCommands().stream() - .filter(it -> it.getCommandType() == Command.Type.SLASH) - .map(it -> (SlashCommandDefinition) it) + SlashCommandDefinition definition = controllerDefinition.definitions().stream() + .filter(SlashCommandDefinition.class::isInstance) + .map(SlashCommandDefinition.class::cast) .filter(c -> c.getMethod().equals(method)) .findFirst().orElse(null); assertNotNull(definition); @@ -79,8 +90,8 @@ public void command_OwnValues_ShouldCombineOrOverride() throws NoSuchMethodExcep assertEquals("super sub", definition.getName()); assertTrue(definition.hasCooldown()); - assertEquals(5, definition.getCooldown().getDelay()); - assertEquals(TimeUnit.DAYS, definition.getCooldown().getTimeUnit()); + assertEquals(5, definition.getCooldown().delay()); + assertEquals(TimeUnit.DAYS, definition.getCooldown().timeUnit()); assertEquals(2, definition.getPermissions().size()); assertTrue(definition.getPermissions().contains("superPermission")); diff --git a/src/test/java/parameters/ParameterDefinitionTest.java b/src/test/java/parameters/ParameterDefinitionTest.java index be604ba68..1f365390a 100644 --- a/src/test/java/parameters/ParameterDefinitionTest.java +++ b/src/test/java/parameters/ParameterDefinitionTest.java @@ -34,7 +34,7 @@ public void method_withPrimitiveInt_ShouldWrap() throws NoSuchMethodException { Method method = controller.getDeclaredMethod("primitives", int.class); ParameterDefinition parameter = ParameterDefinition.build(method.getParameters()[0], validatorRegistry); - assertEquals(Integer.class, parameter.getType()); + assertEquals(Integer.class, parameter.type()); assertTrue(parameter.isPrimitive()); } @@ -44,7 +44,7 @@ public void optional_withoutDefault_ShouldBeNull() throws NoSuchMethodException ParameterDefinition parameter = ParameterDefinition.build(method.getParameters()[0], validatorRegistry); assertTrue(parameter.isOptional()); - assertNull(parameter.getDefaultValue()); + assertNull(parameter.defaultValue()); } @Test @@ -53,7 +53,7 @@ public void optional_withDefault_ShouldWork() throws NoSuchMethodException { ParameterDefinition parameter = ParameterDefinition.build(method.getParameters()[0], validatorRegistry); assertTrue(parameter.isOptional()); - assertEquals("default", parameter.getDefaultValue()); + assertEquals("default", parameter.defaultValue()); } @Test @@ -61,10 +61,10 @@ public void constraintMin_withLimit10_ShouldWork() throws NoSuchMethodException Method method = controller.getDeclaredMethod("constraint", int.class); ParameterDefinition parameter = ParameterDefinition.build(method.getParameters()[0], validatorRegistry); - assertEquals(1, parameter.getConstraints().size()); - assertEquals(10, ((Min) parameter.getConstraints().get(0).getAnnotation()).value()); - assertEquals(MinimumValidator.class, parameter.getConstraints().get(0).getValidator().getClass()); - assertFalse(parameter.getConstraints().get(0).getMessage().isEmpty()); + assertEquals(1, parameter.constraints().size()); + assertEquals(10, ((Min) parameter.constraints().get(0).annotation()).value()); + assertEquals(MinimumValidator.class, parameter.constraints().get(0).validator().getClass()); + assertFalse(parameter.constraints().get(0).message().isEmpty()); } @Test @@ -72,6 +72,6 @@ public void constraint_withMessage_ShouldWork() throws NoSuchMethodException { Method method = controller.getDeclaredMethod("constraintWithMessage", int.class); ParameterDefinition parameter = ParameterDefinition.build(method.getParameters()[0], validatorRegistry); - assertEquals("error message", parameter.getConstraints().get(0).getMessage()); + assertEquals("error message", parameter.constraints().get(0).message()); } } From d75b8db7ebf62352c4df284275f2f2009709fd02 Mon Sep 17 00:00:00 2001 From: Kaktushose <42280757+Kaktushose@users.noreply.github.com> Date: Wed, 25 Dec 2024 17:38:27 +0100 Subject: [PATCH 13/38] [REFACTOR] Internal dispatching flow and reply API (#141) * first pass on VirtualThread refactor * refactor CommandDispatcher * prepare Dispatchers for refactoring and reimplement AutoCompleteDispatcher * Make Runtime closeable * Add Event abstraction over jda and EventHandlers for command and autocomplete * introduce new ExecutionContext and simplify CommandHandler * Use new ExecutionContext in middleware and adapters, restructure EventHandler * Delete JDa Event wrapper * remove CommandExecutionContext.java and remove context from type adapting flow * delete old files * fix type errors * move method invoke to definition classes * Move classes out of refactor sub package * Refactor ExecutionContext and stuff * fix wrong middleware priority * Add KeyValueStore and missing package-info.java files * Add KeyValueStore and missing package-info.java files * Fix double method arguments * Reduce generics complexity * Refactor MiddlewareRegistry * Split ExecutionContext.java * refactor event classes and reply logic * fix middleware order bug * make components attachable again * reimplement replyModal * first pass on ButtonHandler * seal EventHandler * seal EventHandler * Merge InvocationContext and Invocation * refactor reply api * Add getter for JDA Events in Event * make "No interaction found" error message less ambiguous * Refactor reply system (#142) * make reply methods return Message object and reimplement keep components * remove latest reply from runtime * replace ephemeral boolean flag with ReplyConfig and introduce GlobalReplyConfig * write unit tests for new ReplyConfig helper method and fix old unit tests * simplify switch * Hopefully fix middleware priorities * Move HandlerContext out of InvocationContext * Move InstanceSupplier out of InvocationContext * bump code * Explicitly state dispatching user api * Yes.. * fully implement modals and components * first pass on static components * Fix static components, refactor component reply * improve error handling * Remove unused method * Add docs for context, handling and Runtime * fix custom id bugs * write documentation * more documentation and simplify component reply * even more documentation * even more documentation * formatting * add missing method * fix component bug * more documentation * small docs fix * Add logging for EventHandler, JDAEventListener and Runtime * Add ExpirationStrategy * stuff * refactor package structure * add docs for ExpirationStrategy * Expose dispatching package * Add jda javadoc reference to maven java doc plugin * Update src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/Interaction.java Co-authored-by: Nick Hensel <47005420+Goldmensch@users.noreply.github.com> * Update src/main/java/com/github/kaktushose/jda/commands/annotations/Implementation.java Co-authored-by: Nick Hensel <47005420+Goldmensch@users.noreply.github.com> * Update src/main/java/com/github/kaktushose/jda/commands/dispatching/events/ReplyableEvent.java Co-authored-by: Nick Hensel <47005420+Goldmensch@users.noreply.github.com> * Update src/main/java/com/github/kaktushose/jda/commands/dispatching/reply/MessageReply.java Co-authored-by: Nick Hensel <47005420+Goldmensch@users.noreply.github.com> * Update src/main/java/com/github/kaktushose/jda/commands/dispatching/handling/EventHandler.java Co-authored-by: Nick Hensel <47005420+Goldmensch@users.noreply.github.com> * improve docs * remove generic in getSelectMenu * rename Components to Component * update documentation * update documentation * update lastActivity * Add flowchart picture and introduce javadoc directory * resolve review * Add examples --------- Co-authored-by: Nick Hensel Co-authored-by: Nick Hensel <47005420+Goldmensch@users.noreply.github.com> --- .github/workflows/docs.yml | 4 +- pom.xml | 7 +- src/examples/ReportCommand.java | 1 + .../kaktushose/jda/commands/Helpers.java | 91 ----- .../kaktushose/jda/commands/JDACommands.java | 170 +++------ .../commands/annotations/Implementation.java | 5 +- .../interactions/AutoComplete.java | 27 +- .../annotations/interactions/Button.java | 42 +-- .../annotations/interactions/Choices.java | 1 + .../interactions/ContextCommand.java | 45 +-- .../interactions/EntitySelectMenu.java | 44 +-- .../annotations/interactions/Interaction.java | 32 +- .../annotations/interactions/Modal.java | 35 +- .../annotations/interactions/Optional.java | 1 + .../annotations/interactions/ReplyConfig.java | 63 ++++ .../interactions/SelectOption.java | 1 - .../interactions/SlashCommand.java | 62 ++-- .../interactions/StaticInstance.java | 17 - .../interactions/StringSelectMenu.java | 50 +-- .../annotations/interactions/TextInput.java | 15 +- .../jda/commands/data/package-info.java | 4 - .../jda/commands/dependency/package-info.java | 2 +- .../dispatching/DispatcherSupervisor.java | 100 ----- .../dispatching/ExpirationStrategy.java | 29 ++ .../dispatching/RuntimeSupervisor.java | 254 ------------- .../dispatching/adapter/TypeAdapter.java | 12 +- .../adapter/TypeAdapterRegistry.java | 100 +---- .../adapter/impl/AudioChannelAdapter.java | 15 +- .../adapter/impl/BooleanAdapter.java | 9 +- .../dispatching/adapter/impl/ByteAdapter.java | 9 +- .../adapter/impl/CharacterAdapter.java | 9 +- .../adapter/impl/DoubleAdapter.java | 9 +- .../adapter/impl/FloatAdapter.java | 9 +- .../adapter/impl/GuildChannelAdapter.java | 15 +- .../impl/GuildMessageChannelAdapter.java | 15 +- .../adapter/impl/IntegerAdapter.java | 9 +- .../dispatching/adapter/impl/LongAdapter.java | 9 +- .../adapter/impl/MemberAdapter.java | 15 +- .../adapter/impl/NewsChannelAdapter.java | 15 +- .../dispatching/adapter/impl/RoleAdapter.java | 15 +- .../adapter/impl/ShortAdapter.java | 9 +- .../adapter/impl/StageChannelAdapter.java | 15 +- .../adapter/impl/TextChannelAdapter.java | 15 +- .../adapter/impl/ThreadChannelAdapter.java | 15 +- .../dispatching/adapter/impl/UserAdapter.java | 13 +- .../adapter/impl/VoiceChannelAdapter.java | 15 +- .../context/InvocationContext.java | 33 ++ .../{ => context}/KeyValueStore.java | 10 +- .../dispatching/context/package-info.java | 2 + .../commands/dispatching/events/Event.java | 210 +++++++++++ .../events/ModalReplyableEvent.java | 60 +++ .../dispatching/events/ReplyableEvent.java | 105 ++++++ .../interactions}/AutoCompleteEvent.java | 55 +-- .../events/interactions/CommandEvent.java | 42 +++ .../events/interactions/ComponentEvent.java | 43 +++ .../events/interactions/ModalEvent.java | 32 ++ .../events/interactions/package-info.java | 4 + .../dispatching/events/package-info.java | 2 + .../handling/AutoCompleteHandler.java | 33 ++ .../handling/ComponentHandler.java | 52 +++ .../handling/DispatchingContext.java | 17 + .../dispatching/handling/EventHandler.java | 106 ++++++ .../dispatching/handling/ModalHandler.java | 43 +++ .../command/ContextCommandHandler.java | 32 ++ .../handling/command/SlashCommandHandler.java | 107 ++++++ .../handling/command/package-info.java | 1 + .../dispatching/handling/package-info.java | 4 + .../dispatching/interactions/Context.java | 189 ---------- .../interactions/GenericDispatcher.java | 73 ---- .../interactions/GenericEvent.java | 96 ----- .../autocomplete/AutoCompleteDispatcher.java | 81 ---- .../commands/CommandDispatcher.java | 130 ------- .../interactions/commands/CommandEvent.java | 42 --- .../commands/SlashCommandContext.java | 146 -------- .../components/ComponentDispatcher.java | 128 ------- .../components/ComponentEvent.java | 53 --- .../interactions/modals/ModalDispatcher.java | 115 ------ .../interactions/modals/ModalEvent.java | 36 -- .../internal/JDAEventListener.java | 76 ++++ .../dispatching/internal/Runtime.java | 147 ++++++++ .../dispatching/middleware/Middleware.java | 12 +- .../middleware/MiddlewareRegistry.java | 35 +- .../dispatching/middleware/Priority.java | 17 +- .../middleware/impl/ConstraintMiddleware.java | 34 +- .../middleware/impl/CooldownMiddleware.java | 36 +- .../impl/PermissionsMiddleware.java | 23 +- .../middleware/impl/package-info.java | 2 + .../dispatching/middleware/package-info.java | 2 + .../commands/dispatching/reply/Component.java | 65 ++++ .../dispatching/reply/ComponentReply.java | 23 ++ .../dispatching/reply/ConfigurableReply.java | 198 ++++++++++ .../dispatching/reply/GlobalReplyConfig.java | 31 ++ .../dispatching/reply/MessageReply.java | 147 ++++++++ .../dispatching/reply/ModalReplyable.java | 39 -- .../jda/commands/dispatching/reply/Reply.java | 87 +++++ .../dispatching/reply/ReplyContext.java | 246 ------------- .../commands/dispatching/reply/Replyable.java | 347 ------------------ .../dispatching/reply/components/Buttons.java | 59 --- .../reply/components/Component.java | 14 - .../reply/components/SelectMenus.java | 85 ----- .../reply/components/package-info.java | 4 - .../dispatching/validation/Validator.java | 6 +- .../validation/impl/MaximumValidator.java | 6 +- .../validation/impl/MinimumValidator.java | 6 +- .../impl/NotPermissionValidator.java | 6 +- .../validation/impl/NotRoleValidator.java | 8 +- .../validation/impl/NotUserValidator.java | 8 +- .../validation/impl/PermissionValidator.java | 6 +- .../validation/impl/RoleValidator.java | 8 +- .../validation/impl/UserValidator.java | 8 +- .../commands/{data => embeds}/EmbedCache.java | 2 +- .../commands/{data => embeds}/EmbedDTO.java | 6 +- .../commands/embeds/ErrorMessageFactory.java | 77 ---- .../DefaultErrorMessageFactory.java | 45 ++- .../embeds/error/ErrorMessageFactory.java | 72 ++++ .../{ => error}/JsonErrorMessageFactory.java | 57 +-- .../commands/embeds/error/package-info.java | 4 + .../jda/commands/embeds/package-info.java | 6 +- .../jda/commands/internal/Helpers.java | 139 +++++++ .../commands/{ => internal}/JDAContext.java | 2 +- .../register}/CommandTree.java | 4 +- .../register}/SlashCommandUpdater.java | 6 +- .../{data => internal/register}/TreeNode.java | 7 +- .../DefaultPermissionsProvider.java | 41 ++- .../permissions/PermissionsProvider.java | 48 ++- .../reflect/ImplementationRegistry.java | 4 +- .../commands/reflect/InteractionRegistry.java | 28 ++ .../commands/reflect/MethodBuildContext.java | 4 + .../interactions/AutoCompleteDefinition.java | 4 +- .../reflect/interactions/CustomId.java | 97 ++++- .../EphemeralInteractionDefinition.java | 19 +- .../GenericInteractionDefinition.java | 10 +- .../reflect/interactions/ModalDefinition.java | 31 +- .../reflect/interactions/ReplyConfig.java | 24 ++ .../commands/ContextCommandDefinition.java | 25 +- .../commands/GenericCommandDefinition.java | 12 +- .../commands/SlashCommandDefinition.java | 13 +- .../components/ButtonDefinition.java | 13 +- .../GenericComponentDefinition.java | 25 +- .../menus/EntitySelectMenuDefinition.java | 21 +- .../menus/GenericSelectMenuDefinition.java | 28 +- .../menus/StringSelectMenuDefinition.java | 17 +- .../scope/DefaultGuildScopeProvider.java | 1 + src/main/java/module-info.java | 40 ++ src/main/javadoc/doc-files/flowchart.png | Bin 0 -> 129529 bytes src/main/javadoc/overview.md | 65 ++++ src/test/java/adapting/CustomTypeAdapter.java | 5 +- .../adapting/TypeAdapterRegistryTest.java | 347 +++++++++--------- src/test/java/adapting/TypeAdapterTest.java | 113 +++--- .../java/adapting/mock/JDACommandsMock.java | 13 +- .../TypeAdapterRegistryTestController.java | 2 +- .../CommandDefinitionTestController.java | 2 +- .../commands/SlashCommandDefinitionTest.java | 2 +- .../ControllerDefinitionTestController.java | 2 +- .../InteractionControllerDefinitionTest.java | 2 +- src/test/java/reply/CustomController.java | 29 ++ src/test/java/reply/DefaultController.java | 21 ++ src/test/java/reply/ReplyConfigTest.java | 68 ++++ 158 files changed, 3423 insertions(+), 3502 deletions(-) delete mode 100644 src/main/java/com/github/kaktushose/jda/commands/Helpers.java create mode 100644 src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/ReplyConfig.java delete mode 100644 src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/StaticInstance.java delete mode 100644 src/main/java/com/github/kaktushose/jda/commands/data/package-info.java delete mode 100644 src/main/java/com/github/kaktushose/jda/commands/dispatching/DispatcherSupervisor.java create mode 100644 src/main/java/com/github/kaktushose/jda/commands/dispatching/ExpirationStrategy.java delete mode 100644 src/main/java/com/github/kaktushose/jda/commands/dispatching/RuntimeSupervisor.java create mode 100644 src/main/java/com/github/kaktushose/jda/commands/dispatching/context/InvocationContext.java rename src/main/java/com/github/kaktushose/jda/commands/dispatching/{ => context}/KeyValueStore.java (88%) create mode 100644 src/main/java/com/github/kaktushose/jda/commands/dispatching/context/package-info.java create mode 100644 src/main/java/com/github/kaktushose/jda/commands/dispatching/events/Event.java create mode 100644 src/main/java/com/github/kaktushose/jda/commands/dispatching/events/ModalReplyableEvent.java create mode 100644 src/main/java/com/github/kaktushose/jda/commands/dispatching/events/ReplyableEvent.java rename src/main/java/com/github/kaktushose/jda/commands/dispatching/{interactions/autocomplete => events/interactions}/AutoCompleteEvent.java (89%) create mode 100644 src/main/java/com/github/kaktushose/jda/commands/dispatching/events/interactions/CommandEvent.java create mode 100644 src/main/java/com/github/kaktushose/jda/commands/dispatching/events/interactions/ComponentEvent.java create mode 100644 src/main/java/com/github/kaktushose/jda/commands/dispatching/events/interactions/ModalEvent.java create mode 100644 src/main/java/com/github/kaktushose/jda/commands/dispatching/events/interactions/package-info.java create mode 100644 src/main/java/com/github/kaktushose/jda/commands/dispatching/events/package-info.java create mode 100644 src/main/java/com/github/kaktushose/jda/commands/dispatching/handling/AutoCompleteHandler.java create mode 100644 src/main/java/com/github/kaktushose/jda/commands/dispatching/handling/ComponentHandler.java create mode 100644 src/main/java/com/github/kaktushose/jda/commands/dispatching/handling/DispatchingContext.java create mode 100644 src/main/java/com/github/kaktushose/jda/commands/dispatching/handling/EventHandler.java create mode 100644 src/main/java/com/github/kaktushose/jda/commands/dispatching/handling/ModalHandler.java create mode 100644 src/main/java/com/github/kaktushose/jda/commands/dispatching/handling/command/ContextCommandHandler.java create mode 100644 src/main/java/com/github/kaktushose/jda/commands/dispatching/handling/command/SlashCommandHandler.java create mode 100644 src/main/java/com/github/kaktushose/jda/commands/dispatching/handling/command/package-info.java create mode 100644 src/main/java/com/github/kaktushose/jda/commands/dispatching/handling/package-info.java delete mode 100644 src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/Context.java delete mode 100644 src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/GenericDispatcher.java delete mode 100644 src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/GenericEvent.java delete mode 100644 src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/autocomplete/AutoCompleteDispatcher.java delete mode 100644 src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/commands/CommandDispatcher.java delete mode 100644 src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/commands/CommandEvent.java delete mode 100644 src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/commands/SlashCommandContext.java delete mode 100644 src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/components/ComponentDispatcher.java delete mode 100644 src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/components/ComponentEvent.java delete mode 100644 src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/modals/ModalDispatcher.java delete mode 100644 src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/modals/ModalEvent.java create mode 100644 src/main/java/com/github/kaktushose/jda/commands/dispatching/internal/JDAEventListener.java create mode 100644 src/main/java/com/github/kaktushose/jda/commands/dispatching/internal/Runtime.java create mode 100644 src/main/java/com/github/kaktushose/jda/commands/dispatching/middleware/impl/package-info.java create mode 100644 src/main/java/com/github/kaktushose/jda/commands/dispatching/middleware/package-info.java create mode 100644 src/main/java/com/github/kaktushose/jda/commands/dispatching/reply/Component.java create mode 100644 src/main/java/com/github/kaktushose/jda/commands/dispatching/reply/ComponentReply.java create mode 100644 src/main/java/com/github/kaktushose/jda/commands/dispatching/reply/ConfigurableReply.java create mode 100644 src/main/java/com/github/kaktushose/jda/commands/dispatching/reply/GlobalReplyConfig.java create mode 100644 src/main/java/com/github/kaktushose/jda/commands/dispatching/reply/MessageReply.java delete mode 100644 src/main/java/com/github/kaktushose/jda/commands/dispatching/reply/ModalReplyable.java create mode 100644 src/main/java/com/github/kaktushose/jda/commands/dispatching/reply/Reply.java delete mode 100644 src/main/java/com/github/kaktushose/jda/commands/dispatching/reply/ReplyContext.java delete mode 100644 src/main/java/com/github/kaktushose/jda/commands/dispatching/reply/Replyable.java delete mode 100644 src/main/java/com/github/kaktushose/jda/commands/dispatching/reply/components/Buttons.java delete mode 100644 src/main/java/com/github/kaktushose/jda/commands/dispatching/reply/components/Component.java delete mode 100644 src/main/java/com/github/kaktushose/jda/commands/dispatching/reply/components/SelectMenus.java delete mode 100644 src/main/java/com/github/kaktushose/jda/commands/dispatching/reply/components/package-info.java rename src/main/java/com/github/kaktushose/jda/commands/{data => embeds}/EmbedCache.java (98%) rename src/main/java/com/github/kaktushose/jda/commands/{data => embeds}/EmbedDTO.java (98%) delete mode 100644 src/main/java/com/github/kaktushose/jda/commands/embeds/ErrorMessageFactory.java rename src/main/java/com/github/kaktushose/jda/commands/embeds/{ => error}/DefaultErrorMessageFactory.java (82%) create mode 100644 src/main/java/com/github/kaktushose/jda/commands/embeds/error/ErrorMessageFactory.java rename src/main/java/com/github/kaktushose/jda/commands/embeds/{ => error}/JsonErrorMessageFactory.java (72%) create mode 100644 src/main/java/com/github/kaktushose/jda/commands/embeds/error/package-info.java create mode 100644 src/main/java/com/github/kaktushose/jda/commands/internal/Helpers.java rename src/main/java/com/github/kaktushose/jda/commands/{ => internal}/JDAContext.java (98%) rename src/main/java/com/github/kaktushose/jda/commands/{data => internal/register}/CommandTree.java (96%) rename src/main/java/com/github/kaktushose/jda/commands/{ => internal/register}/SlashCommandUpdater.java (96%) rename src/main/java/com/github/kaktushose/jda/commands/{data => internal/register}/TreeNode.java (97%) create mode 100644 src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/ReplyConfig.java create mode 100644 src/main/javadoc/doc-files/flowchart.png create mode 100644 src/main/javadoc/overview.md create mode 100644 src/test/java/reply/CustomController.java create mode 100644 src/test/java/reply/DefaultController.java create mode 100644 src/test/java/reply/ReplyConfigTest.java diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 790f78324..4824540b6 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -14,7 +14,7 @@ jobs: fetch-depth: 0 - name: Generate Documentation run: | - mvn javadoc:aggregate + mvn javadoc:javadoc - name: Checkout Docs Branch run: | git checkout docs @@ -24,8 +24,6 @@ jobs: rm -rf ./docs mv ./target/site/apidocs/ ./docs rm -rf ./target - - name: Modify search.js - run: echo 'getURLPrefix = function(ui) {return "";};' >> ./docs/search.js - name: Commit & Push Changes run: | git config --global user.email 41898282+github-actions[bot]@users.noreply.github.com diff --git a/pom.xml b/pom.xml index 891c26d86..f45bedc44 100644 --- a/pom.xml +++ b/pom.xml @@ -36,8 +36,13 @@ maven-javadoc-plugin 3.3.2 - 11 + 23 none + ${basedir}/src/main/javadoc/overview.md + + https://javadoc.io/doc/net.dv8tion/JDA/5.0.2 + + true diff --git a/src/examples/ReportCommand.java b/src/examples/ReportCommand.java index 4590f53c2..b6ff7bd41 100644 --- a/src/examples/ReportCommand.java +++ b/src/examples/ReportCommand.java @@ -23,6 +23,7 @@ public void onReportGet(CommandEvent event, Member member) { name = "Remove Report", usage = "{prefix}report remove @Member", desc = "Remove the reports of a user", category = "Moderation") public void onReportRemove(CommandEvent event, Member member) { event.reply("Cleared reports for member %s", member.getAsMention()); + event.kv().put("error", error); } } diff --git a/src/main/java/com/github/kaktushose/jda/commands/Helpers.java b/src/main/java/com/github/kaktushose/jda/commands/Helpers.java deleted file mode 100644 index e78e8649f..000000000 --- a/src/main/java/com/github/kaktushose/jda/commands/Helpers.java +++ /dev/null @@ -1,91 +0,0 @@ -package com.github.kaktushose.jda.commands; - -import com.github.kaktushose.jda.commands.annotations.interactions.Permissions; -import com.github.kaktushose.jda.commands.dispatching.interactions.Context; -import com.github.kaktushose.jda.commands.reflect.MethodBuildContext; -import net.dv8tion.jda.api.entities.Guild; -import net.dv8tion.jda.api.entities.channel.middleman.GuildChannel; -import org.jetbrains.annotations.NotNull; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.lang.reflect.Method; -import java.util.Collections; -import java.util.HashSet; -import java.util.Optional; -import java.util.Set; - -public final class Helpers { - - private static final Logger log = LoggerFactory.getLogger(Helpers.class); - - /** - * Sanitizes a String containing a raw mention. This will remove all markdown characters namely < @ # & ! > - * For instance: {@code <@!393843637437464588>} gets sanitized to {@code 393843637437464588} - * - * @param mention the raw String to sanitize - * @return the sanitized String - */ - public static String sanitizeMention(@NotNull String mention) { - if (mention.matches("<[@#][&!]?([0-9]{4,})>")) { - return mention.replaceAll("<[@#][&!]?", "").replace(">", ""); - } - return mention; - } - - public static Optional resolveGuildChannel(Context context, String raw) { - GuildChannel guildChannel; - raw = sanitizeMention(raw); - - Guild guild = context.getEvent().getGuild(); - if (raw.matches("\\d+")) { - guildChannel = guild.getGuildChannelById(raw); - } else { - String finalRaw = raw; - guildChannel = guild.getChannels().stream().filter(it -> it.getName().equalsIgnoreCase(finalRaw)) - .findFirst().orElse(null); - } - return Optional.ofNullable(guildChannel); - } - - public static Set permissions(MethodBuildContext context) { - Permissions permission = context.method().getAnnotation(Permissions.class); - - if (permission != null) { - HashSet mergedPermissions = new HashSet<>(context.permissions()); - mergedPermissions.addAll(Set.of(permission.value())); - return Collections.unmodifiableSet(mergedPermissions); - } - return context.permissions(); - } - - public static boolean ephemeral(MethodBuildContext context, boolean localEphemeral) { - return context.interaction().ephemeral() || localEphemeral; - } - - public static boolean isIncorrectParameterType(Method method, int index, Class type) { - if (!type.isAssignableFrom(method.getParameters()[index].getType())) { - log.error("An error has occurred! Skipping Interaction {}.{}:", - method.getDeclaringClass().getSimpleName(), - method.getName(), - new IllegalArgumentException(String.format("%d. parameter must be of type %s", index + 1, type.getSimpleName()))); - return true; - } - return false; - } - - public static boolean isIncorrectParameterAmount(Method method, int amount) { - if (method.getParameters().length != amount) { - log.error("An error has occurred! Skipping Interaction {}.{}:", - method.getDeclaringClass().getSimpleName(), - method.getName(), - new IllegalArgumentException(String.format( - "Invalid amount of parameters!. Expected: %d Actual: %d", - amount, - method.getParameters().length - ))); - return true; - } - return false; - } -} diff --git a/src/main/java/com/github/kaktushose/jda/commands/JDACommands.java b/src/main/java/com/github/kaktushose/jda/commands/JDACommands.java index 33864f8e1..2e1346a1d 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/JDACommands.java +++ b/src/main/java/com/github/kaktushose/jda/commands/JDACommands.java @@ -1,12 +1,22 @@ package com.github.kaktushose.jda.commands; +import com.github.kaktushose.jda.commands.annotations.interactions.EntitySelectMenu; +import com.github.kaktushose.jda.commands.annotations.interactions.StringSelectMenu; import com.github.kaktushose.jda.commands.dependency.DefaultDependencyInjector; import com.github.kaktushose.jda.commands.dependency.DependencyInjector; -import com.github.kaktushose.jda.commands.dispatching.DispatcherSupervisor; -import com.github.kaktushose.jda.commands.dispatching.RuntimeSupervisor; +import com.github.kaktushose.jda.commands.dispatching.ExpirationStrategy; +import com.github.kaktushose.jda.commands.dispatching.internal.JDAEventListener; +import com.github.kaktushose.jda.commands.dispatching.internal.Runtime; import com.github.kaktushose.jda.commands.dispatching.adapter.TypeAdapterRegistry; +import com.github.kaktushose.jda.commands.dispatching.handling.DispatchingContext; import com.github.kaktushose.jda.commands.dispatching.middleware.MiddlewareRegistry; +import com.github.kaktushose.jda.commands.dispatching.middleware.Priority; +import com.github.kaktushose.jda.commands.dispatching.middleware.impl.ConstraintMiddleware; +import com.github.kaktushose.jda.commands.dispatching.middleware.impl.CooldownMiddleware; +import com.github.kaktushose.jda.commands.dispatching.middleware.impl.PermissionsMiddleware; import com.github.kaktushose.jda.commands.dispatching.validation.ValidatorRegistry; +import com.github.kaktushose.jda.commands.internal.JDAContext; +import com.github.kaktushose.jda.commands.internal.register.SlashCommandUpdater; import com.github.kaktushose.jda.commands.reflect.ImplementationRegistry; import com.github.kaktushose.jda.commands.reflect.InteractionRegistry; import com.github.kaktushose.jda.commands.reflect.interactions.components.ButtonDefinition; @@ -24,18 +34,16 @@ public record JDACommands( JDAContext jdaContext, - DispatcherSupervisor dispatcherSupervisor, + JDAEventListener JDAEventListener, MiddlewareRegistry middlewareRegistry, TypeAdapterRegistry adapterRegistry, ValidatorRegistry validatorRegistry, DependencyInjector dependencyInjector, InteractionRegistry interactionRegistry, - SlashCommandUpdater updater, - RuntimeSupervisor runtimeSupervisor -) { + SlashCommandUpdater updater) { private static final Logger log = LoggerFactory.getLogger(JDACommands.class); - private static JDACommands startInternal(Object jda, Class clazz, LocalizationFunction function, DependencyInjector dependencyInjector, String... packages) { + private static JDACommands startInternal(Object jda, Class clazz, LocalizationFunction function, DependencyInjector dependencyInjector, ExpirationStrategy expirationStrategy, String[] packages) { log.info("Starting JDA-Commands..."); var jdaContext = new JDAContext(jda); @@ -47,8 +55,10 @@ private static JDACommands startInternal(Object jda, Class clazz, Localizatio var implementationRegistry = new ImplementationRegistry(dependencyInjector, middlewareRegistry, adapterRegistry, validatorRegistry); var interactionRegistry = new InteractionRegistry(validatorRegistry, dependencyInjector, function); - var runtimeSupervisor = new RuntimeSupervisor(dependencyInjector); - var dispatcherSupervisor = new DispatcherSupervisor(middlewareRegistry, implementationRegistry, interactionRegistry, adapterRegistry, runtimeSupervisor); + middlewareRegistry.register(Priority.PERMISSIONS, new PermissionsMiddleware(implementationRegistry)); + middlewareRegistry.register(Priority.NORMAL, new ConstraintMiddleware(implementationRegistry), new CooldownMiddleware(implementationRegistry)); + + var eventListener = new JDAEventListener(new DispatchingContext(middlewareRegistry, implementationRegistry, interactionRegistry, adapterRegistry, expirationStrategy)); implementationRegistry.index(clazz, packages); @@ -57,20 +67,19 @@ private static JDACommands startInternal(Object jda, Class clazz, Localizatio var updater = new SlashCommandUpdater(jdaContext, implementationRegistry.getGuildScopeProvider(), interactionRegistry); updater.updateAllCommands(); - jdaContext.performTask(it -> it.addEventListener(dispatcherSupervisor)); + jdaContext.performTask(it -> it.addEventListener(eventListener)); log.info("Finished loading!"); return new JDACommands( jdaContext, - dispatcherSupervisor, + eventListener, middlewareRegistry, adapterRegistry, validatorRegistry, dependencyInjector, interactionRegistry, - updater, - runtimeSupervisor + updater ); } @@ -83,7 +92,7 @@ private static JDACommands startInternal(Object jda, Class clazz, Localizatio * @return a new JDACommands instance */ public static JDACommands start(@NotNull JDA jda, @NotNull Class clazz, @NotNull String... packages) { - return startInternal(jda, clazz, ResourceBundleLocalizationFunction.empty().build(), new DefaultDependencyInjector(), packages); + return startInternal(jda, clazz, ResourceBundleLocalizationFunction.empty().build(), new DefaultDependencyInjector(), ExpirationStrategy.AFTER_15_MINUTES, packages); } /** @@ -95,7 +104,7 @@ public static JDACommands start(@NotNull JDA jda, @NotNull Class clazz, @NotN * @return a new JDACommands instance */ public static JDACommands start(@NotNull ShardManager shardManager, @NotNull Class clazz, @NotNull String... packages) { - return startInternal(shardManager, clazz, ResourceBundleLocalizationFunction.empty().build(), new DefaultDependencyInjector(), packages); + return startInternal(shardManager, clazz, ResourceBundleLocalizationFunction.empty().build(), new DefaultDependencyInjector(), ExpirationStrategy.AFTER_15_MINUTES, packages); } /** @@ -108,7 +117,7 @@ public static JDACommands start(@NotNull ShardManager shardManager, @NotNull Cla * @return a new JDACommands instance */ public static JDACommands start(@NotNull JDA jda, @NotNull Class clazz, LocalizationFunction function, @NotNull String... packages) { - return startInternal(jda, clazz, function, new DefaultDependencyInjector(), packages); + return startInternal(jda, clazz, function, new DefaultDependencyInjector(), ExpirationStrategy.AFTER_15_MINUTES, packages); } /** @@ -121,7 +130,7 @@ public static JDACommands start(@NotNull JDA jda, @NotNull Class clazz, Local * @return a new JDACommands instance */ public static JDACommands start(@NotNull ShardManager shardManager, @NotNull Class clazz, LocalizationFunction function, @NotNull String... packages) { - return startInternal(shardManager, clazz, function, new DefaultDependencyInjector(), packages); + return startInternal(shardManager, clazz, function, new DefaultDependencyInjector(), ExpirationStrategy.AFTER_15_MINUTES, packages); } /** @@ -134,8 +143,8 @@ public static JDACommands start(@NotNull ShardManager shardManager, @NotNull Cla * @param packages package(s) to exclusively scan * @return a new JDACommands instance */ - public static JDACommands start(@NotNull JDA jda, @NotNull Class clazz, LocalizationFunction function, DependencyInjector injector, @NotNull String... packages) { - return startInternal(jda, clazz, function, injector, packages); + public static JDACommands start(@NotNull JDA jda, @NotNull Class clazz, LocalizationFunction function, DependencyInjector injector, ExpirationStrategy expirationStrategy, @NotNull String... packages) { + return startInternal(jda, clazz, function, injector, expirationStrategy, packages); } /** @@ -148,8 +157,8 @@ public static JDACommands start(@NotNull JDA jda, @NotNull Class clazz, Local * @param packages package(s) to exclusively scan * @return a new JDACommands instance */ - public static JDACommands start(@NotNull ShardManager shardManager, @NotNull Class clazz, LocalizationFunction function, DependencyInjector injector, @NotNull String... packages) { - return startInternal(shardManager, clazz, function, injector, packages); + public static JDACommands start(@NotNull ShardManager shardManager, @NotNull Class clazz, LocalizationFunction function, DependencyInjector injector, ExpirationStrategy expirationStrategy, @NotNull String... packages) { + return startInternal(shardManager, clazz, function, injector, expirationStrategy, packages); } /** @@ -157,7 +166,7 @@ public static JDACommands start(@NotNull ShardManager shardManager, @NotNull Cla * This will not unregister any slash commands. */ public void shutdown() { - jdaContext.performTask(jda -> jda.removeEventListener(dispatcherSupervisor)); + jdaContext.performTask(jda -> jda.removeEventListener(JDAEventListener)); } /** @@ -169,19 +178,16 @@ public void updateGuildCommands() { updater.updateGuildCommands(); } - /** - * Gets a JDA {@link Button} to use it for message builders based on the jda-commands id. - * - *

- * The id is made up of the simple class name and the method name. E.g. the id of a button defined by a - * {@code onButton(ComponentEvent event)} method inside an {@code ExampleButton} class would be - * {@code ExampleButton.onButton}. - *

- * - * @param button the id of the button - * @return a JDA {@link Button} - */ - public Button getButton(String button) { + /// Gets a [`Button`][com.github.kaktushose.jda.commands.annotations.interactions.Button] based on the method name + /// and transforms it into a JDA [Button]. + /// + /// The button will be [`Runtime`]({@docRoot}/index.html#runtime-concept-heading) independent. This may be useful if you want to send a message without + /// using the framework. + /// + /// @param button the name of the button + /// @return the JDA [Button] + @NotNull + public Button getButton(@NotNull String button) { if (!button.matches("[a-zA-Z]+\\.[a-zA-Z]+")) { throw new IllegalArgumentException("Unknown Button"); } @@ -191,95 +197,29 @@ public Button getButton(String button) { .filter(it -> it.getDefinitionId().equals(sanitizedId)) .findFirst().orElseThrow(() -> new IllegalArgumentException("Unknown Button")); - RuntimeSupervisor.InteractionRuntime runtime = runtimeSupervisor.newRuntime(buttonDefinition); - return buttonDefinition.toButton().withId(buttonDefinition.createCustomId(runtime.getRuntimeId())); + return buttonDefinition.toButton().withId(buttonDefinition.independentCustomId()); } - /** - * Gets a JDA {@link Button} to use it for message builders based on the jda-commands id and links it an - * existing - * {@link com.github.kaktushose.jda.commands.dispatching.RuntimeSupervisor.InteractionRuntime InteractionRuntime}. - * - * - *

- * The id is made up of the simple class name and the method name. E.g. the id of a button defined by a - * {@code onButton(ComponentEvent event)} method inside an {@code ExampleButton} class would be - * {@code ExampleButton.onButton}. - *

- * - * @param button the id of the button - * @param runtimeId the id of the - * {@link com.github.kaktushose.jda.commands.dispatching.RuntimeSupervisor.InteractionRuntime InteractionRuntime} - * @return a JDA {@link Button} - */ - public Button getButton(String button, String runtimeId) { - if (!button.matches("[a-zA-Z]+\\.[a-zA-Z]+")) { - throw new IllegalArgumentException("Unknown Button"); - } - - String sanitizedId = button.replaceAll("\\.", ""); - ButtonDefinition buttonDefinition = interactionRegistry.getButtons().stream() - .filter(it -> it.getDefinitionId().equals(sanitizedId)) - .findFirst().orElseThrow(() -> new IllegalArgumentException("Unknown Button")); - - return buttonDefinition.toButton().withId(buttonDefinition.createCustomId(runtimeId)); - } - - /** - * Gets a JDA {@link SelectMenu} to use it for message builders based on the jda-commands id. - * - *

- * The id is made up of the simple class name and the method name. E.g. the id of a a select menu defined by a - * {@code onSelectMenu(ComponentEvent event)} method inside an {@code ExampleMenu} class would be - * {@code ExampleMenu.onSelectMenu}. - *

- * - * @param selectMenu the id of the selectMenu - * @return a JDA {@link SelectMenu} - */ - @SuppressWarnings("unchecked") - public T getSelectMenu(String selectMenu) { - if (!selectMenu.matches("[a-zA-Z]+\\.[a-zA-Z]+")) { - throw new IllegalArgumentException("Unknown Select Menu"); - } - - String sanitizedId = selectMenu.replaceAll("\\.", ""); - GenericSelectMenuDefinition selectMenuDefinition = interactionRegistry.getSelectMenus().stream() - .filter(it -> it.getDefinitionId().equals(sanitizedId)) - .findFirst().orElseThrow(() -> new IllegalArgumentException("Unknown Select Menu")); - - RuntimeSupervisor.InteractionRuntime runtime = runtimeSupervisor.newRuntime(selectMenuDefinition); - return (T) selectMenuDefinition.toSelectMenu(runtime.getRuntimeId(), true); - } - - /** - * Gets a JDA {@link SelectMenu} subtype to use it for message builders based on the jda-commands id and links it an - * existing - * {@link com.github.kaktushose.jda.commands.dispatching.RuntimeSupervisor.InteractionRuntime InteractionRuntime}. - * - *

- * The id is made up of the simple class name and the method name. E.g. the id of a select menu defined by a - * {@code onSelectMenu(ComponentEvent event)} method inside an {@code ExampleMenu} class would be - * {@code ExampleMenu.onSelectMenu}. - *

- * - * @param selectMenu the id of the selectMenu - * @param runtimeId the id of the - * {@link com.github.kaktushose.jda.commands.dispatching.RuntimeSupervisor.InteractionRuntime - * InteractionRuntime} - * @return a JDA {@link SelectMenu} - */ + /// Gets a [StringSelectMenu] or [EntitySelectMenu] based on the method name and transforms it into a JDA [SelectMenu]. + /// + /// The select menu will be [`Runtime`]({@docRoot}/index.html#runtime-concept-heading) independent. This may be useful if you want to send a component + /// without using the framework. + /// + /// @param the type of [SelectMenu] + /// @param menu the name of the select menu + /// @return the JDA [SelectMenu] @SuppressWarnings("unchecked") - public T getSelectMenu(String selectMenu, String runtimeId) { - if (!selectMenu.matches("[a-zA-Z]+\\.[a-zA-Z]+")) { + @NotNull + public S getSelectMenu(@NotNull String menu) { + if (!menu.matches("[a-zA-Z]+\\.[a-zA-Z]+")) { throw new IllegalArgumentException("Unknown Select Menu"); } - String sanitizedId = selectMenu.replaceAll("\\.", ""); + String sanitizedId = menu.replaceAll("\\.", ""); GenericSelectMenuDefinition selectMenuDefinition = interactionRegistry.getSelectMenus().stream() .filter(it -> it.getDefinitionId().equals(sanitizedId)) .findFirst().orElseThrow(() -> new IllegalArgumentException("Unknown Select Menu")); - return (T) selectMenuDefinition.toSelectMenu(runtimeId, true); + return (S) selectMenuDefinition.toSelectMenu(selectMenuDefinition.independentCustomId(), true); } } diff --git a/src/main/java/com/github/kaktushose/jda/commands/annotations/Implementation.java b/src/main/java/com/github/kaktushose/jda/commands/annotations/Implementation.java index 9c30866ab..99aedd334 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/annotations/Implementation.java +++ b/src/main/java/com/github/kaktushose/jda/commands/annotations/Implementation.java @@ -2,6 +2,7 @@ import com.github.kaktushose.jda.commands.annotations.constraints.Constraint; import com.github.kaktushose.jda.commands.dispatching.middleware.Priority; +import com.github.kaktushose.jda.commands.embeds.error.ErrorMessageFactory; import java.lang.annotation.*; @@ -14,7 +15,7 @@ * @see com.github.kaktushose.jda.commands.dispatching.adapter.TypeAdapter TypeAdapter * @see com.github.kaktushose.jda.commands.permissions.PermissionsProvider PermissionsProvider * @see com.github.kaktushose.jda.commands.scope.GuildScopeProvider GuildScopeProvider - * @see com.github.kaktushose.jda.commands.embeds.ErrorMessageFactory ErrorMessageFactory + * @see ErrorMessageFactory ErrorMessageFactory * @since 2.0.0 */ @Target(ElementType.TYPE) @@ -34,7 +35,7 @@ /** * Gets the annotation the {@link com.github.kaktushose.jda.commands.dispatching.validation.Validator Validator} - * should be mapped to. If the component is not a subtype of + * should be mapped to. If this class is not a subtype of * {@link com.github.kaktushose.jda.commands.dispatching.validation.Validator Validator}, this field can be ignored. * * @return the annotation the {@link com.github.kaktushose.jda.commands.dispatching.validation.Validator Validator} diff --git a/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/AutoComplete.java b/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/AutoComplete.java index c31b37244..5af595b39 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/AutoComplete.java +++ b/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/AutoComplete.java @@ -1,18 +1,31 @@ package com.github.kaktushose.jda.commands.annotations.interactions; +import com.github.kaktushose.jda.commands.dispatching.events.interactions.AutoCompleteEvent; + import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -/** - * Methods annotated with AutoComplete will be registered as a handler for - * {@link com.github.kaktushose.jda.commands.dispatching.interactions.autocomplete.AutoCompleteEvent AutoCompleteEvents}. - * - * @see SlashCommand - * @since 4.0.0 - */ +/// Methods annotated with AutoComplete will be registered as a handler for [AutoCompleteEvent]s for the given +/// [SlashCommand](s). +/// +/// ## Example: +/// ``` +/// @SlashCommand(value = "favourite fruit") +/// public void fruitCommand(CommandEvent event, String fruit) { +/// event.reply("You've chosen: %s", fruit); +/// } +/// +/// @AutoComplete("fruitCommand") +/// public void onFruitAutoComplete(AutoCompleteEvent event) { +/// event.replyChoices(...); +/// } +/// ``` +/// +/// @see SlashCommand +/// @since 4.0.0 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface AutoComplete { diff --git a/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/Button.java b/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/Button.java index 669a90ea5..8d51a4073 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/Button.java +++ b/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/Button.java @@ -1,5 +1,8 @@ package com.github.kaktushose.jda.commands.annotations.interactions; +import com.github.kaktushose.jda.commands.dispatching.events.interactions.ComponentEvent; +import com.github.kaktushose.jda.commands.dispatching.reply.Component; +import com.github.kaktushose.jda.commands.dispatching.reply.ConfigurableReply; import net.dv8tion.jda.api.entities.emoji.Emoji; import net.dv8tion.jda.api.interactions.components.buttons.ButtonStyle; @@ -8,20 +11,24 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -/** - * Methods annotated with Button will be registered as a button at startup. - * - *

Therefore the method must be declared inside a class that is annotated with - * {@link Interaction}. - * Furthermore, the method signature has to meet the following conditions: - *

    - *
  • First parameter must be of type - * {@link com.github.kaktushose.jda.commands.dispatching.interactions.components.ComponentEvent ComponentEvent}
  • - *
- * - * @see Interaction - * @since 2.3.0 - */ +/// Methods annotated with [Button] will be registered as a button at startup. +/// +/// Therefore, the method must be declared inside a class that is annotated with +/// [Interaction]. +/// Furthermore, the method signature has to meet the following conditions: +/// +/// - First parameter must be of type [ComponentEvent] +/// +/// You can reply with a button by calling [ConfigurableReply#components(Component...)]. +/// +/// ## Example: +/// ``` +/// @Button(value = "Press me", style = ButtonStyle.DANGER) +/// public void onButton(ComponentEvent event) { ... } +/// ``` +/// +/// @see Interaction +/// @since 2.3.0 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Button { @@ -54,11 +61,4 @@ */ String link() default ""; - /** - * Whether this button should send ephemeral replies by default. - * - * @return {@code true} if to send ephemeral replies - */ - boolean ephemeral() default false; - } diff --git a/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/Choices.java b/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/Choices.java index 1d0473c88..bc04a52c3 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/Choices.java +++ b/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/Choices.java @@ -8,6 +8,7 @@ /** * Annotation used to add choices to parameters. * + * @see SlashCommand * @since 2.3.0 */ @Target(ElementType.PARAMETER) diff --git a/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/ContextCommand.java b/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/ContextCommand.java index 5dbf518d9..602081004 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/ContextCommand.java +++ b/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/ContextCommand.java @@ -1,7 +1,9 @@ package com.github.kaktushose.jda.commands.annotations.interactions; -import com.github.kaktushose.jda.commands.dispatching.interactions.commands.CommandEvent; +import com.github.kaktushose.jda.commands.dispatching.events.interactions.CommandEvent; import net.dv8tion.jda.api.Permission; +import net.dv8tion.jda.api.entities.Message; +import net.dv8tion.jda.api.entities.User; import net.dv8tion.jda.api.interactions.commands.Command; import java.lang.annotation.ElementType; @@ -9,19 +11,26 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -/** - * Methods annotated with ContextMenu will be registered as a context menu command. - * - *

Therefore the method must be declared inside a class that is annotated with - * {@link Interaction}. - * Furthermore, the method signature has to meet the following conditions: - *

    - *
  • First parameter must be of type {@link CommandEvent}
  • - *
- * - * @see Interaction - * @since 4.0.0 - */ +/// Methods annotated with ContextMenu will be registered as a context menu command. +/// +/// Therefore, the method must be declared inside a class that is annotated with +/// [Interaction]. +/// Furthermore, the method signature has to meet the following conditions: +/// +/// - First parameter must be of type [CommandEvent] +/// - Second parameter must either be a [User] or a [Message] +/// +/// ## Examples: +/// ``` +/// @ContextCommand(value = "message context command", type = Command.Type.MESSAGE) +/// public void onCommand(CommandEvent event, Message target) { ... } +/// +/// @ContextCommand(value = "user context command", type = Command.Type.USER) +/// public void onCommand(CommandEvent event, User target) { ... } +/// ``` +/// +/// @see Interaction +/// @since 4.0.0 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface ContextCommand { @@ -48,14 +57,6 @@ */ boolean isNSFW() default false; - /** - * Returns whether this command should send ephemeral replies by default. Note that {@link Interaction#ephemeral()} - * set to {@code true} will override this value. - * - * @return {@code true} if this command should send ephemeral replies - */ - boolean ephemeral() default false; - /** * Returns an array of {@link net.dv8tion.jda.api.Permission Permissions} this command * should be enabled for by default. Note that guild admins can modify this at any time. diff --git a/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/EntitySelectMenu.java b/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/EntitySelectMenu.java index 154af061f..5a68978fc 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/EntitySelectMenu.java +++ b/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/EntitySelectMenu.java @@ -1,6 +1,9 @@ package com.github.kaktushose.jda.commands.annotations.interactions; -import com.github.kaktushose.jda.commands.dispatching.interactions.components.ComponentEvent; +import com.github.kaktushose.jda.commands.dispatching.events.interactions.ComponentEvent; +import com.github.kaktushose.jda.commands.dispatching.reply.Component; +import com.github.kaktushose.jda.commands.dispatching.reply.ConfigurableReply; +import net.dv8tion.jda.api.entities.Mentions; import net.dv8tion.jda.api.entities.channel.ChannelType; import net.dv8tion.jda.api.interactions.components.selections.EntitySelectMenu.SelectTarget; @@ -9,20 +12,24 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -/** - * Methods annotated with EntitySelectMenu will be registered as a EntitySelectMenu at startup. - * - *

Therefore the method must be declared inside a class that is annotated with - * {@link Interaction}. - * Furthermore, the method signature has to meet the following conditions: - *

    - *
  • First parameter must be of type - * {@link ComponentEvent SelectMenuEvent}
  • - *
- * - * @see Interaction - * @since 2.3.0 - */ +/// Methods annotated with EntitySelectMenu will be registered as a EntitySelectMenu at startup. +/// +/// Therefore, the method must be declared inside a class that is annotated with +/// [Interaction]. +/// Furthermore, the method signature has to meet the following conditions: +/// +/// - First parameter must be of type [ComponentEvent] +/// - Second parameter must be of type [Mentions] +/// +/// You can reply with an entity select menu by calling [ConfigurableReply#components(Component...)]. +/// +/// ## Example: +/// ``` +/// @EntitySelectMenu(value = SelectTarget.USER, placeholder = "Who's your favourite user?") +/// public void onMenu(ComponentEvent event, Mentions mentions) { ... } +/// ``` +/// @see Interaction +/// @since 2.3.0 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface EntitySelectMenu { @@ -93,11 +100,4 @@ */ int maxValue() default 1; - /** - * Whether this button should send ephemeral replies by default. - * - * @return {@code true} if to send ephemeral replies - */ - boolean ephemeral() default false; - } diff --git a/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/Interaction.java b/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/Interaction.java index 608b708b7..32c010799 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/Interaction.java +++ b/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/Interaction.java @@ -5,12 +5,25 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -/** - * Classes annotated with Interaction will be scanned at startup and are eligible for defining interactions such as - * slash commands, buttonContainers, modals or context menus. - * - * @since 4.0.0 - */ +/// Classes annotated with [Interaction] are responsible for defining and handling interactions. +/// +/// A class annotated with [Interaction] can define interactions via its methods. Therefore, such methods must be annotated +/// with one of the following interaction annotations: [SlashCommand], [ContextCommand], [Button], [EntitySelectMenu], +/// [StringSelectMenu] or [Modal]. See the respective annotations for details. +/// +/// ## Example: +/// ``` +/// @Interaction +/// public class InteractionClass { +/// +/// @SlashCommand("greet") +/// public void onCommand(CommandEvent event) { +/// event.reply("Hello World!"); +/// } +/// } +/// ``` +/// +/// @since 4.0.0 @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Interaction { @@ -22,11 +35,4 @@ */ String value() default ""; - /** - * Returns whether this interaction should send ephemeral replies by default. - * - * @return {@code true} if this interaction should send ephemeral replies by default - */ - boolean ephemeral() default false; - } diff --git a/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/Modal.java b/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/Modal.java index 6c64c6963..a9d86b105 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/Modal.java +++ b/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/Modal.java @@ -1,20 +1,28 @@ package com.github.kaktushose.jda.commands.annotations.interactions; +import com.github.kaktushose.jda.commands.dispatching.events.ModalReplyableEvent; + import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -/** - * Methods annotated with Modal will be registered as a modal at startup. - * - *

Therefore the method must be declared inside a class that is annotated with - * {@link Interaction}. - * - * @see Interaction - * @see TextInput - * @since 4.0.0 - */ +/// Methods annotated with [Modal] will be registered as a modal at startup. +/// +/// Therefore, the method must be declared inside a class that is annotated with +/// [Interaction]. Text inputs are defined via method parameters that must be annotated with [TextInput]. +/// +/// You can reply with a modal by calling [ModalReplyableEvent#replyModal(String)]. +/// +/// ## Example: +/// ``` +/// @Modal("My Modal") +/// public void onModal(ModalEvent event, @TextInput("Type here") String input) { ... } +/// ``` +/// +/// @see Interaction +/// @see TextInput +/// @since 4.0.0 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Modal { @@ -26,11 +34,4 @@ */ String value(); - /** - * Whether this Modal should send ephemeral replies by default. - * - * @return {@code true} if to send ephemeral replies - */ - boolean ephemeral() default false; - } diff --git a/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/Optional.java b/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/Optional.java index 3b78a39c5..0e5fc05b6 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/Optional.java +++ b/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/Optional.java @@ -17,6 +17,7 @@ * will try to parse it. If the parsing fails the command will still be executed but with empty or * possible {@code null} values. * + * @see SlashCommand * @since 1.0.0 */ @Target(ElementType.PARAMETER) diff --git a/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/ReplyConfig.java b/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/ReplyConfig.java new file mode 100644 index 000000000..d624a0b74 --- /dev/null +++ b/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/ReplyConfig.java @@ -0,0 +1,63 @@ +package com.github.kaktushose.jda.commands.annotations.interactions; + +import com.github.kaktushose.jda.commands.dispatching.events.ReplyableEvent; +import com.github.kaktushose.jda.commands.dispatching.reply.ConfigurableReply; +import com.github.kaktushose.jda.commands.dispatching.reply.GlobalReplyConfig; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/// Used to configure the reply behaviour of interaction replies. +/// +/// Interaction methods annotated with [ReplyConfig] will use the configured values of this annotation when sending a reply. +/// Interaction classes annotated with [ReplyConfig] will apply the configured values of this annotation to +/// every method, if and only if no annotation is present at method level. If the [ReplyConfig] annotation is neither +/// present at the class level nor the method level, the [GlobalReplyConfig] will be used instead. +/// +/// **These values are always overridden by [ConfigurableReply#ephemeral(boolean)], +/// [ConfigurableReply#keepComponents(boolean)] or respectively [ConfigurableReply#editReply(boolean)].** +/// +/// In other words the hierarchy is as following: +/// 1. [ConfigurableReply] +/// 2. [ReplyConfig] method annotation +/// 3. [ReplyConfig] class annotation +/// 4. [GlobalReplyConfig] +/// +/// @see ReplyableEvent +/// @since 4.0.0 +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface ReplyConfig { + + /// Whether to send ephemeral replies. Default value is `false`. + /// + /// Ephemeral messages have some limitations and will be removed once the user restarts their client. + /// Limitations: + /// - Cannot contain any files/ attachments + /// - Cannot be reacted to + /// - Cannot be retrieved + /// + /// @return `true` if to send ephemeral replies + boolean ephemeral() default false; + + /// Whether to keep the original components when editing a message. Default value is `true`. + /// + /// More formally, if editing a message and `keepComponents` is `true`, the original message will first be queried and + /// its components get added to the reply before it is sent. + /// + /// @return `true` if to keep the original components + boolean keepComponents() default true; + + /// Whether to edit the original message or to send a new one. Default value is `true`. + /// + /// The original message is the message, from which this event (interaction) originates. + /// For example if this event is a ButtonEvent, the original message will be the message to which the pressed button is attached to. + /// + /// Subsequent replies to the same slash command event or the button event cannot be edited. + /// + /// @return `true` if to edit the original method + boolean editReply() default true; + +} diff --git a/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/SelectOption.java b/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/SelectOption.java index 57916f842..de03b476e 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/SelectOption.java +++ b/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/SelectOption.java @@ -9,7 +9,6 @@ * Used to define {@link net.dv8tion.jda.api.interactions.components.selections.SelectOption SelectOptions} * for {@link StringSelectMenu StringSelectMenus}. * - * @see Interaction * @see StringSelectMenu * @since 4.0.0 */ diff --git a/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/SlashCommand.java b/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/SlashCommand.java index 8a5792b05..177f3786d 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/SlashCommand.java +++ b/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/SlashCommand.java @@ -1,6 +1,7 @@ package com.github.kaktushose.jda.commands.annotations.interactions; -import com.github.kaktushose.jda.commands.dispatching.interactions.commands.CommandEvent; +import com.github.kaktushose.jda.commands.dispatching.adapter.TypeAdapterRegistry; +import com.github.kaktushose.jda.commands.dispatching.events.interactions.CommandEvent; import net.dv8tion.jda.api.Permission; import java.lang.annotation.ElementType; @@ -8,24 +9,39 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -/** - * Methods annotated with SlashCommand will be registered as a slash command at startup. - * - *

Therefore the method must be declared inside a class that is annotated with {@link Interaction}. - * Furthermore, the method signature has to meet the following conditions: - *

    - *
  • First parameter must be of type {@link CommandEvent}
  • - *
  • Remaining parameter types must be registered at the - * {@link com.github.kaktushose.jda.commands.dispatching.adapter.TypeAdapterRegistry TypeAdapterRegistry} or be a - * String array
  • - *
  • Parameter constraints must be valid
  • - *
- * - * @see Interaction - * @see com.github.kaktushose.jda.commands.annotations.interactions.Interaction Interaction - * @see com.github.kaktushose.jda.commands.annotations.constraints.Constraint Constraint - * @since 1.0.0 - */ +/// Methods annotated with SlashCommand will be registered as a slash command at startup. +/// +/// Therefore, the method must be declared inside a class that is annotated with [Interaction]. +/// Furthermore, the method signature has to meet the following conditions: +/// +/// - First parameter must be of type [CommandEvent] +/// - Remaining parameter types must be registered at the [TypeAdapterRegistry] +/// - or the second parameter is a String array +/// +/// ## Examples: +/// ``` +/// @SlashCommand("greet") +/// public void onCommand(CommandEvent event) { +/// event.reply("Hello World!"); +/// } +/// +/// @SlashCommand(value="moderation ban", desc="Bans a member", enabledFor=Permission.BAN_MEMBERS) +/// public void onCommand(CommandEvent event, @Param("The member to ban") Member target, @Optional("no reason given") String reason) { ... } +/// +/// @SlashCommand(value = "favourite fruit") +/// public void onCommand(CommandEvent event, @Choices({"Apple", "Banana", "Orange"}) String fruit) { +/// event.reply("You've chosen: %s", fruit); +/// } +/// +/// @SlashCommand("example command") { +/// public void onCommand(CommandEvent event, String[] arguments) {} +/// } +/// ``` +/// +/// @see Interaction +/// @see com.github.kaktushose.jda.commands.annotations.interactions.Interaction Interaction +/// @see com.github.kaktushose.jda.commands.annotations.constraints.Constraint Constraint +/// @since 1.0.0 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface SlashCommand { @@ -59,14 +75,6 @@ */ boolean isNSFW() default false; - /** - * Returns whether this command should send ephemeral replies by default. Note that {@link Interaction#ephemeral()} - * set to {@code true} will override this value. - * - * @return {@code true} if this command should send ephemeral replies - */ - boolean ephemeral() default false; - /** * Returns an array of {@link net.dv8tion.jda.api.Permission Permissions} this command * should be enabled for by default. Note that guild admins can modify this at any time. diff --git a/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/StaticInstance.java b/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/StaticInstance.java deleted file mode 100644 index 702ee2c05..000000000 --- a/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/StaticInstance.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.github.kaktushose.jda.commands.annotations.interactions; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Marker interface to indicate that an {@link Interaction} class should use one static instance instead of - * request-scoped instances. - * - * @since 4.0.0 - */ -@Target(ElementType.TYPE) -@Retention(RetentionPolicy.RUNTIME) -public @interface StaticInstance { -} diff --git a/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/StringSelectMenu.java b/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/StringSelectMenu.java index 4617d1226..688f622a6 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/StringSelectMenu.java +++ b/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/StringSelectMenu.java @@ -1,27 +1,37 @@ package com.github.kaktushose.jda.commands.annotations.interactions; -import com.github.kaktushose.jda.commands.dispatching.interactions.components.ComponentEvent; +import com.github.kaktushose.jda.commands.dispatching.events.interactions.ComponentEvent; +import com.github.kaktushose.jda.commands.dispatching.reply.Component; +import com.github.kaktushose.jda.commands.dispatching.reply.ConfigurableReply; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import java.util.List; -/** - * Methods annotated with StringSelectMenu will be registered as a StringSelectMenu at startup. - * - *

Therefore the method must be declared inside a class that is annotated with - * {@link Interaction}. - * Furthermore, the method signature has to meet the following conditions: - *

    - *
  • First parameter must be of type - * {@link ComponentEvent SelectMenuEvent}
  • - *
- * - * @see Interaction - * @see SelectOption - * @since 4.0.0 - */ +/// Methods annotated with StringSelectMenu will be registered as a StringSelectMenu at startup. +/// +/// Therefore, the method must be declared inside a class that is annotated with +/// [Interaction]. +/// Furthermore, the method signature has to meet the following conditions: +/// +/// - First parameter must be of type [ComponentEvent] +/// - Second parameter must be of type [`List`][List] +/// +/// You can reply with a string select menu by calling [ConfigurableReply#components(Component...)]. +/// +/// ## Example: +/// ``` +/// @SelectOption(label= "Pizza", value = "pizza") +/// @SelectOption(label= "Hamburger", value = "hamburger") +/// @SelectOption(label= "Sushi", value = "Sushi") +/// @StringSelectMenu("What's your favourite food?") +/// public void onMenu(ComponentEvent event, List choices) { ... } +/// ``` +/// @see Interaction +/// @see SelectOption +/// @since 4.0.0 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface StringSelectMenu { @@ -31,7 +41,7 @@ * * @return the placeholder which is displayed when no selections have been made yet */ - String value() default ""; + String value(); /** * The minimum amount of values a user has to select. @@ -53,10 +63,4 @@ */ int maxValue() default 1; - /** - * Whether this button should send ephemeral replies by default. - * - * @return {@code true} if to send ephemeral replies - */ - boolean ephemeral() default false; } diff --git a/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/TextInput.java b/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/TextInput.java index 7ca0b484f..c1de71bc1 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/TextInput.java +++ b/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/TextInput.java @@ -10,6 +10,7 @@ /** * Annotation used to add TextInputs to {@link Modal Modals}. * + * @see Modal * @since 4.0.0 */ @Target(ElementType.PARAMETER) @@ -17,19 +18,19 @@ public @interface TextInput { /** - * The label shown above this text input box + * The placeholder of this TextInput + *
This is the short hint that describes the expected value of the TextInput field. * - * @return Label for the input + * @return Placeholder */ - String label() default ""; + String value(); /** - * The placeholder of this TextInput - *
This is the short hint that describes the expected value of the TextInput field. + * The label shown above this text input box * - * @return Placeholder + * @return Label for the input */ - String value() default ""; + String label() default ""; /** * The default value of this TextInput. diff --git a/src/main/java/com/github/kaktushose/jda/commands/data/package-info.java b/src/main/java/com/github/kaktushose/jda/commands/data/package-info.java deleted file mode 100644 index fc2d5c455..000000000 --- a/src/main/java/com/github/kaktushose/jda/commands/data/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * The data persistence and sorting features of JDA-Commands. - */ -package com.github.kaktushose.jda.commands.data; diff --git a/src/main/java/com/github/kaktushose/jda/commands/dependency/package-info.java b/src/main/java/com/github/kaktushose/jda/commands/dependency/package-info.java index 8ca174c45..cf6192e8b 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dependency/package-info.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dependency/package-info.java @@ -1,4 +1,4 @@ /** - * The Dependency Injection feature of JDA-Commands. + * The Dependency Injection feature of jda-commands. */ package com.github.kaktushose.jda.commands.dependency; diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/DispatcherSupervisor.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/DispatcherSupervisor.java deleted file mode 100644 index 1eba503bc..000000000 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/DispatcherSupervisor.java +++ /dev/null @@ -1,100 +0,0 @@ -package com.github.kaktushose.jda.commands.dispatching; - -import com.github.kaktushose.jda.commands.dispatching.adapter.TypeAdapterRegistry; -import com.github.kaktushose.jda.commands.dispatching.interactions.Context; -import com.github.kaktushose.jda.commands.dispatching.interactions.GenericDispatcher; -import com.github.kaktushose.jda.commands.dispatching.interactions.autocomplete.AutoCompleteDispatcher; -import com.github.kaktushose.jda.commands.dispatching.interactions.commands.CommandDispatcher; -import com.github.kaktushose.jda.commands.dispatching.interactions.commands.SlashCommandContext; -import com.github.kaktushose.jda.commands.dispatching.interactions.components.ComponentDispatcher; -import com.github.kaktushose.jda.commands.dispatching.interactions.modals.ModalDispatcher; -import com.github.kaktushose.jda.commands.dispatching.middleware.MiddlewareRegistry; -import com.github.kaktushose.jda.commands.reflect.ImplementationRegistry; -import com.github.kaktushose.jda.commands.reflect.InteractionRegistry; -import net.dv8tion.jda.api.events.interaction.GenericInteractionCreateEvent; -import net.dv8tion.jda.api.events.interaction.ModalInteractionEvent; -import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent; -import net.dv8tion.jda.api.events.interaction.command.GenericCommandInteractionEvent; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; -import net.dv8tion.jda.api.events.interaction.component.GenericComponentInteractionCreateEvent; -import net.dv8tion.jda.api.hooks.ListenerAdapter; -import org.jetbrains.annotations.NotNull; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; - -/** - * Registry for {@link GenericDispatcher Dispatchers}. Delegates incoming {@link Context} to the respective - * {@link GenericDispatcher}. - * - * @since 4.0.0 - */ -public class DispatcherSupervisor extends ListenerAdapter { - - private static final Logger log = LoggerFactory.getLogger(DispatcherSupervisor.class); - private final Map, GenericDispatcher> dispatchers; - private final InteractionRegistry interactionRegistry; - private final ImplementationRegistry implementationRegistry; - - /** - * Constructs a new DispatcherSupervisor. - */ - public DispatcherSupervisor(MiddlewareRegistry middlewareRegistry, ImplementationRegistry implementationRegistry, InteractionRegistry interactionRegistry, TypeAdapterRegistry adapterRegistry, RuntimeSupervisor runtimeSupervisor) { - dispatchers = new HashMap<>(); - register(GenericCommandInteractionEvent.class, new CommandDispatcher(middlewareRegistry, implementationRegistry, interactionRegistry, adapterRegistry, runtimeSupervisor)); - register(CommandAutoCompleteInteractionEvent.class, new AutoCompleteDispatcher(middlewareRegistry, implementationRegistry, interactionRegistry, adapterRegistry, runtimeSupervisor)); - register(GenericComponentInteractionCreateEvent.class, new ComponentDispatcher(middlewareRegistry, implementationRegistry, interactionRegistry, adapterRegistry, runtimeSupervisor)); - register(ModalInteractionEvent.class, new ModalDispatcher(middlewareRegistry, implementationRegistry, interactionRegistry, adapterRegistry, runtimeSupervisor)); - this.interactionRegistry = interactionRegistry; - this.implementationRegistry = implementationRegistry; - } - - /** - * Registers a new {@link GenericDispatcher}. - * - * @param event a subtype of {@link GenericInteractionCreateEvent} - * @param dispatcher the {@link GenericDispatcher} implementation for the event - */ - public void register(@NotNull Class event, @NotNull GenericDispatcher dispatcher) { - dispatchers.put(event, dispatcher); - log.debug("Registered dispatcher {} for event {}", dispatcher.getClass().getName(), event.getSimpleName()); - } - - /** - * Unregisters a {@link GenericDispatcher} - * - * @param event the {@link GenericInteractionCreateEvent} to unregister any {@link GenericDispatcher} for - */ - public void unregister(@NotNull Class event) { - dispatchers.remove(event); - log.debug("Unregistered dispatcher binding for event {}", event.getSimpleName()); - } - - @Override - public void onGenericInteractionCreate(@NotNull GenericInteractionCreateEvent event) { - Class clazz = event.getClass(); - Optional> key = dispatchers.keySet().stream() - .filter(it -> it.isAssignableFrom(clazz)) - .findFirst(); - if (key.isEmpty()) { - log.debug("No dispatcher found for {}", clazz.getSimpleName()); - return; - } - - Context context = SlashCommandInteractionEvent.class.isAssignableFrom(clazz) - ? new SlashCommandContext((SlashCommandInteractionEvent) event, interactionRegistry, implementationRegistry) - : new Context(event, interactionRegistry, implementationRegistry); - - GenericDispatcher dispatcher = dispatchers.get(key.get()); - log.debug("Calling {}", dispatcher.getClass().getName()); - try { - dispatcher.onEvent(context); - } catch (Exception e) { - //TODO send this as a reply - log.error("Command execution failed!", e); - } - } -} diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/ExpirationStrategy.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/ExpirationStrategy.java new file mode 100644 index 000000000..78ffd3951 --- /dev/null +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/ExpirationStrategy.java @@ -0,0 +1,29 @@ +package com.github.kaktushose.jda.commands.dispatching; + +import com.github.kaktushose.jda.commands.dispatching.events.Event; + +/// Classes implementing [ExpirationStrategy] define a strategy when a [`Runtime`]({@docRoot}/index.html#runtime-concept-heading) should be closed. +/// +/// There are two strategies available: +/// - [Inactivity]: closes a [`Runtime`]({@docRoot}/index.html#runtime-concept-heading) after the passed amount of time +/// - [Explicit]: **Only** closes a runtime when [Event#closeRuntime()] is called. +/// +/// @implNote The [Inactivity] strategy will check all active [`Runtimes`]({@docRoot}/index.html#runtime-concept-heading) at every incoming jda event. If a +/// [`Runtime`]({@docRoot}/index.html#runtime-concept-heading) didn't handle any events in the last *x* minutes, it will be closed. +/// @since 4.0.0 +public sealed interface ExpirationStrategy { + + /// Default [ExpirationStrategy] of [Inactivity], that closes [`Runtimes`]({@docRoot}/index.html#runtime-concept-heading) after *15* minutes of inactivity. + ExpirationStrategy AFTER_15_MINUTES = new ExpirationStrategy.Inactivity(15); + + /// Default [ExpirationStrategy] of [Explicit]. + ExpirationStrategy EXPLICIT = new ExpirationStrategy.Explicit(); + + /// [ExpirationStrategy] that closes a [`Runtime`]({@docRoot}/index.html#runtime-concept-heading) after the passed amount of time. + /// + /// @param afterMinutes the amount of time after a [`Runtime`]({@docRoot}/index.html#runtime-concept-heading) should be closed, in minutes. + record Inactivity(long afterMinutes) implements ExpirationStrategy {} + + /// [ExpirationStrategy] that closes a [`Runtime`]({@docRoot}/index.html#runtime-concept-heading) **only** when [Event#closeRuntime()] has been called. + record Explicit() implements ExpirationStrategy {} +} diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/RuntimeSupervisor.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/RuntimeSupervisor.java deleted file mode 100644 index 1de864ce0..000000000 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/RuntimeSupervisor.java +++ /dev/null @@ -1,254 +0,0 @@ -package com.github.kaktushose.jda.commands.dispatching; - -import com.github.kaktushose.jda.commands.annotations.interactions.Interaction; -import com.github.kaktushose.jda.commands.annotations.interactions.StaticInstance; -import com.github.kaktushose.jda.commands.dependency.DependencyInjector; -import com.github.kaktushose.jda.commands.reflect.interactions.AutoCompleteDefinition; -import com.github.kaktushose.jda.commands.reflect.interactions.GenericInteractionDefinition; -import com.github.kaktushose.jda.commands.reflect.interactions.components.GenericComponentDefinition; -import net.dv8tion.jda.api.events.interaction.ModalInteractionEvent; -import net.dv8tion.jda.api.events.interaction.command.GenericCommandInteractionEvent; -import net.dv8tion.jda.api.events.interaction.component.GenericComponentInteractionCreateEvent; -import net.dv8tion.jda.api.utils.messages.MessageCreateData; -import org.jetbrains.annotations.Nullable; - -import java.lang.reflect.InvocationTargetException; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledThreadPoolExecutor; -import java.util.concurrent.TimeUnit; - -import static com.github.kaktushose.jda.commands.reflect.interactions.CustomId.CUSTOM_ID_REGEX; - -/** - * Supervisor that creates and stores {@link InteractionRuntime InteractionRuntimes}. This supervisor will create a - * new {@link InteractionRuntime} for every command execution with a TTL of 15 minutes. - * - * @since 4.0.0 - */ -public class RuntimeSupervisor { - - private final Map runtimes; - private final ScheduledExecutorService executor; - private final DependencyInjector injector; - - /** - * Constructs a new RuntimeSupervisor. - */ - public RuntimeSupervisor(DependencyInjector injector) { - this.injector = injector; - runtimes = new HashMap<>(); - executor = new ScheduledThreadPoolExecutor(4); - } - - /** - * Creates a new {@link InteractionRuntime}, which will expire after 15 minutes. If the interaction class is instead - * marked with {@link StaticInstance} will instead return the static instance. - * - * @param event the {@link GenericCommandInteractionEvent} to create the {@link InteractionRuntime} for - * @param interaction the {@link GenericInteractionDefinition} to create the {@link InteractionRuntime} from - * @return a new {@link InteractionRuntime} with a TTL of 15 minutes - * @throws InvocationTargetException if the underlying constructor throws an exception - * @throws InstantiationException if the class that declares the underlying constructor represents an abstract class - * @throws IllegalAccessException if this Constructor object is enforcing Java language access control and - * the underlying constructor is inaccessible - */ - public InteractionRuntime newRuntime(GenericCommandInteractionEvent event, GenericInteractionDefinition interaction) - throws InvocationTargetException, InstantiationException, IllegalAccessException { - - Class interactionClass = interaction.getMethod().getDeclaringClass(); - if (interactionClass.isAnnotationPresent(StaticInstance.class)) { - String runtimeId = String.format("s%s", interactionClass.getName().hashCode()); - if (runtimes.containsKey(runtimeId)) { - return runtimes.get(runtimeId); - } - - Object instance = interaction.newInstance(); - injector.inject(instance); - - InteractionRuntime runtime = new InteractionRuntime(runtimeId, instance); - runtimes.put(runtimeId, runtime); - return runtime; - } - - Object instance = interaction.newInstance(); - injector.inject(instance); - - String id = event.getId(); - InteractionRuntime runtime = new InteractionRuntime(id, instance); - - runtimes.put(id, runtime); - executor.schedule(() -> runtimes.remove(id), 15, TimeUnit.MINUTES); - - return runtime; - } - - /** - * Gets the static runtime for the given {@link AutoCompleteDefinition}. If no instance exists yet, creates, stores and then - * returns the instance. - * - * @param autoComplete the {@link GenericInteractionDefinition} to create or get an instance from - * @return an instance of the provided {@link GenericInteractionDefinition} - * @throws InvocationTargetException if the underlying constructor throws an exception - * @throws InstantiationException if the class that declares the underlying constructor represents an abstract class - * @throws IllegalAccessException if this Constructor object is enforcing Java language access control and - * the underlying constructor is inaccessible - */ - public InteractionRuntime newRuntime(AutoCompleteDefinition autoComplete) - throws InvocationTargetException, InstantiationException, IllegalAccessException { - String runtimeId = String.format("s%s", autoComplete.getMethod().getDeclaringClass().getName().hashCode()); - if (runtimes.containsKey(runtimeId)) { - return runtimes.get(runtimeId); - } - - Object instance = autoComplete.newInstance(); - injector.inject(instance); - - InteractionRuntime runtime = new InteractionRuntime(runtimeId, instance); - runtimes.put(runtimeId, runtime); - return runtime; - } - - /** - * Creates a new runtime for the given {@link GenericComponentDefinition}. This runtime will always be static. - * - * @param componentDefinition the {@link GenericComponentDefinition} to create the runtime for - */ - public InteractionRuntime newRuntime(GenericComponentDefinition componentDefinition) { - Class interactionClass = componentDefinition.getMethod().getDeclaringClass(); - String runtimeId = String.format("s%s", interactionClass.getName().hashCode()); - if (runtimes.containsKey(runtimeId)) { - return runtimes.get(runtimeId); - } - - Object instance; - try { - instance = componentDefinition.newInstance(); - } catch (InvocationTargetException | InstantiationException | IllegalAccessException e) { - throw new RuntimeException(e); - } - - injector.inject(instance); - - InteractionRuntime runtime = new InteractionRuntime(runtimeId, instance); - runtimes.put(runtimeId, runtime); - - return runtime; - } - - /** - * Gets an {@link Optional} holding the {@link InteractionRuntime}. Returns an empty {@link Optional} if no - * {@link InteractionRuntime} has been created yet by calling - * {@link #newRuntime(GenericCommandInteractionEvent, GenericInteractionDefinition)}, if the underlying component wasn't - * created by jda-commands or if the {@link InteractionRuntime} expired. - * - * @param event the {@link GenericComponentInteractionCreateEvent} to get the {@link InteractionRuntime} for - * @return an {@link Optional} holding the {@link InteractionRuntime} - */ - public Optional getRuntime(GenericComponentInteractionCreateEvent event) { - return getRuntime(event.getComponentId()); - } - - /** - * Gets an {@link Optional} holding the {@link InteractionRuntime}. Returns an empty {@link Optional} if no - * {@link InteractionRuntime} has been created yet by calling - * {@link #newRuntime(GenericCommandInteractionEvent, GenericInteractionDefinition)}, if the underlying component wasn't - * created by jda-commands or if the {@link InteractionRuntime} expired. - * - * @param event the {@link ModalInteractionEvent} to get the {@link InteractionRuntime} for - * @return an {@link Optional} holding the {@link InteractionRuntime} - */ - public Optional getRuntime(ModalInteractionEvent event) { - return getRuntime(event.getModalId()); - } - - private Optional getRuntime(String interactionId) { - if (!interactionId.matches(CUSTOM_ID_REGEX)) { - return Optional.empty(); - } - String runtimeId = interactionId.split("\\.")[2]; - return Optional.ofNullable(runtimes.get(runtimeId)); - } - - /** - * A runtime used for executing interactions. This class holds the instance of the class annotated with - * {@link Interaction Interaction} where commands, buttonContainers, etc. live in. This runtime can only be used once per - * command execution. - * - * @since 4.0.0 - */ - public static class InteractionRuntime { - private final String runtimeId; - private final Object instance; - private MessageCreateData messageCreateData; - private KeyValueStore keyValueStore; - - /** - * Constructs a new InteractionRuntime. - * - * @param runtimeId the id of this instance, i.e. the snowflake id of the event creating this runtime - * @param instance the instance of the {@link Interaction Interaction} class - */ - public InteractionRuntime(String runtimeId, Object instance) { - this.runtimeId = runtimeId; - this.instance = instance; - keyValueStore = new KeyValueStore(); - } - - /** - * Gets the instance id. - * - * @return the instance id - */ - public String getRuntimeId() { - return runtimeId; - } - - /** - * Gets the instance. - * - * @return the instance - */ - public Object getInstance() { - return instance; - } - - /** - * Gets the latest message that was sent with this runtime. - * - * @return an {@link Optional} holding a {@link MessageCreateData} describing the latest message - */ - public Optional getLatestReply() { - return Optional.ofNullable(messageCreateData); - } - - /** - * Sets the latest message sent with this runtime. - * - * @param messageCreateData a {@link MessageCreateData} describing the latest message - */ - public void setLatestReply(@Nullable MessageCreateData messageCreateData) { - this.messageCreateData = messageCreateData; - } - - /** - * Gets the {@link KeyValueStore} that is bound to this runtime. - * - * @return the {@link KeyValueStore} bound to this runtime - */ - public KeyValueStore getKeyValueStore() { - return keyValueStore; - } - - /** - * Sets the {@link KeyValueStore} that is bound to this runtime. - * - * @param keyValueStore the {@link KeyValueStore} bound to this runtime - */ - public void setKeyValueStore(KeyValueStore keyValueStore) { - this.keyValueStore = keyValueStore; - } - } -} diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/TypeAdapter.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/TypeAdapter.java index bb6ece764..d164331fa 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/TypeAdapter.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/TypeAdapter.java @@ -1,6 +1,6 @@ package com.github.kaktushose.jda.commands.dispatching.adapter; -import com.github.kaktushose.jda.commands.dispatching.interactions.Context; +import net.dv8tion.jda.api.events.interaction.GenericInteractionCreateEvent; import org.jetbrains.annotations.NotNull; import java.util.Optional; @@ -10,19 +10,19 @@ * Generic top level interface for type adapting. * * @param the type the adapter parses - * @see com.github.kaktushose.jda.commands.annotations.Implementation + * @see com.github.kaktushose.jda.commands.annotations.Implementation Implementation * @since 2.0.0 */ @FunctionalInterface -public interface TypeAdapter extends BiFunction> { +public interface TypeAdapter extends BiFunction> { /** * Attempts to parse a String to the given type. * - * @param raw the String to parse - * @param context the {@link Context} + * @param raw the String to parse + * @param event the {@link GenericInteractionCreateEvent} * @return the parsed type or an empty Optional if the parsing fails */ - Optional apply(@NotNull String raw, @NotNull Context context); + @NotNull Optional apply(@NotNull String raw, @NotNull GenericInteractionCreateEvent event); } diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/TypeAdapterRegistry.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/TypeAdapterRegistry.java index 78c7b0c29..0289c12f6 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/TypeAdapterRegistry.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/TypeAdapterRegistry.java @@ -1,10 +1,6 @@ package com.github.kaktushose.jda.commands.dispatching.adapter; import com.github.kaktushose.jda.commands.dispatching.adapter.impl.*; -import com.github.kaktushose.jda.commands.dispatching.interactions.commands.SlashCommandContext; -import com.github.kaktushose.jda.commands.embeds.ErrorMessageFactory; -import com.github.kaktushose.jda.commands.reflect.ParameterDefinition; -import com.github.kaktushose.jda.commands.reflect.interactions.commands.SlashCommandDefinition; import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Role; import net.dv8tion.jda.api.entities.User; @@ -17,7 +13,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.*; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; /** * Central registry for all type adapters. @@ -27,19 +25,17 @@ */ public class TypeAdapterRegistry { + public static final Map, Object> DEFAULT_MAPPINGS = Map.of( + byte.class, ((byte) 0), + short.class, ((short) 0), + int.class, 0, + long.class, 0L, + double.class, 0.0d, + float.class, 0.0f, + boolean.class, false, + char.class, '\u0000' + ); private static final Logger log = LoggerFactory.getLogger(TypeAdapterRegistry.class); - private static final Map, Object> DEFAULT_MAPPINGS = new HashMap<>() { - { - put(byte.class, (byte) 0); - put(short.class, (short) 0); - put(int.class, 0); - put(long.class, 0L); - put(double.class, 0.0d); - put(float.class, 0.0f); - put(boolean.class, false); - put(char.class, '\u0000'); - } - }; private final Map, TypeAdapter> parameterAdapters; /** @@ -50,7 +46,7 @@ public class TypeAdapterRegistry { *
  • String Array
  • *
  • {@link Member}
  • *
  • {@link User}
  • - *
  • {@link TextChannel}
  • + *
  • {@link net.dv8tion.jda.api.entities.channel.middleman.MessageChannel MessageChannel} and subtypes
  • *
  • {@link Role}
  • * */ @@ -66,8 +62,8 @@ public TypeAdapterRegistry() { register(Double.class, new DoubleAdapter()); register(Character.class, new CharacterAdapter()); register(Boolean.class, new BooleanAdapter()); - register(String.class, (TypeAdapter) (raw, guild) -> Optional.of(raw)); - register(String[].class, (TypeAdapter) (raw, guild) -> Optional.of(raw)); + register(String.class, (TypeAdapter) (raw, _) -> Optional.of(raw)); + register(String[].class, (TypeAdapter) (raw, _) -> Optional.of(raw)); // jda specific register(Member.class, new MemberAdapter()); @@ -124,69 +120,5 @@ public Optional> get(@Nullable Class type) { return Optional.ofNullable(parameterAdapters.get(type)); } - /** - * Takes a {@link SlashCommandContext} and attempts to type adapt the command input to the type specified by the - * {@link SlashCommandDefinition}. Cancels the {@link SlashCommandContext} if the type adapting fails. - * - * @param context the {@link SlashCommandContext} to type adapt - */ - public void adapt(@NotNull SlashCommandContext context) { - SlashCommandDefinition command = Objects.requireNonNull(context.getCommand()); - List arguments = new ArrayList<>(); - String[] input = context.getInput(); - ErrorMessageFactory messageFactory = context.getImplementationRegistry().getErrorMessageFactory(); - - log.debug("Type adapting arguments..."); - for (int i = 0; i < command.getActualParameters().size(); i++) { - ParameterDefinition parameter = command.getActualParameters().get(i); - - // if parameter is array don't parse - if (String[].class.isAssignableFrom(parameter.type())) { - log.debug("First parameter is String array. Not adapting arguments"); - arguments.add(input); - break; - } - - String raw; - // current parameter index == total amount of input, check if it's optional else cancel context - if (i >= input.length) { - if (!parameter.isOptional()) { - IllegalStateException exception = new IllegalStateException( - "Command input doesn't match parameter length! Please report this error the the devs of jda-commands." - ); - context.setCancelled(messageFactory.getCommandExecutionFailedMessage(context, exception)); - throw exception; - } - // if the default value is an empty String (thus not present) add a null value to the argument list - // else try to type adapt the default value - if (parameter.defaultValue() == null) { - arguments.add(DEFAULT_MAPPINGS.getOrDefault(parameter.type(), null)); - continue; - } else { - raw = parameter.defaultValue(); - } - } else { - raw = input[i]; - } - - log.debug("Trying to adapt input \"{}\" to type {}", raw, parameter.type().getName()); - - Optional> adapter = get(parameter.type()); - if (adapter.isEmpty()) { - throw new IllegalArgumentException("No type adapter found!"); - } - - Optional parsed = adapter.get().apply(raw, context); - if (parsed.isEmpty()) { - log.debug("Type adapting failed!"); - context.setCancelled(messageFactory.getTypeAdaptingFailedMessage(context)); - break; - } - - arguments.add(parsed.get()); - log.debug("Added \"{}\" to the argument list", parsed.get()); - } - context.setArguments(arguments); - } } diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/AudioChannelAdapter.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/AudioChannelAdapter.java index 761277506..828c83134 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/AudioChannelAdapter.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/AudioChannelAdapter.java @@ -1,10 +1,10 @@ package com.github.kaktushose.jda.commands.dispatching.adapter.impl; -import com.github.kaktushose.jda.commands.Helpers; +import com.github.kaktushose.jda.commands.internal.Helpers; import com.github.kaktushose.jda.commands.dispatching.adapter.TypeAdapter; -import com.github.kaktushose.jda.commands.dispatching.interactions.Context; import net.dv8tion.jda.api.entities.channel.Channel; import net.dv8tion.jda.api.entities.channel.middleman.AudioChannel; +import net.dv8tion.jda.api.events.interaction.GenericInteractionCreateEvent; import org.jetbrains.annotations.NotNull; import java.util.Optional; @@ -19,18 +19,19 @@ public class AudioChannelAdapter implements TypeAdapter { /** * Attempts to parse a String to a {@link AudioChannel}. Accepts both the channel id and name. * - * @param raw the String to parse - * @param context the {@link Context} + * @param raw the String to parse + * @param event the {@link GenericInteractionCreateEvent} * @return the parsed {@link AudioChannel} or an empty Optional if the parsing fails */ + @NotNull @Override - public Optional apply(@NotNull String raw, @NotNull Context context) { - Channel channel = context.getEvent().getChannel(); + public Optional apply(@NotNull String raw, @NotNull GenericInteractionCreateEvent event) { + Channel channel = event.getChannel(); if (channel == null) { return Optional.empty(); } - return Helpers.resolveGuildChannel(context, raw) + return Helpers.resolveGuildChannel(raw, event) .filter(it -> it.getType().isAudio()) .map(AudioChannel.class::cast); } diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/BooleanAdapter.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/BooleanAdapter.java index f34be3cd1..ceed0a002 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/BooleanAdapter.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/BooleanAdapter.java @@ -1,7 +1,7 @@ package com.github.kaktushose.jda.commands.dispatching.adapter.impl; import com.github.kaktushose.jda.commands.dispatching.adapter.TypeAdapter; -import com.github.kaktushose.jda.commands.dispatching.interactions.Context; +import net.dv8tion.jda.api.events.interaction.GenericInteractionCreateEvent; import org.jetbrains.annotations.NotNull; import java.util.Optional; @@ -17,12 +17,13 @@ public class BooleanAdapter implements TypeAdapter { * Attempts to parse a String to a Boolean. Accepts both String literals and {@code 0} or {@code 1}. * Parsing is case-insensitive. * - * @param raw the String to parse - * @param context the {@link Context} + * @param raw the String to parse + * @param event the {@link GenericInteractionCreateEvent} * @return the parsed boolean or an empty Optional if the parsing fails */ + @NotNull @Override - public Optional apply(@NotNull String raw, @NotNull Context context) { + public Optional apply(@NotNull String raw, @NotNull GenericInteractionCreateEvent event) { if ("true".equalsIgnoreCase(raw) || "1".equals(raw)) { return Optional.of(true); } diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/ByteAdapter.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/ByteAdapter.java index 6cff244ad..761e0cb25 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/ByteAdapter.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/ByteAdapter.java @@ -1,7 +1,7 @@ package com.github.kaktushose.jda.commands.dispatching.adapter.impl; import com.github.kaktushose.jda.commands.dispatching.adapter.TypeAdapter; -import com.github.kaktushose.jda.commands.dispatching.interactions.Context; +import net.dv8tion.jda.api.events.interaction.GenericInteractionCreateEvent; import org.jetbrains.annotations.NotNull; import java.util.Optional; @@ -16,12 +16,13 @@ public class ByteAdapter implements TypeAdapter { /** * Attempts to parse a String to a Byte. * - * @param raw the String to parse - * @param context the {@link Context} + * @param raw the String to parse + * @param event the {@link GenericInteractionCreateEvent} * @return the parsed Byte or an empty Optional if the parsing fails */ + @NotNull @Override - public Optional apply(@NotNull String raw, @NotNull Context context) { + public Optional apply(@NotNull String raw, @NotNull GenericInteractionCreateEvent event) { try { return Optional.of(Byte.valueOf(raw)); } catch (NumberFormatException ignored) { diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/CharacterAdapter.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/CharacterAdapter.java index 295379dd6..a58a30923 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/CharacterAdapter.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/CharacterAdapter.java @@ -1,7 +1,7 @@ package com.github.kaktushose.jda.commands.dispatching.adapter.impl; import com.github.kaktushose.jda.commands.dispatching.adapter.TypeAdapter; -import com.github.kaktushose.jda.commands.dispatching.interactions.Context; +import net.dv8tion.jda.api.events.interaction.GenericInteractionCreateEvent; import org.jetbrains.annotations.NotNull; import java.util.Optional; @@ -16,12 +16,13 @@ public class CharacterAdapter implements TypeAdapter { /** * Casts a String to a Char if and only if {@code raw.length == 1}. Else, returns an empty Optional. * - * @param raw the String to parse - * @param context the {@link Context} + * @param raw the String to parse + * @param event the {@link GenericInteractionCreateEvent} * @return the parsed Char or an empty Optional if the parsing fails */ + @NotNull @Override - public Optional apply(@NotNull String raw, @NotNull Context context) { + public Optional apply(@NotNull String raw, @NotNull GenericInteractionCreateEvent event) { if (raw.length() == 1) { return Optional.of(raw.charAt(0)); } diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/DoubleAdapter.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/DoubleAdapter.java index 891ba7202..93da24305 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/DoubleAdapter.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/DoubleAdapter.java @@ -1,7 +1,7 @@ package com.github.kaktushose.jda.commands.dispatching.adapter.impl; import com.github.kaktushose.jda.commands.dispatching.adapter.TypeAdapter; -import com.github.kaktushose.jda.commands.dispatching.interactions.Context; +import net.dv8tion.jda.api.events.interaction.GenericInteractionCreateEvent; import org.jetbrains.annotations.NotNull; import java.util.Optional; @@ -16,12 +16,13 @@ public class DoubleAdapter implements TypeAdapter { /** * Attempts to parse a String to a Double. * - * @param raw the String to parse - * @param context the {@link Context} + * @param raw the String to parse + * @param event the {@link GenericInteractionCreateEvent} * @return the parsed Double or an empty Optional if the parsing fails */ + @NotNull @Override - public Optional apply(@NotNull String raw, @NotNull Context context) { + public Optional apply(@NotNull String raw, @NotNull GenericInteractionCreateEvent event) { try { return Optional.of(Double.valueOf(raw)); } catch (NumberFormatException ignored) { diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/FloatAdapter.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/FloatAdapter.java index 5fd3b5c2e..cdf56c273 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/FloatAdapter.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/FloatAdapter.java @@ -1,7 +1,7 @@ package com.github.kaktushose.jda.commands.dispatching.adapter.impl; import com.github.kaktushose.jda.commands.dispatching.adapter.TypeAdapter; -import com.github.kaktushose.jda.commands.dispatching.interactions.Context; +import net.dv8tion.jda.api.events.interaction.GenericInteractionCreateEvent; import org.jetbrains.annotations.NotNull; import java.util.Optional; @@ -16,12 +16,13 @@ public class FloatAdapter implements TypeAdapter { /** * Attempts to parse a String to a Float. * - * @param raw the String to parse - * @param context the {@link Context} + * @param raw the String to parse + * @param event the {@link GenericInteractionCreateEvent} * @return the parsed Float or an empty Optional if the parsing fails */ + @NotNull @Override - public Optional apply(@NotNull String raw, @NotNull Context context) { + public Optional apply(@NotNull String raw, @NotNull GenericInteractionCreateEvent event) { try { return Optional.of(Float.valueOf(raw)); } catch (NumberFormatException ignored) { diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/GuildChannelAdapter.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/GuildChannelAdapter.java index aaa78ed8f..3fa37a155 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/GuildChannelAdapter.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/GuildChannelAdapter.java @@ -1,10 +1,10 @@ package com.github.kaktushose.jda.commands.dispatching.adapter.impl; -import com.github.kaktushose.jda.commands.Helpers; +import com.github.kaktushose.jda.commands.internal.Helpers; import com.github.kaktushose.jda.commands.dispatching.adapter.TypeAdapter; -import com.github.kaktushose.jda.commands.dispatching.interactions.Context; import net.dv8tion.jda.api.entities.channel.Channel; import net.dv8tion.jda.api.entities.channel.middleman.GuildChannel; +import net.dv8tion.jda.api.events.interaction.GenericInteractionCreateEvent; import org.jetbrains.annotations.NotNull; import java.util.Optional; @@ -19,17 +19,18 @@ public class GuildChannelAdapter implements TypeAdapter { /** * Attempts to parse a String to a {@link GuildChannel}. Accepts both the channel id and name. * - * @param raw the String to parse - * @param context the {@link Context} + * @param raw the String to parse + * @param event the {@link GenericInteractionCreateEvent} * @return the parsed {@link GuildChannel} or an empty Optional if the parsing fails */ + @NotNull @Override - public Optional apply(@NotNull String raw, @NotNull Context context) { - Channel channel = context.getEvent().getChannel(); + public Optional apply(@NotNull String raw, @NotNull GenericInteractionCreateEvent event) { + Channel channel = event.getChannel(); if (channel == null) { return Optional.empty(); } - return Helpers.resolveGuildChannel(context, raw); + return Helpers.resolveGuildChannel(raw, event); } } diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/GuildMessageChannelAdapter.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/GuildMessageChannelAdapter.java index 917285bdd..93607c0a9 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/GuildMessageChannelAdapter.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/GuildMessageChannelAdapter.java @@ -1,10 +1,10 @@ package com.github.kaktushose.jda.commands.dispatching.adapter.impl; -import com.github.kaktushose.jda.commands.Helpers; +import com.github.kaktushose.jda.commands.internal.Helpers; import com.github.kaktushose.jda.commands.dispatching.adapter.TypeAdapter; -import com.github.kaktushose.jda.commands.dispatching.interactions.Context; import net.dv8tion.jda.api.entities.channel.Channel; import net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel; +import net.dv8tion.jda.api.events.interaction.GenericInteractionCreateEvent; import org.jetbrains.annotations.NotNull; import java.util.Optional; @@ -19,18 +19,19 @@ public class GuildMessageChannelAdapter implements TypeAdapter apply(@NotNull String raw, @NotNull Context context) { - Channel channel = context.getEvent().getChannel(); + public Optional apply(@NotNull String raw, @NotNull GenericInteractionCreateEvent event) { + Channel channel = event.getChannel(); if (channel == null) { return Optional.empty(); } - return Helpers.resolveGuildChannel(context, raw) + return Helpers.resolveGuildChannel(raw, event) .filter(it -> it.getType().isMessage()) .map(GuildMessageChannel.class::cast); } diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/IntegerAdapter.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/IntegerAdapter.java index af18275a0..8ad95d21c 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/IntegerAdapter.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/IntegerAdapter.java @@ -1,7 +1,7 @@ package com.github.kaktushose.jda.commands.dispatching.adapter.impl; import com.github.kaktushose.jda.commands.dispatching.adapter.TypeAdapter; -import com.github.kaktushose.jda.commands.dispatching.interactions.Context; +import net.dv8tion.jda.api.events.interaction.GenericInteractionCreateEvent; import org.jetbrains.annotations.NotNull; import java.util.Optional; @@ -16,12 +16,13 @@ public class IntegerAdapter implements TypeAdapter { /** * Attempts to parse a String to an Integer. * - * @param raw the String to parse - * @param context the {@link Context} + * @param raw the String to parse + * @param event the {@link GenericInteractionCreateEvent} * @return the parsed Integer or an empty Optional if the parsing fails */ + @NotNull @Override - public Optional apply(@NotNull String raw, @NotNull Context context) { + public Optional apply(@NotNull String raw, @NotNull GenericInteractionCreateEvent event) { try { return Optional.of((int) Double.parseDouble(raw)); } catch (NumberFormatException ignored) { diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/LongAdapter.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/LongAdapter.java index 7b753cf47..53151a042 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/LongAdapter.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/LongAdapter.java @@ -1,7 +1,7 @@ package com.github.kaktushose.jda.commands.dispatching.adapter.impl; import com.github.kaktushose.jda.commands.dispatching.adapter.TypeAdapter; -import com.github.kaktushose.jda.commands.dispatching.interactions.Context; +import net.dv8tion.jda.api.events.interaction.GenericInteractionCreateEvent; import org.jetbrains.annotations.NotNull; import java.util.Optional; @@ -16,12 +16,13 @@ public class LongAdapter implements TypeAdapter { /** * Attempts to parse a String to a Long. * - * @param raw the String to parse - * @param context the {@link Context} + * @param raw the String to parse + * @param event the {@link GenericInteractionCreateEvent} * @return the parsed Long or an empty Optional if the parsing fails */ + @NotNull @Override - public Optional apply(@NotNull String raw, @NotNull Context context) { + public Optional apply(@NotNull String raw, @NotNull GenericInteractionCreateEvent event) { try { return Optional.of((long) Double.parseDouble(raw)); } catch (NumberFormatException ignored) { diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/MemberAdapter.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/MemberAdapter.java index e7faf5e64..cde583edf 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/MemberAdapter.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/MemberAdapter.java @@ -1,10 +1,10 @@ package com.github.kaktushose.jda.commands.dispatching.adapter.impl; -import com.github.kaktushose.jda.commands.Helpers; +import com.github.kaktushose.jda.commands.internal.Helpers; import com.github.kaktushose.jda.commands.dispatching.adapter.TypeAdapter; -import com.github.kaktushose.jda.commands.dispatching.interactions.Context; import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.events.interaction.GenericInteractionCreateEvent; import net.dv8tion.jda.api.exceptions.ErrorResponseException; import org.jetbrains.annotations.NotNull; @@ -20,20 +20,21 @@ public class MemberAdapter implements TypeAdapter { /** * Attempts to parse a String to a {@link Member}. Accepts both the member id and name. * - * @param raw the String to parse - * @param context the {@link Context} + * @param raw the String to parse + * @param event the {@link GenericInteractionCreateEvent} * @return the parsed {@link Member} or an empty Optional if the parsing fails */ + @NotNull @Override - public Optional apply(@NotNull String raw, @NotNull Context context) { - if (context.getEvent().getGuild() == null) { + public Optional apply(@NotNull String raw, @NotNull GenericInteractionCreateEvent event) { + if (event.getGuild() == null) { return Optional.empty(); } Member member; raw = Helpers.sanitizeMention(raw); - Guild guild = context.getEvent().getGuild(); + Guild guild = event.getGuild(); if (raw.matches("\\d+")) { try { member = guild.retrieveMemberById(raw).complete(); diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/NewsChannelAdapter.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/NewsChannelAdapter.java index 778e7f94a..7f5bf70d2 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/NewsChannelAdapter.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/NewsChannelAdapter.java @@ -1,10 +1,10 @@ package com.github.kaktushose.jda.commands.dispatching.adapter.impl; -import com.github.kaktushose.jda.commands.Helpers; +import com.github.kaktushose.jda.commands.internal.Helpers; import com.github.kaktushose.jda.commands.dispatching.adapter.TypeAdapter; -import com.github.kaktushose.jda.commands.dispatching.interactions.Context; import net.dv8tion.jda.api.entities.channel.Channel; import net.dv8tion.jda.api.entities.channel.concrete.NewsChannel; +import net.dv8tion.jda.api.events.interaction.GenericInteractionCreateEvent; import org.jetbrains.annotations.NotNull; import java.util.Optional; @@ -19,17 +19,18 @@ public class NewsChannelAdapter implements TypeAdapter { /** * Attempts to parse a String to a {@link NewsChannel}. Accepts both the channel id and name. * - * @param raw the String to parse - * @param context the {@link Context} + * @param raw the String to parse + * @param event the {@link GenericInteractionCreateEvent} * @return the parsed {@link NewsChannel} or an empty Optional if the parsing fails */ + @NotNull @Override - public Optional apply(@NotNull String raw, @NotNull Context context) { - Channel channel = context.getEvent().getChannel(); + public Optional apply(@NotNull String raw, @NotNull GenericInteractionCreateEvent event) { + Channel channel = event.getChannel(); if (channel == null) { return Optional.empty(); } - return Helpers.resolveGuildChannel(context, raw).filter(NewsChannel.class::isInstance).map(NewsChannel.class::cast); + return Helpers.resolveGuildChannel(raw, event).filter(NewsChannel.class::isInstance).map(NewsChannel.class::cast); } } diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/RoleAdapter.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/RoleAdapter.java index 7f0de03da..2c550bd4c 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/RoleAdapter.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/RoleAdapter.java @@ -1,10 +1,10 @@ package com.github.kaktushose.jda.commands.dispatching.adapter.impl; -import com.github.kaktushose.jda.commands.Helpers; +import com.github.kaktushose.jda.commands.internal.Helpers; import com.github.kaktushose.jda.commands.dispatching.adapter.TypeAdapter; -import com.github.kaktushose.jda.commands.dispatching.interactions.Context; import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.Role; +import net.dv8tion.jda.api.events.interaction.GenericInteractionCreateEvent; import org.jetbrains.annotations.NotNull; import java.util.Optional; @@ -19,20 +19,21 @@ public class RoleAdapter implements TypeAdapter { /** * Attempts to parse a String to a {@link Role}. Accepts both the role id and name. * - * @param raw the String to parse - * @param context the {@link Context} + * @param raw the String to parse + * @param event the {@link GenericInteractionCreateEvent} * @return the parsed {@link Role} or an empty Optional if the parsing fails */ + @NotNull @Override - public Optional apply(@NotNull String raw, @NotNull Context context) { - if (context.getEvent().getGuild() == null) { + public Optional apply(@NotNull String raw, @NotNull GenericInteractionCreateEvent event) { + if (event.getGuild() == null) { return Optional.empty(); } Role role; raw = Helpers.sanitizeMention(raw); - Guild guild = context.getEvent().getGuild(); + Guild guild = event.getGuild(); if (raw.matches("\\d+")) { role = guild.getRoleById(raw); } else { diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/ShortAdapter.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/ShortAdapter.java index 8a4d4fe34..dbf9bb271 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/ShortAdapter.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/ShortAdapter.java @@ -1,7 +1,7 @@ package com.github.kaktushose.jda.commands.dispatching.adapter.impl; import com.github.kaktushose.jda.commands.dispatching.adapter.TypeAdapter; -import com.github.kaktushose.jda.commands.dispatching.interactions.Context; +import net.dv8tion.jda.api.events.interaction.GenericInteractionCreateEvent; import org.jetbrains.annotations.NotNull; import java.util.Optional; @@ -16,12 +16,13 @@ public class ShortAdapter implements TypeAdapter { /** * Attempts to parse a String to a Short. * - * @param raw the String to parse - * @param context the {@link Context} + * @param raw the String to parse + * @param event the {@link GenericInteractionCreateEvent} * @return the parsed Short or an empty Optional if the parsing fails */ + @NotNull @Override - public Optional apply(@NotNull String raw, @NotNull Context context) { + public Optional apply(@NotNull String raw, @NotNull GenericInteractionCreateEvent event) { try { return Optional.of(Short.valueOf(raw)); } catch (NumberFormatException ignored) { diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/StageChannelAdapter.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/StageChannelAdapter.java index 4e57998cd..8f7dba30a 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/StageChannelAdapter.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/StageChannelAdapter.java @@ -1,10 +1,10 @@ package com.github.kaktushose.jda.commands.dispatching.adapter.impl; -import com.github.kaktushose.jda.commands.Helpers; +import com.github.kaktushose.jda.commands.internal.Helpers; import com.github.kaktushose.jda.commands.dispatching.adapter.TypeAdapter; -import com.github.kaktushose.jda.commands.dispatching.interactions.Context; import net.dv8tion.jda.api.entities.channel.Channel; import net.dv8tion.jda.api.entities.channel.concrete.StageChannel; +import net.dv8tion.jda.api.events.interaction.GenericInteractionCreateEvent; import org.jetbrains.annotations.NotNull; import java.util.Optional; @@ -19,17 +19,18 @@ public class StageChannelAdapter implements TypeAdapter { /** * Attempts to parse a String to a {@link StageChannel}. Accepts both the channel id and name. * - * @param raw the String to parse - * @param context the {@link Context} + * @param raw the String to parse + * @param event the {@link GenericInteractionCreateEvent} * @return the parsed {@link StageChannel} or an empty Optional if the parsing fails */ + @NotNull @Override - public Optional apply(@NotNull String raw, @NotNull Context context) { - Channel channel = context.getEvent().getChannel(); + public Optional apply(@NotNull String raw, @NotNull GenericInteractionCreateEvent event) { + Channel channel = event.getChannel(); if (channel == null) { return Optional.empty(); } - return Helpers.resolveGuildChannel(context, raw).filter(StageChannel.class::isInstance).map(StageChannel.class::cast); + return Helpers.resolveGuildChannel(raw, event).filter(StageChannel.class::isInstance).map(StageChannel.class::cast); } } diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/TextChannelAdapter.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/TextChannelAdapter.java index 38dd0a4a0..7732b244e 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/TextChannelAdapter.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/TextChannelAdapter.java @@ -1,9 +1,9 @@ package com.github.kaktushose.jda.commands.dispatching.adapter.impl; -import com.github.kaktushose.jda.commands.Helpers; +import com.github.kaktushose.jda.commands.internal.Helpers; import com.github.kaktushose.jda.commands.dispatching.adapter.TypeAdapter; -import com.github.kaktushose.jda.commands.dispatching.interactions.Context; import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; +import net.dv8tion.jda.api.events.interaction.GenericInteractionCreateEvent; import org.jetbrains.annotations.NotNull; import java.util.Optional; @@ -18,16 +18,17 @@ public class TextChannelAdapter implements TypeAdapter { /** * Attempts to parse a String to a {@link TextChannel}. Accepts both the channel id and name. * - * @param raw the String to parse - * @param context the {@link Context} + * @param raw the String to parse + * @param event the {@link GenericInteractionCreateEvent} * @return the parsed {@link TextChannel} or an empty Optional if the parsing fails */ + @NotNull @Override - public Optional apply(@NotNull String raw, @NotNull Context context) { - if (context.getEvent().getGuild() == null) { + public Optional apply(@NotNull String raw, @NotNull GenericInteractionCreateEvent event) { + if (event.getGuild() == null) { return Optional.empty(); } - return Helpers.resolveGuildChannel(context, raw).filter(TextChannel.class::isInstance).map(TextChannel.class::cast); + return Helpers.resolveGuildChannel(raw, event).filter(TextChannel.class::isInstance).map(TextChannel.class::cast); } } diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/ThreadChannelAdapter.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/ThreadChannelAdapter.java index 7a797d9c1..d0c3fd180 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/ThreadChannelAdapter.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/ThreadChannelAdapter.java @@ -1,10 +1,10 @@ package com.github.kaktushose.jda.commands.dispatching.adapter.impl; -import com.github.kaktushose.jda.commands.Helpers; +import com.github.kaktushose.jda.commands.internal.Helpers; import com.github.kaktushose.jda.commands.dispatching.adapter.TypeAdapter; -import com.github.kaktushose.jda.commands.dispatching.interactions.Context; import net.dv8tion.jda.api.entities.channel.Channel; import net.dv8tion.jda.api.entities.channel.concrete.ThreadChannel; +import net.dv8tion.jda.api.events.interaction.GenericInteractionCreateEvent; import org.jetbrains.annotations.NotNull; import java.util.Optional; @@ -19,17 +19,18 @@ public class ThreadChannelAdapter implements TypeAdapter { /** * Attempts to parse a String to a {@link ThreadChannel}. Accepts both the channel id and name. * - * @param raw the String to parse - * @param context the {@link Context} + * @param raw the String to parse + * @param event the {@link GenericInteractionCreateEvent} * @return the parsed {@link ThreadChannel} or an empty Optional if the parsing fails */ + @NotNull @Override - public Optional apply(@NotNull String raw, @NotNull Context context) { - Channel channel = context.getEvent().getChannel(); + public Optional apply(@NotNull String raw, @NotNull GenericInteractionCreateEvent event) { + Channel channel = event.getChannel(); if (channel == null) { return Optional.empty(); } - return Helpers.resolveGuildChannel(context, raw).filter(ThreadChannel.class::isInstance).map(ThreadChannel.class::cast); + return Helpers.resolveGuildChannel(raw, event).filter(ThreadChannel.class::isInstance).map(ThreadChannel.class::cast); } } diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/UserAdapter.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/UserAdapter.java index 314f8cb59..c812e0505 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/UserAdapter.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/UserAdapter.java @@ -1,10 +1,10 @@ package com.github.kaktushose.jda.commands.dispatching.adapter.impl; -import com.github.kaktushose.jda.commands.Helpers; +import com.github.kaktushose.jda.commands.internal.Helpers; import com.github.kaktushose.jda.commands.dispatching.adapter.TypeAdapter; -import com.github.kaktushose.jda.commands.dispatching.interactions.Context; import net.dv8tion.jda.api.JDA; import net.dv8tion.jda.api.entities.User; +import net.dv8tion.jda.api.events.interaction.GenericInteractionCreateEvent; import net.dv8tion.jda.api.exceptions.ErrorResponseException; import org.jetbrains.annotations.NotNull; @@ -20,15 +20,16 @@ public class UserAdapter implements TypeAdapter { /** * Attempts to parse a String to a {@link User}. Accepts both the user id and name. * - * @param raw the String to parse - * @param context the {@link Context} + * @param raw the String to parse + * @param event the {@link GenericInteractionCreateEvent} * @return the parsed {@link User} or an empty Optional if the parsing fails */ + @NotNull @Override - public Optional apply(@NotNull String raw, @NotNull Context context) { + public Optional apply(@NotNull String raw, @NotNull GenericInteractionCreateEvent event) { User user; raw = Helpers.sanitizeMention(raw); - JDA jda = context.getEvent().getJDA(); + JDA jda = event.getJDA(); if (raw.matches("\\d+")) { try { user = jda.retrieveUserById(raw).complete(); diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/VoiceChannelAdapter.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/VoiceChannelAdapter.java index 6a804e3a2..21562b260 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/VoiceChannelAdapter.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/adapter/impl/VoiceChannelAdapter.java @@ -1,10 +1,10 @@ package com.github.kaktushose.jda.commands.dispatching.adapter.impl; -import com.github.kaktushose.jda.commands.Helpers; +import com.github.kaktushose.jda.commands.internal.Helpers; import com.github.kaktushose.jda.commands.dispatching.adapter.TypeAdapter; -import com.github.kaktushose.jda.commands.dispatching.interactions.Context; import net.dv8tion.jda.api.entities.channel.Channel; import net.dv8tion.jda.api.entities.channel.concrete.VoiceChannel; +import net.dv8tion.jda.api.events.interaction.GenericInteractionCreateEvent; import org.jetbrains.annotations.NotNull; import java.util.Optional; @@ -19,17 +19,18 @@ public class VoiceChannelAdapter implements TypeAdapter { /** * Attempts to parse a String to a {@link VoiceChannel}. Accepts both the channel id and name. * - * @param raw the String to parse - * @param context the {@link Context} + * @param raw the String to parse + * @param event the {@link GenericInteractionCreateEvent} * @return the parsed {@link VoiceChannel} or an empty Optional if the parsing fails */ + @NotNull @Override - public Optional apply(@NotNull String raw, @NotNull Context context) { - Channel channel = context.getEvent().getChannel(); + public Optional apply(@NotNull String raw, @NotNull GenericInteractionCreateEvent event) { + Channel channel = event.getChannel(); if (channel == null) { return Optional.empty(); } - return Helpers.resolveGuildChannel(context, raw).filter(VoiceChannel.class::isInstance).map(VoiceChannel.class::cast); + return Helpers.resolveGuildChannel(raw, event).filter(VoiceChannel.class::isInstance).map(VoiceChannel.class::cast); } } diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/context/InvocationContext.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/context/InvocationContext.java new file mode 100644 index 000000000..95e9cafd8 --- /dev/null +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/context/InvocationContext.java @@ -0,0 +1,33 @@ +package com.github.kaktushose.jda.commands.dispatching.context; + +import com.github.kaktushose.jda.commands.dispatching.reply.MessageReply; +import com.github.kaktushose.jda.commands.reflect.interactions.GenericInteractionDefinition; +import com.github.kaktushose.jda.commands.reflect.interactions.ReplyConfig; +import net.dv8tion.jda.api.events.interaction.GenericInteractionCreateEvent; +import net.dv8tion.jda.api.utils.messages.MessageCreateData; + +import java.util.SequencedCollection; + +/// Bundles data that is important for the execution of an interaction, especially for invoking the user defined method. +/// +/// @param The used type of [GenericInteractionCreateEvent] +/// @param event the underlying jda event +/// @param keyValueStore the [KeyValueStore] belonging to this interaction over its whole lifetime +/// @param arguments the arguments used to call the final user defined method via [GenericInteractionDefinition#invoke(java.lang.Object, com.github.kaktushose.jda.commands.dispatching.context.InvocationContext)] +/// @param definition the [GenericInteractionDefinition] defining this interaction (referring to the user defined method) +public record InvocationContext( + T event, + KeyValueStore keyValueStore, + GenericInteractionDefinition definition, + SequencedCollection arguments +) { + /// Stops further execution of this invocation at the next suitable moment. + /// + /// @implNote This will interrupt the current event thread + /// @param errorMessage the error message that should be sent to the user as a reply + public void cancel(MessageCreateData errorMessage) { + new MessageReply(event, definition, new ReplyConfig()).reply(errorMessage); + + Thread.currentThread().interrupt(); + } +} diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/KeyValueStore.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/context/KeyValueStore.java similarity index 88% rename from src/main/java/com/github/kaktushose/jda/commands/dispatching/KeyValueStore.java rename to src/main/java/com/github/kaktushose/jda/commands/dispatching/context/KeyValueStore.java index 801903e8f..cb4377f68 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/KeyValueStore.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/context/KeyValueStore.java @@ -1,10 +1,10 @@ -package com.github.kaktushose.jda.commands.dispatching; +package com.github.kaktushose.jda.commands.dispatching.context; import org.jetbrains.annotations.NotNull; +import java.util.HashMap; import java.util.Map; import java.util.Optional; -import java.util.concurrent.ConcurrentHashMap; /** * A simple key-value-store to store variables between events. @@ -13,11 +13,7 @@ */ public class KeyValueStore { - private final Map values; - - public KeyValueStore() { - values = new ConcurrentHashMap<>(); - } + private final Map values = new HashMap<>(); /** * Gets a value. diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/context/package-info.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/context/package-info.java new file mode 100644 index 000000000..b4cc1ac0c --- /dev/null +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/context/package-info.java @@ -0,0 +1,2 @@ +/// Context objects that are bound to every event handling. +package com.github.kaktushose.jda.commands.dispatching.context; \ No newline at end of file diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/events/Event.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/events/Event.java new file mode 100644 index 000000000..ac3f2db1e --- /dev/null +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/events/Event.java @@ -0,0 +1,210 @@ +package com.github.kaktushose.jda.commands.dispatching.events; + +import com.github.kaktushose.jda.commands.annotations.interactions.EntitySelectMenu; +import com.github.kaktushose.jda.commands.annotations.interactions.StringSelectMenu; +import com.github.kaktushose.jda.commands.dispatching.events.interactions.ComponentEvent; +import com.github.kaktushose.jda.commands.dispatching.events.interactions.ModalEvent; +import com.github.kaktushose.jda.commands.dispatching.internal.Runtime; +import com.github.kaktushose.jda.commands.dispatching.context.KeyValueStore; +import com.github.kaktushose.jda.commands.dispatching.events.interactions.AutoCompleteEvent; +import com.github.kaktushose.jda.commands.dispatching.events.interactions.CommandEvent; +import com.github.kaktushose.jda.commands.dispatching.middleware.Middleware; +import com.github.kaktushose.jda.commands.reflect.InteractionRegistry; +import com.github.kaktushose.jda.commands.reflect.interactions.components.ButtonDefinition; +import com.github.kaktushose.jda.commands.reflect.interactions.components.menus.GenericSelectMenuDefinition; +import net.dv8tion.jda.api.JDA; +import net.dv8tion.jda.api.entities.Entitlement; +import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.entities.User; +import net.dv8tion.jda.api.entities.channel.Channel; +import net.dv8tion.jda.api.events.interaction.GenericInteractionCreateEvent; +import net.dv8tion.jda.api.interactions.DiscordLocale; +import net.dv8tion.jda.api.interactions.Interaction; +import net.dv8tion.jda.api.interactions.components.buttons.Button; +import net.dv8tion.jda.api.interactions.components.selections.SelectMenu; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + + +/// Abstract base event for all interaction events, like [CommandEvent]. +/// +/// This class also holds the [GenericInteractionCreateEvent] and provides some shortcut methods to directly access its +/// content. +/// +/// +/// @param the type of [GenericInteractionCreateEvent] this event represents +/// @see AutoCompleteEvent +/// @see CommandEvent +/// @see ComponentEvent +/// @see ModalEvent +/// @since 4.0.0 +public abstract sealed class Event implements Interaction + permits ReplyableEvent, AutoCompleteEvent { + + protected final T event; + protected final InteractionRegistry interactionRegistry; + private final Runtime runtime; + + /// Constructs a new Event. + /// + /// @param event the subtype [T] of [GenericInteractionCreateEvent] + /// @param interactionRegistry the corresponding [InteractionRegistry] + /// @param runtime the [Runtime] this event lives in + protected Event(@NotNull T event, @NotNull InteractionRegistry interactionRegistry, @NotNull Runtime runtime) { + this.event = event; + this.interactionRegistry = interactionRegistry; + this.runtime = runtime; + } + + /// Returns the underlying [GenericInteractionCreateEvent] of this event + /// + /// @return the [GenericInteractionCreateEvent] + @NotNull + public T jdaEvent() { + return this.event; + } + + /// Gets a [`Button`][com.github.kaktushose.jda.commands.annotations.interactions.Button] based on the method name + /// and transforms it into a JDA [Button]. + /// + /// The button will be linked to the current [`Runtime`]({@docRoot}/index.html#runtime-concept-heading). This may be useful if you want to send a component without + /// using the framework. + /// + /// @param button the name of the button + /// @return the JDA [Button] + @NotNull + public Button getButton(@NotNull String button) { + if (!button.matches("[a-zA-Z]+\\.[a-zA-Z]+")) { + throw new IllegalArgumentException("Unknown Button"); + } + + String sanitizedId = button.replaceAll("\\.", ""); + ButtonDefinition buttonDefinition = interactionRegistry.getButtons().stream() + .filter(it -> it.getDefinitionId().equals(sanitizedId)) + .findFirst().orElseThrow(() -> new IllegalArgumentException("Unknown Button")); + + return buttonDefinition.toButton().withId(buttonDefinition.boundCustomId(runtimeId())); + } + + /// Gets a [StringSelectMenu] or [EntitySelectMenu] based on the method name and transforms it into a JDA [SelectMenu]. + /// + /// The select menu will be linked to the current [`Runtime`]({@docRoot}/index.html#runtime-concept-heading). This may be useful if you want to send a component + /// without using the framework. + /// + /// @param menu the name of the select menu + /// @return the JDA [SelectMenu] + @NotNull + public SelectMenu getSelectMenu(@NotNull String menu) { + if (!menu.matches("[a-zA-Z]+\\.[a-zA-Z]+")) { + throw new IllegalArgumentException("Unknown Select Menu"); + } + + String sanitizedId = menu.replaceAll("\\.", ""); + GenericSelectMenuDefinition selectMenuDefinition = interactionRegistry.getSelectMenus().stream() + .filter(it -> it.getDefinitionId().equals(sanitizedId)) + .findFirst().orElseThrow(() -> new IllegalArgumentException("Unknown Select Menu")); + + return selectMenuDefinition.toSelectMenu(runtimeId(), true); + } + + /// Returns the id of the [`Runtime`]({@docRoot}/index.html#runtime-concept-heading) this event is dispatched in. + /// + /// + /// @return the id of the current [`Runtime`]({@docRoot}/index.html#runtime-concept-heading) + @NotNull + public String runtimeId() { + return runtime.id(); + } + + /// Closes the underlying [`Runtime`]({@docRoot}/index.html#runtime-concept-heading). This will ignore any new jda events belonging to this interaction, resulting + /// in the freeing of occupied resources for gc. + /// + /// This is only needed if the expiration strategy + /// [com.github.kaktushose.jda.commands.dispatching.ExpirationStrategy.Explicit] is used. + public void closeRuntime() { + runtime.close(); + } + + /// Returns the [KeyValueStore] of this [`Runtime`]({@docRoot}/index.html#runtime-concept-heading). + /// + /// The [KeyValueStore] can be accessed during the [Middleware] execution as well as any + /// interaction execution. Its content will be the same as long as the executions take place in the same [`Runtime`]({@docRoot}/index.html#runtime-concept-heading). + /// + /// @return the [KeyValueStore] + @NotNull + public KeyValueStore kv() { + return runtime.keyValueStore(); + } + + + @Override + public int getTypeRaw() { + return event.getTypeRaw(); + } + + @NotNull + @Override + public String getToken() { + return event.getToken(); + } + + @Nullable + @Override + public Guild getGuild() { + return event.getGuild(); + } + + @NotNull + @Override + public User getUser() { + return event.getUser(); + } + + @Nullable + @Override + public Member getMember() { + return event.getMember(); + } + + @Override + public boolean isAcknowledged() { + return event.isAcknowledged(); + } + + @Nullable + @Override + public Channel getChannel() { + return event.getChannel(); + } + + @Override + public long getChannelIdLong() { + return event.getChannelIdLong(); + } + + @NotNull + @Override + public DiscordLocale getUserLocale() { + return event.getUserLocale(); + } + + @NotNull + @Override + public List getEntitlements() { + return event.getEntitlements(); + } + + @NotNull + @Override + public JDA getJDA() { + return event.getJDA(); + } + + @Override + public long getIdLong() { + return event.getIdLong(); + } +} diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/events/ModalReplyableEvent.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/events/ModalReplyableEvent.java new file mode 100644 index 000000000..670eec8f1 --- /dev/null +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/events/ModalReplyableEvent.java @@ -0,0 +1,60 @@ +package com.github.kaktushose.jda.commands.dispatching.events; + +import com.github.kaktushose.jda.commands.annotations.interactions.Modal; +import com.github.kaktushose.jda.commands.dispatching.internal.Runtime; +import com.github.kaktushose.jda.commands.dispatching.events.interactions.CommandEvent; +import com.github.kaktushose.jda.commands.dispatching.events.interactions.ComponentEvent; +import com.github.kaktushose.jda.commands.reflect.InteractionRegistry; +import com.github.kaktushose.jda.commands.reflect.interactions.EphemeralInteractionDefinition; +import com.github.kaktushose.jda.commands.reflect.interactions.ModalDefinition; +import net.dv8tion.jda.api.events.interaction.GenericInteractionCreateEvent; +import net.dv8tion.jda.api.interactions.callbacks.IModalCallback; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/// Subtype of [ReplyableEvent] that also supports replying with a [Modal]. +/// +/// +/// @param the type of [GenericInteractionCreateEvent] this event represents +/// @see CommandEvent +/// @see ComponentEvent +/// @since 4.0.0 +public abstract sealed class ModalReplyableEvent + extends ReplyableEvent + permits CommandEvent, ComponentEvent { + + private static final Logger log = LoggerFactory.getLogger(ModalReplyableEvent.class); + + /// Constructs a new ModalReplyableEvent. + /// + /// @param event the subtype [T] of [GenericInteractionCreateEvent] + /// @param interactionRegistry the corresponding [InteractionRegistry] + /// @param runtime the [Runtime] this event lives in + /// @param definition the [EphemeralInteractionDefinition] this event belongs to + protected ModalReplyableEvent(@NotNull T event, + @NotNull InteractionRegistry interactionRegistry, + @NotNull Runtime runtime, + @NotNull EphemeralInteractionDefinition definition) { + super(event, interactionRegistry, runtime, definition); + } + + /// Acknowledgement of this event with a [Modal]. This will open a popup on the target user's Discord client. + /// + /// @param modal the method name of the [Modal] you want to reply with + /// @throws IllegalArgumentException if no [Modal] with the given name was found + public void replyModal(@NotNull String modal) { + if (event instanceof IModalCallback callback) { + var definitionId = String.valueOf((definition.getMethod().getDeclaringClass().getName() + modal).hashCode()); + var modalDefinition = interactionRegistry.find(ModalDefinition.class, false, it -> + it.getDefinitionId().equals(definitionId) + ); + log.debug("Replying to interaction \"{}\" with Modal: \"{}\". [Runtime={}]", definition.getDisplayName(), modalDefinition.getDisplayName(), runtimeId()); + callback.replyModal(modalDefinition.toModal(runtimeId())).queue(); + } else { + throw new IllegalStateException( + String.format("Cannot reply to '%s'! Please report this error to the jda-commands devs!", event.getClass().getName()) + ); + } + } +} diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/events/ReplyableEvent.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/events/ReplyableEvent.java new file mode 100644 index 000000000..79832ab65 --- /dev/null +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/events/ReplyableEvent.java @@ -0,0 +1,105 @@ +package com.github.kaktushose.jda.commands.dispatching.events; + +import com.github.kaktushose.jda.commands.dispatching.events.interactions.CommandEvent; +import com.github.kaktushose.jda.commands.dispatching.events.interactions.ComponentEvent; +import com.github.kaktushose.jda.commands.dispatching.internal.Runtime; +import com.github.kaktushose.jda.commands.dispatching.events.interactions.ModalEvent; +import com.github.kaktushose.jda.commands.dispatching.reply.ConfigurableReply; +import com.github.kaktushose.jda.commands.dispatching.reply.MessageReply; +import com.github.kaktushose.jda.commands.dispatching.reply.Reply; +import com.github.kaktushose.jda.commands.reflect.InteractionRegistry; +import com.github.kaktushose.jda.commands.reflect.interactions.EphemeralInteractionDefinition; +import com.github.kaktushose.jda.commands.reflect.interactions.ReplyConfig; +import net.dv8tion.jda.api.EmbedBuilder; +import net.dv8tion.jda.api.entities.Message; +import net.dv8tion.jda.api.events.interaction.GenericInteractionCreateEvent; +import net.dv8tion.jda.api.interactions.callbacks.IReplyCallback; +import net.dv8tion.jda.api.utils.messages.MessageCreateData; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/// Subtype of [Event] that supports replying to the [GenericInteractionCreateEvent] with text messages. +/// +/// You can either reply directly by using one of the `reply` methods, like [#reply(String)], or you can call +/// [#with()] to use a [ConfigurableReply] to append components or override reply settings from the +/// [`ReplyConfig`][com.github.kaktushose.jda.commands.annotations.interactions.ReplyConfig]. +/// +/// Example: +/// ``` +/// public void onInteraction(ReplayableEvent event) { +/// event.reply("Hello World"); +/// } +/// ``` +/// +/// @since 4.0.0 +/// @see ModalEvent +/// @see CommandEvent +/// @see ComponentEvent +/// @since 4.0.0 +public sealed abstract class ReplyableEvent extends Event implements Reply + permits ModalEvent, ModalReplyableEvent { + + private static final Logger log = LoggerFactory.getLogger(ReplyableEvent.class); + protected final EphemeralInteractionDefinition definition; + private final ReplyConfig replyConfig; + + /// Constructs a new ReplyableEvent. + /// + /// @param event the subtype [T] of [GenericInteractionCreateEvent] + /// @param interactionRegistry the corresponding [InteractionRegistry] + /// @param runtime the [Runtime] this event lives in + /// @param definition the [EphemeralInteractionDefinition] this event belongs to + protected ReplyableEvent(T event, + InteractionRegistry interactionRegistry, + Runtime runtime, + EphemeralInteractionDefinition definition) { + super(event, interactionRegistry, runtime); + this.replyConfig = definition.replyConfig(); + this.definition = definition; + } + + /// Removes all components from the original message. + /// + /// The original message is the message, from which this event (interaction) originates. For example if this event is a ButtonEvent, the original message will be the message to which the pressed button is attached to. + public void removeComponents() { + log.debug("Reply Debug: Removing components from original message"); + if (event instanceof IReplyCallback callback) { + if (!event.isAcknowledged()) { + callback.deferReply(replyConfig.ephemeral()).queue(); + } + callback.getHook().editOriginalComponents().queue(); + } + } + + /// Entry point for configuring a reply. + /// + /// Returns a new [ConfigurableReply] that can be used to append components or override reply settings. + /// + /// @return [ConfigurableReply] + /// @see [ConfigurableReply] + @NotNull + public ConfigurableReply with() { + return new ConfigurableReply(newReply(), interactionRegistry, runtimeId()); + } + + @NotNull + public Message reply(@NotNull String message) { + return newReply().reply(message); + } + + @NotNull + public Message reply(@NotNull MessageCreateData message) { + return newReply().reply(message); + } + + @NotNull + public Message reply(@NotNull EmbedBuilder builder) { + return newReply().reply(builder); + } + + private MessageReply newReply() { + log.debug("Reply Debug: [Runtime={}]", runtimeId()); + return new MessageReply(event, definition); + } +} diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/autocomplete/AutoCompleteEvent.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/events/interactions/AutoCompleteEvent.java similarity index 89% rename from src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/autocomplete/AutoCompleteEvent.java rename to src/main/java/com/github/kaktushose/jda/commands/dispatching/events/interactions/AutoCompleteEvent.java index 425738cdb..8484a9f37 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/autocomplete/AutoCompleteEvent.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/events/interactions/AutoCompleteEvent.java @@ -1,13 +1,14 @@ -package com.github.kaktushose.jda.commands.dispatching.interactions.autocomplete; +package com.github.kaktushose.jda.commands.dispatching.events.interactions; -import com.github.kaktushose.jda.commands.dispatching.interactions.Context; -import com.github.kaktushose.jda.commands.dispatching.interactions.GenericEvent; +import com.github.kaktushose.jda.commands.dispatching.internal.Runtime; +import com.github.kaktushose.jda.commands.dispatching.events.Event; import com.github.kaktushose.jda.commands.reflect.InteractionRegistry; import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent; import net.dv8tion.jda.api.interactions.commands.Command; import net.dv8tion.jda.api.interactions.commands.CommandAutoCompleteInteraction; import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.interactions.commands.build.OptionData; +import org.jetbrains.annotations.NotNull; import javax.annotation.Nonnull; import java.util.Arrays; @@ -15,19 +16,25 @@ import java.util.stream.Collectors; /** - * This class is a subclass of {@link GenericEvent}. + * This class is a subclass of {@link Event}. * It provides additional features for replying to a {@link CommandAutoCompleteInteractionEvent}. * - * @see GenericEvent + * @see Event * @since 4.0.0 */ -public class AutoCompleteEvent extends GenericEvent { +public final class AutoCompleteEvent extends Event { - private final CommandAutoCompleteInteractionEvent event; - - protected AutoCompleteEvent(Context context, InteractionRegistry interactionRegistry) { - super(context, interactionRegistry); - event = (CommandAutoCompleteInteractionEvent) context.getEvent(); + /** + * Constructs a new AutoCompleteEvent. + * + * @param event the {@link CommandAutoCompleteInteractionEvent} this event holds + * @param interactionRegistry the corresponding {@link InteractionRegistry} + * @param runtime the corresponding {@link Runtime} + */ + public AutoCompleteEvent(@NotNull CommandAutoCompleteInteractionEvent event, + @NotNull InteractionRegistry interactionRegistry, + @NotNull Runtime runtime) { + super(event, interactionRegistry, runtime); } /** @@ -45,7 +52,7 @@ protected AutoCompleteEvent(Context context, InteractionRegistry interactionRegi * * */ - public void replyChoices(@Nonnull Collection choices) { + public void replyChoices(@NotNull Collection choices) { event.replyChoices(choices).queue(); } @@ -63,7 +70,7 @@ public void replyChoices(@Nonnull Collection choices) { *
  • If the string value of any of the choices is empty or longer than {@value OptionData#MAX_CHOICE_VALUE_LENGTH}
  • * */ - public void replyChoices(@Nonnull Command.Choice... choices) { + public void replyChoices(@NotNull Command.Choice... choices) { replyChoices(Arrays.asList(choices)); } @@ -99,7 +106,7 @@ public void replyChoice(@Nonnull String name, @Nonnull String value) { *
  • If the value of is not between {@value OptionData#MIN_NEGATIVE_NUMBER} and {@value OptionData#MAX_POSITIVE_NUMBER}
  • * */ - public void replyChoice(@Nonnull String name, long value) { + public void replyChoice(@NotNull String name, long value) { replyChoices(new Command.Choice(name, value)); } @@ -117,7 +124,7 @@ public void replyChoice(@Nonnull String name, long value) { *
  • If the value of is not between {@value OptionData#MIN_NEGATIVE_NUMBER} and {@value OptionData#MAX_POSITIVE_NUMBER}
  • * */ - public void replyChoice(@Nonnull String name, double value) { + public void replyChoice(@NotNull String name, double value) { replyChoices(new Command.Choice(name, value)); } @@ -135,7 +142,7 @@ public void replyChoice(@Nonnull String name, double value) { *
  • If the string value of any of the choices is empty or longer than {@value OptionData#MAX_CHOICE_VALUE_LENGTH}
  • * */ - public void replyChoiceStrings(@Nonnull String... choices) { + public void replyChoiceStrings(@NotNull String... choices) { replyChoices(Arrays.stream(choices).map(it -> new Command.Choice(it, it)).collect(Collectors.toList())); } @@ -153,7 +160,7 @@ public void replyChoiceStrings(@Nonnull String... choices) { *
  • If the string value of any of the choices is empty or longer than {@value OptionData#MAX_CHOICE_VALUE_LENGTH}
  • * */ - public void replyChoiceStrings(@Nonnull Collection choices) { + public void replyChoiceStrings(@NotNull Collection choices) { replyChoices(choices.stream().map(it -> new Command.Choice(it, it)).collect(Collectors.toList())); } @@ -171,7 +178,7 @@ public void replyChoiceStrings(@Nonnull Collection choices) { *
  • If the numeric value of any of the choices is not between {@value OptionData#MIN_NEGATIVE_NUMBER} and {@value OptionData#MAX_POSITIVE_NUMBER}
  • * */ - public void replyChoiceLongs(@Nonnull long... choices) { + public void replyChoiceLongs(long... choices) { replyChoices(Arrays.stream(choices).mapToObj(it -> new Command.Choice(String.valueOf(it), it)).collect(Collectors.toList())); } @@ -189,7 +196,7 @@ public void replyChoiceLongs(@Nonnull long... choices) { *
  • If the numeric value of any of the choices is not between {@value OptionData#MIN_NEGATIVE_NUMBER} and {@value OptionData#MAX_POSITIVE_NUMBER}
  • * */ - public void replyChoiceLongs(@Nonnull Collection choices) { + public void replyChoiceLongs(@NotNull Collection choices) { replyChoices(choices.stream().map(it -> new Command.Choice(String.valueOf(it), it)).collect(Collectors.toList())); } @@ -208,7 +215,7 @@ public void replyChoiceLongs(@Nonnull Collection choices) { * */ - public void replyChoiceDoubles(@Nonnull double... choices) { + public void replyChoiceDoubles(double... choices) { replyChoices(Arrays.stream(choices).mapToObj(it -> new Command.Choice(String.valueOf(it), it)).collect(Collectors.toList())); } @@ -227,7 +234,7 @@ public void replyChoiceDoubles(@Nonnull double... choices) { * */ - public void replyChoiceDoubles(@Nonnull Collection choices) { + public void replyChoiceDoubles(@NotNull Collection choices) { replyChoices(choices.stream().map(it -> new Command.Choice(String.valueOf(it), it)).collect(Collectors.toList())); } @@ -236,7 +243,7 @@ public void replyChoiceDoubles(@Nonnull Collection choices) { * * @return The option name */ - @Nonnull + @NotNull public String getName() { return event.getFocusedOption().getName(); } @@ -249,7 +256,7 @@ public String getName() { * * @return The current auto-completable query value */ - @Nonnull + @NotNull public String getValue() { return event.getFocusedOption().getValue(); } @@ -259,7 +266,7 @@ public String getValue() { * * @return The option type expected from this auto-complete response */ - @Nonnull + @NotNull public OptionType getOptionType() { return event.getFocusedOption().getType(); } diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/events/interactions/CommandEvent.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/events/interactions/CommandEvent.java new file mode 100644 index 000000000..9bd940fcc --- /dev/null +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/events/interactions/CommandEvent.java @@ -0,0 +1,42 @@ +package com.github.kaktushose.jda.commands.dispatching.events.interactions; + +import com.github.kaktushose.jda.commands.dispatching.internal.Runtime; +import com.github.kaktushose.jda.commands.dispatching.events.Event; +import com.github.kaktushose.jda.commands.dispatching.events.ModalReplyableEvent; +import com.github.kaktushose.jda.commands.reflect.InteractionRegistry; +import com.github.kaktushose.jda.commands.reflect.interactions.EphemeralInteractionDefinition; +import net.dv8tion.jda.api.events.interaction.command.GenericCommandInteractionEvent; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; +import org.jetbrains.annotations.NotNull; + +/// This class is a subclass of [Event]. It provides additional features for replying to a [GenericCommandInteractionEvent]. +/// +/// @see Event +/// @see ModalReplyableEvent +/// @since 4.0.0 +public final class CommandEvent extends ModalReplyableEvent { + + /// Constructs a new CommandEvent. + /// + /// @param event the [GenericCommandInteractionEvent] this event holds + /// @param interactionRegistry the corresponding [InteractionRegistry] + /// @param runtime the corresponding [Runtime] + /// @param definition the corresponding [EphemeralInteractionDefinition] + public CommandEvent(@NotNull GenericCommandInteractionEvent event, + @NotNull InteractionRegistry interactionRegistry, + @NotNull Runtime runtime, + @NotNull EphemeralInteractionDefinition definition) { + super(event, interactionRegistry, runtime, definition); + } + + /// Returns the underlying [GenericCommandInteractionEvent] and casts it to the given type. + /// + /// @param type a subtype of [GenericCommandInteractionEvent], like [SlashCommandInteractionEvent] + /// @param a subtype of [GenericCommandInteractionEvent] + /// @return [T] + @NotNull + public T jdaEvent(@NotNull Class type) { + return type.cast(event); + } + +} diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/events/interactions/ComponentEvent.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/events/interactions/ComponentEvent.java new file mode 100644 index 000000000..067744cf4 --- /dev/null +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/events/interactions/ComponentEvent.java @@ -0,0 +1,43 @@ +package com.github.kaktushose.jda.commands.dispatching.events.interactions; + +import com.github.kaktushose.jda.commands.dispatching.internal.Runtime; +import com.github.kaktushose.jda.commands.dispatching.events.Event; +import com.github.kaktushose.jda.commands.dispatching.events.ModalReplyableEvent; +import com.github.kaktushose.jda.commands.reflect.InteractionRegistry; +import com.github.kaktushose.jda.commands.reflect.interactions.EphemeralInteractionDefinition; +import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent; +import net.dv8tion.jda.api.events.interaction.component.EntitySelectInteractionEvent; +import net.dv8tion.jda.api.events.interaction.component.GenericComponentInteractionCreateEvent; +import net.dv8tion.jda.api.events.interaction.component.StringSelectInteractionEvent; +import org.jetbrains.annotations.NotNull; + +/// This class is a subclass of [Event]. It provides additional features for replying to a [GenericComponentInteractionCreateEvent]. +/// +/// @see Event +/// @see ModalReplyableEvent +/// @since 4.0.0 +public final class ComponentEvent extends ModalReplyableEvent { + + /// Constructs a new CommandEvent. + /// + /// @param event the [GenericComponentInteractionCreateEvent] this event holds + /// @param interactionRegistry the corresponding [InteractionRegistry] + /// @param runtime the corresponding [Runtime] + /// @param definition the corresponding [EphemeralInteractionDefinition] + public ComponentEvent(@NotNull GenericComponentInteractionCreateEvent event, + @NotNull InteractionRegistry interactionRegistry, + @NotNull Runtime runtime, + @NotNull EphemeralInteractionDefinition definition) { + super(event, interactionRegistry, runtime, definition); + } + + /// Returns the underlying [GenericComponentInteractionCreateEvent] and casts it to the given type. + /// + /// @param type a subtype of [GenericComponentInteractionCreateEvent], namely [ButtonInteractionEvent], + /// [EntitySelectInteractionEvent] or [StringSelectInteractionEvent] + /// @param a subtype of [GenericComponentInteractionCreateEvent] + /// @return [T] + public T jdaEvent(Class type) { + return type.cast(event); + } +} diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/events/interactions/ModalEvent.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/events/interactions/ModalEvent.java new file mode 100644 index 000000000..c8f60a9a6 --- /dev/null +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/events/interactions/ModalEvent.java @@ -0,0 +1,32 @@ +package com.github.kaktushose.jda.commands.dispatching.events.interactions; + +import com.github.kaktushose.jda.commands.dispatching.internal.Runtime; +import com.github.kaktushose.jda.commands.dispatching.events.Event; +import com.github.kaktushose.jda.commands.dispatching.events.ReplyableEvent; +import com.github.kaktushose.jda.commands.reflect.InteractionRegistry; +import com.github.kaktushose.jda.commands.reflect.interactions.EphemeralInteractionDefinition; +import net.dv8tion.jda.api.events.interaction.ModalInteractionEvent; +import net.dv8tion.jda.api.events.interaction.command.GenericCommandInteractionEvent; +import org.jetbrains.annotations.NotNull; + +/// This class is a subclass of [Event]. It provides additional features for replying to a [ModalInteractionEvent]. +/// +/// @see Event +/// @see ReplyableEvent +/// @since 4.0.0 +public final class ModalEvent extends ReplyableEvent { + + /// Constructs a new CommandEvent. + /// + /// @param event the [GenericCommandInteractionEvent] this event holds + /// @param interactionRegistry the corresponding [InteractionRegistry] + /// @param runtime the corresponding [Runtime] + /// @param definition the corresponding [EphemeralInteractionDefinition] + public ModalEvent(@NotNull ModalInteractionEvent event, + @NotNull InteractionRegistry interactionRegistry, + @NotNull Runtime runtime, + @NotNull EphemeralInteractionDefinition definition) { + super(event, interactionRegistry, runtime, definition); + } + +} diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/events/interactions/package-info.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/events/interactions/package-info.java new file mode 100644 index 000000000..323ac6561 --- /dev/null +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/events/interactions/package-info.java @@ -0,0 +1,4 @@ +/// Event classes that represent an interaction execution. +/// +/// @since 4.0.0 +package com.github.kaktushose.jda.commands.dispatching.events.interactions; \ No newline at end of file diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/events/package-info.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/events/package-info.java new file mode 100644 index 000000000..ea31d952d --- /dev/null +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/events/package-info.java @@ -0,0 +1,2 @@ +/// Abstract event classes that build the foundation for specific event implementations. +package com.github.kaktushose.jda.commands.dispatching.events; \ No newline at end of file diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/handling/AutoCompleteHandler.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/handling/AutoCompleteHandler.java new file mode 100644 index 000000000..cc10b7d86 --- /dev/null +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/handling/AutoCompleteHandler.java @@ -0,0 +1,33 @@ +package com.github.kaktushose.jda.commands.dispatching.handling; + +import com.github.kaktushose.jda.commands.dispatching.internal.Runtime; +import com.github.kaktushose.jda.commands.dispatching.context.InvocationContext; +import com.github.kaktushose.jda.commands.dispatching.events.interactions.AutoCompleteEvent; +import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent; +import net.dv8tion.jda.api.interactions.commands.CommandAutoCompleteInteraction; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +@ApiStatus.Internal +public final class AutoCompleteHandler extends EventHandler { + + public AutoCompleteHandler(DispatchingContext dispatchingContext) { + super(dispatchingContext); + } + + @Override + protected InvocationContext prepare(@NotNull CommandAutoCompleteInteractionEvent event, @NotNull Runtime runtime) { + CommandAutoCompleteInteraction interaction = event.getInteraction(); + + return interactionRegistry.getAutoCompletes().stream() + .filter(it -> it.getCommandNames().stream().anyMatch(name -> interaction.getFullCommandName().startsWith(name))) + .findFirst() + .map(autoComplete -> new InvocationContext<>(event, runtime.keyValueStore(), autoComplete, List.of(new AutoCompleteEvent(event, interactionRegistry, runtime)))) + .orElseGet(() -> { + log.debug("No auto complete handler found for {}", interaction.getFullCommandName()); + return null; + }); + } +} diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/handling/ComponentHandler.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/handling/ComponentHandler.java new file mode 100644 index 000000000..1093eaad4 --- /dev/null +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/handling/ComponentHandler.java @@ -0,0 +1,52 @@ +package com.github.kaktushose.jda.commands.dispatching.handling; + +import com.github.kaktushose.jda.commands.dispatching.internal.Runtime; +import com.github.kaktushose.jda.commands.dispatching.context.InvocationContext; +import com.github.kaktushose.jda.commands.dispatching.events.interactions.ComponentEvent; +import com.github.kaktushose.jda.commands.reflect.interactions.CustomId; +import com.github.kaktushose.jda.commands.reflect.interactions.components.GenericComponentDefinition; +import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent; +import net.dv8tion.jda.api.events.interaction.component.EntitySelectInteractionEvent; +import net.dv8tion.jda.api.events.interaction.component.GenericComponentInteractionCreateEvent; +import net.dv8tion.jda.api.events.interaction.component.StringSelectInteractionEvent; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +@ApiStatus.Internal +public final class ComponentHandler extends EventHandler { + + public ComponentHandler(DispatchingContext dispatchingContext) { + super(dispatchingContext); + } + + @Override + protected InvocationContext prepare(@NotNull GenericComponentInteractionCreateEvent genericEvent, @NotNull Runtime runtime) { + // ignore non jda-commands events + if (CustomId.isInvalid(genericEvent.getComponentId())) { + return null; + } + + var component = interactionRegistry.find(GenericComponentDefinition.class, true, it -> + it.getDefinitionId().equals(CustomId.definitionId(genericEvent.getComponentId())) + ); + + List arguments = switch (genericEvent) { + case StringSelectInteractionEvent event -> new ArrayList<>(List.of(event.getValues())); + case EntitySelectInteractionEvent event -> new ArrayList<>(List.of(event.getMentions())); + case ButtonInteractionEvent _ -> new ArrayList<>(); + default -> + throw new IllegalStateException("Should not occur. Please report this error the the devs of jda-commands."); + }; + arguments.addFirst(new ComponentEvent(genericEvent, interactionRegistry, runtime, component)); + + return new InvocationContext<>( + genericEvent, + runtime.keyValueStore(), + component, + arguments + ); + } +} diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/handling/DispatchingContext.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/handling/DispatchingContext.java new file mode 100644 index 000000000..83f5f09f5 --- /dev/null +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/handling/DispatchingContext.java @@ -0,0 +1,17 @@ +package com.github.kaktushose.jda.commands.dispatching.handling; + +import com.github.kaktushose.jda.commands.dispatching.ExpirationStrategy; +import com.github.kaktushose.jda.commands.dispatching.adapter.TypeAdapterRegistry; +import com.github.kaktushose.jda.commands.dispatching.middleware.MiddlewareRegistry; +import com.github.kaktushose.jda.commands.reflect.ImplementationRegistry; +import com.github.kaktushose.jda.commands.reflect.InteractionRegistry; +import org.jetbrains.annotations.ApiStatus; + +/// A collection of classes relevant for [EventHandler]s. +@ApiStatus.Internal +public record DispatchingContext(MiddlewareRegistry middlewareRegistry, + ImplementationRegistry implementationRegistry, + InteractionRegistry interactionRegistry, + TypeAdapterRegistry adapterRegistry, + ExpirationStrategy expirationStrategy) { +} diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/handling/EventHandler.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/handling/EventHandler.java new file mode 100644 index 000000000..2919128e9 --- /dev/null +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/handling/EventHandler.java @@ -0,0 +1,106 @@ +package com.github.kaktushose.jda.commands.dispatching.handling; + +import com.github.kaktushose.jda.commands.dispatching.internal.Runtime; +import com.github.kaktushose.jda.commands.dispatching.adapter.TypeAdapterRegistry; +import com.github.kaktushose.jda.commands.dispatching.context.InvocationContext; +import com.github.kaktushose.jda.commands.dispatching.handling.command.ContextCommandHandler; +import com.github.kaktushose.jda.commands.dispatching.handling.command.SlashCommandHandler; +import com.github.kaktushose.jda.commands.dispatching.middleware.Middleware; +import com.github.kaktushose.jda.commands.dispatching.middleware.MiddlewareRegistry; +import com.github.kaktushose.jda.commands.dispatching.middleware.Priority; +import com.github.kaktushose.jda.commands.reflect.ImplementationRegistry; +import com.github.kaktushose.jda.commands.reflect.InteractionRegistry; +import com.github.kaktushose.jda.commands.reflect.interactions.GenericInteractionDefinition; +import net.dv8tion.jda.api.events.interaction.GenericInteractionCreateEvent; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.lang.reflect.InvocationTargetException; +import java.util.SequencedCollection; +import java.util.function.BiConsumer; + +/// Implementations of this class are handling specific [GenericInteractionCreateEvent]s. +/// +/// Each [EventHandler] is split into 3 steps: +/// 1. Preparation ([EventHandler#prepare(GenericInteractionCreateEvent, Runtime)]): +/// In this step the [InvocationContext] is created from the jda event and the involved [Runtime]. +/// +/// 2. Middleware execution: In this step all registered [Middleware]s +/// are executed ordered by their [Priority]. +/// +/// 3. Invocation ([EventHandler#invoke(InvocationContext, Runtime)]): +/// In this step the user implemented method is called with help of the right [GenericInteractionDefinition] +@ApiStatus.Internal +public abstract sealed class EventHandler + implements BiConsumer + permits AutoCompleteHandler, ComponentHandler, ModalHandler, ContextCommandHandler, SlashCommandHandler { + + public static final Logger log = LoggerFactory.getLogger(EventHandler.class); + + protected final DispatchingContext dispatchingContext; + protected final MiddlewareRegistry middlewareRegistry; + protected final ImplementationRegistry implementationRegistry; + protected final InteractionRegistry interactionRegistry; + protected final TypeAdapterRegistry adapterRegistry; + + public EventHandler(DispatchingContext dispatchingContext) { + this.dispatchingContext = dispatchingContext; + this.middlewareRegistry = dispatchingContext.middlewareRegistry(); + this.implementationRegistry = dispatchingContext.implementationRegistry(); + this.interactionRegistry = dispatchingContext.interactionRegistry(); + this.adapterRegistry = dispatchingContext.adapterRegistry(); + } + + @Nullable + protected abstract InvocationContext prepare(@NotNull T event, @NotNull Runtime runtime); + + @Override + public final void accept(T e, Runtime runtime) { + log.debug("Got event {}", e); + + InvocationContext context = prepare(e, runtime); + + if (context == null || Thread.interrupted()) { + log.debug("Interaction execution cancelled by preparation task"); + return; + } + + log.debug("Executing middlewares..."); + middlewareRegistry.forAllOrdered(middleware -> { + log.debug("Executing middleware {}", middleware.getClass().getSimpleName()); + middleware.accept(context); + }); + + if (Thread.interrupted()) { + log.debug("Interaction execution cancelled by middleware"); + return; + } + + invoke(context, runtime); + } + + private void invoke(@NotNull InvocationContext invocation, @NotNull Runtime runtime) { + SequencedCollection arguments = invocation.arguments(); + + var definition = invocation.definition(); + + log.info("Executing interaction \"{}\" for user \"{}\"", definition.getDisplayName(), invocation.event().getUser().getEffectiveName()); + try { + log.debug("Invoking method \"{}.{}\" with following arguments: {}", + definition.getMethod().getDeclaringClass().getName(), + definition.getMethod().getName(), + arguments + ); + Object instance = runtime.instance(definition); + definition.invoke(instance, invocation); + } catch (Exception exception) { + log.error("Interaction execution failed!", exception); + // this unwraps the underlying error in case of an exception inside the command class + Throwable throwable = exception instanceof InvocationTargetException ? exception.getCause() : exception; + invocation.cancel(implementationRegistry.getErrorMessageFactory().getCommandExecutionFailedMessage(invocation.event(), throwable)); + } + } +} diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/handling/ModalHandler.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/handling/ModalHandler.java new file mode 100644 index 000000000..b055c33f1 --- /dev/null +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/handling/ModalHandler.java @@ -0,0 +1,43 @@ +package com.github.kaktushose.jda.commands.dispatching.handling; + +import com.github.kaktushose.jda.commands.dispatching.internal.Runtime; +import com.github.kaktushose.jda.commands.dispatching.context.InvocationContext; +import com.github.kaktushose.jda.commands.dispatching.events.interactions.ModalEvent; +import com.github.kaktushose.jda.commands.reflect.interactions.CustomId; +import com.github.kaktushose.jda.commands.reflect.interactions.ModalDefinition; +import net.dv8tion.jda.api.events.interaction.ModalInteractionEvent; +import net.dv8tion.jda.api.interactions.modals.ModalMapping; +import org.jetbrains.annotations.NotNull; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +public final class ModalHandler extends EventHandler { + + public ModalHandler(DispatchingContext dispatchingContext) { + super(dispatchingContext); + } + + @Override + protected InvocationContext prepare(@NotNull ModalInteractionEvent event, @NotNull Runtime runtime) { + // ignore non jda-commands events + if (CustomId.isInvalid(event.getModalId())) { + return null; + } + + var modal = interactionRegistry.find(ModalDefinition.class, true, it -> + it.getDefinitionId().equals(CustomId.definitionId(event.getModalId())) + ); + + List arguments = event.getValues().stream().map(ModalMapping::getAsString).collect(Collectors.toList()); + arguments.addFirst(new ModalEvent(event, interactionRegistry, runtime, modal)); + + return new InvocationContext<>( + event, + runtime.keyValueStore(), + modal, + Collections.unmodifiableList(arguments) + ); + } +} diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/handling/command/ContextCommandHandler.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/handling/command/ContextCommandHandler.java new file mode 100644 index 000000000..5b782917c --- /dev/null +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/handling/command/ContextCommandHandler.java @@ -0,0 +1,32 @@ +package com.github.kaktushose.jda.commands.dispatching.handling.command; + +import com.github.kaktushose.jda.commands.dispatching.internal.Runtime; +import com.github.kaktushose.jda.commands.dispatching.context.InvocationContext; +import com.github.kaktushose.jda.commands.dispatching.events.interactions.CommandEvent; +import com.github.kaktushose.jda.commands.dispatching.handling.EventHandler; +import com.github.kaktushose.jda.commands.dispatching.handling.DispatchingContext; +import com.github.kaktushose.jda.commands.reflect.interactions.commands.ContextCommandDefinition; +import net.dv8tion.jda.api.events.interaction.command.GenericContextInteractionEvent; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +@ApiStatus.Internal +public final class ContextCommandHandler extends EventHandler> { + + public ContextCommandHandler(DispatchingContext dispatchingContext) { + super(dispatchingContext); + } + + @Override + protected InvocationContext> prepare(@NotNull GenericContextInteractionEvent event, @NotNull Runtime runtime) { + ContextCommandDefinition command = interactionRegistry.find(ContextCommandDefinition.class, true, it -> + it.getName().equals(event.getFullCommandName()) + ); + + return new InvocationContext<>(event, runtime.keyValueStore(), command, + List.of(new CommandEvent(event, interactionRegistry, runtime, command), event.getTarget()) + ); + } +} diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/handling/command/SlashCommandHandler.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/handling/command/SlashCommandHandler.java new file mode 100644 index 000000000..34f1efa65 --- /dev/null +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/handling/command/SlashCommandHandler.java @@ -0,0 +1,107 @@ +package com.github.kaktushose.jda.commands.dispatching.handling.command; + +import com.github.kaktushose.jda.commands.dispatching.internal.Runtime; +import com.github.kaktushose.jda.commands.dispatching.adapter.TypeAdapter; +import com.github.kaktushose.jda.commands.dispatching.adapter.TypeAdapterRegistry; +import com.github.kaktushose.jda.commands.dispatching.context.InvocationContext; +import com.github.kaktushose.jda.commands.dispatching.events.interactions.CommandEvent; +import com.github.kaktushose.jda.commands.dispatching.handling.EventHandler; +import com.github.kaktushose.jda.commands.dispatching.handling.DispatchingContext; +import com.github.kaktushose.jda.commands.dispatching.reply.MessageReply; +import com.github.kaktushose.jda.commands.embeds.error.ErrorMessageFactory; +import com.github.kaktushose.jda.commands.reflect.ParameterDefinition; +import com.github.kaktushose.jda.commands.reflect.interactions.commands.SlashCommandDefinition; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +@ApiStatus.Internal +public final class SlashCommandHandler extends EventHandler { + + public SlashCommandHandler(DispatchingContext dispatchingContext) { + super(dispatchingContext); + } + + @Override + protected InvocationContext prepare(@NotNull SlashCommandInteractionEvent event, @NotNull Runtime runtime) { + SlashCommandDefinition command = interactionRegistry.find(SlashCommandDefinition.class, true, it -> + it.getName().equals(event.getFullCommandName()) + ); + + return parseArguments(command, event, runtime) + .map(args -> new InvocationContext<>(event, runtime.keyValueStore(), command, args)) + .orElse(null); + + } + + private Optional> parseArguments(SlashCommandDefinition command, SlashCommandInteractionEvent event, Runtime runtime) { + var input = command.getActualParameters().stream() + .map(it -> event.getOption(it.name()).getAsString()) + .toArray(String[]::new); + + List arguments = new ArrayList<>(); + arguments.addFirst(new CommandEvent(event, interactionRegistry, runtime, command)); + + ErrorMessageFactory messageFactory = implementationRegistry.getErrorMessageFactory(); + + log.debug("Type adapting arguments..."); + for (int i = 0; i < command.getActualParameters().size(); i++) { + ParameterDefinition parameter = command.getActualParameters().get(i); + + // if parameter is array don't parse + if (String[].class.isAssignableFrom(parameter.type())) { + log.debug("First parameter is String array. Not adapting arguments"); + arguments.add(input); + break; + } + + String raw; + // current parameter index == total amount of input, check if it's optional else cancel context + if (i >= input.length) { + if (!parameter.isOptional()) { + throw new IllegalStateException( + "Command input doesn't match parameter length! Please report this error the the devs of jda-commands." + ); + } + + // if the default value is an empty String (thus not present) add a null value to the argument list + // else try to type adapt the default value + if (parameter.defaultValue() == null) { + arguments.add(TypeAdapterRegistry.DEFAULT_MAPPINGS.getOrDefault(parameter.type(), null)); + continue; + } else { + raw = parameter.defaultValue(); + } + } else { + raw = input[i]; + } + + log.debug("Trying to adapt input \"{}\" to type {}", raw, parameter.type().getName()); + + TypeAdapter adapter = adapterRegistry.get(parameter.type()).orElseThrow(() -> + new IllegalArgumentException( + "No type adapter implementation found for %s. Consider implementing one or change the required type" + .formatted(parameter.type()) + ) + ); + + Optional parsed = adapter.apply(raw, event); + if (parsed.isEmpty()) { + log.debug("Type adapting failed!"); + new MessageReply(event, command).reply( + messageFactory.getTypeAdaptingFailedMessage(event, command, Arrays.asList(input)) + ); + return Optional.empty(); + } + + arguments.add(parsed.get()); + log.debug("Added \"{}\" to the argument list", parsed.get()); + } + return Optional.of(arguments); + } +} diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/handling/command/package-info.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/handling/command/package-info.java new file mode 100644 index 000000000..1174dfb75 --- /dev/null +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/handling/command/package-info.java @@ -0,0 +1 @@ +package com.github.kaktushose.jda.commands.dispatching.handling.command; \ No newline at end of file diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/handling/package-info.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/handling/package-info.java new file mode 100644 index 000000000..b4f17e991 --- /dev/null +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/handling/package-info.java @@ -0,0 +1,4 @@ +@ApiStatus.Internal +package com.github.kaktushose.jda.commands.dispatching.handling; + +import org.jetbrains.annotations.ApiStatus; \ No newline at end of file diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/Context.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/Context.java deleted file mode 100644 index 1959eac86..000000000 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/Context.java +++ /dev/null @@ -1,189 +0,0 @@ -package com.github.kaktushose.jda.commands.dispatching.interactions; - -import com.github.kaktushose.jda.commands.dispatching.KeyValueStore; -import com.github.kaktushose.jda.commands.dispatching.RuntimeSupervisor.InteractionRuntime; -import com.github.kaktushose.jda.commands.reflect.ImplementationRegistry; -import com.github.kaktushose.jda.commands.reflect.InteractionRegistry; -import com.github.kaktushose.jda.commands.reflect.interactions.GenericInteractionDefinition; -import net.dv8tion.jda.api.entities.Message; -import net.dv8tion.jda.api.events.interaction.GenericInteractionCreateEvent; -import net.dv8tion.jda.api.utils.messages.MessageCreateData; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -/** - * This class models an interaction execution. It gets constructed at the beginning of dispatching and gets passed - * through the execution chain. - * - * @see GenericEvent - * @see com.github.kaktushose.jda.commands.dispatching.interactions.commands.SlashCommandContext SlashCommandContext - * @since 2.0.0 - */ -public class Context { - - protected final GenericInteractionCreateEvent event; - protected MessageCreateData errorMessage; - protected ImplementationRegistry registry; - protected boolean cancelled; - protected boolean ephemeral; - protected InteractionRuntime runtime; - protected GenericInteractionDefinition interactionDefinition; - protected final InteractionRegistry interactionRegistry; - protected final ImplementationRegistry implementationRegistry; - - /** - * Constructs a new GenericContext. - * - * @param event the corresponding {@link GenericInteractionCreateEvent} - */ - public Context(GenericInteractionCreateEvent event, InteractionRegistry interactionRegistry, ImplementationRegistry implementationRegistry) { - this.event = event; - this.interactionRegistry = interactionRegistry; - this.implementationRegistry = implementationRegistry; - } - - public GenericInteractionCreateEvent getEvent() { - return event; - } - - /** - * Gets the {@link Message} to send if an error occurred. - * - * @return {@link Message} to send - */ - @Nullable - public MessageCreateData getErrorMessage() { - return errorMessage; - } - - /** - * Set the {@link ImplementationRegistry} instance. - * - * @param registry the {@link ImplementationRegistry} instance - * @return the current CommandContext instance - */ - @NotNull - public Context setImplementationRegistry(@NotNull ImplementationRegistry registry) { - this.registry = registry; - return this; - } - - /** - * Whether the context should be cancelled. - * - * @return {@code true} if the context should be cancelled - */ - public boolean isCancelled() { - return cancelled; - } - - /** - * Sets this context as cancelled and uses the provided {@link MessageCreateData} as an error message. - * - * @param errorMessage the error message as {@link MessageCreateData} - * @return the current Context instance - */ - public Context setCancelled(MessageCreateData errorMessage) { - this.cancelled = true; - this.errorMessage = errorMessage; - return this; - } - - /** - * Undoes {@link #setCancelled(MessageCreateData)} and. - * - * @return the current Context instance - */ - public Context setUncancelled() { - this.cancelled = false; - return this; - } - - /** - * Whether this context should send ephemeral replies. - * - * @return {@code true} if this context should send ephemeral replies - */ - public boolean isEphemeral() { - return ephemeral; - } - - - /** - * Sets whether this context should send ephemeral replies. - * - * @param ephemeral {@code true} if this context should send ephemeral replies - * @return the current Context instance - */ - public Context setEphemeral(boolean ephemeral) { - this.ephemeral = ephemeral; - return this; - } - - /** - * Gets the {@link InteractionRuntime} used to execute this command event. - * - * @return the {@link InteractionRuntime} - */ - public InteractionRuntime getRuntime() { - return runtime; - } - - /** - * Sets the {@link InteractionRuntime} that will be used to execute this command event. - * - * @return the current CommandContext instance - */ - public Context setRuntime(InteractionRuntime runtime) { - this.runtime = runtime; - return this; - } - - /** - * Gets the {@link GenericInteractionDefinition} this context got created from. - * - * @return the {@link GenericInteractionDefinition} - */ - public GenericInteractionDefinition getInteractionDefinition() { - return interactionDefinition; - } - - /** - * Sets the {@link GenericInteractionDefinition} - * - * @param interactionDefinition the {@link GenericInteractionDefinition} of this context - * @return the current Context instance - */ - public Context setInteractionDefinition(GenericInteractionDefinition interactionDefinition) { - this.interactionDefinition = interactionDefinition; - return this; - } - - /** - * Gets the {@link KeyValueStore} that is bound to this runtime. - * - * @return the {@link KeyValueStore} bound to this runtime - */ - public KeyValueStore getKeyValueStore() { - return runtime.getKeyValueStore(); - } - - /** - * Sets the {@link KeyValueStore} that is bound to this runtime. - * - * @param keyValueStore the {@link KeyValueStore} bound to this runtime - * @return the current Context instance - */ - public Context setKeyValueStore(KeyValueStore keyValueStore) { - runtime.setKeyValueStore(keyValueStore); - return this; - } - - public ImplementationRegistry getImplementationRegistry() { - return implementationRegistry; - } - - public InteractionRegistry getInteractionRegistry() { - return interactionRegistry; - } -} diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/GenericDispatcher.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/GenericDispatcher.java deleted file mode 100644 index 26c05690d..000000000 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/GenericDispatcher.java +++ /dev/null @@ -1,73 +0,0 @@ -package com.github.kaktushose.jda.commands.dispatching.interactions; - -import com.github.kaktushose.jda.commands.dispatching.RuntimeSupervisor; -import com.github.kaktushose.jda.commands.dispatching.adapter.TypeAdapterRegistry; -import com.github.kaktushose.jda.commands.dispatching.middleware.Middleware; -import com.github.kaktushose.jda.commands.dispatching.middleware.MiddlewareRegistry; -import com.github.kaktushose.jda.commands.dispatching.middleware.Priority; -import com.github.kaktushose.jda.commands.reflect.ImplementationRegistry; -import com.github.kaktushose.jda.commands.reflect.InteractionRegistry; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Abstract base class for event dispatchers. - * - * @since 4.0.0 - */ -public abstract class GenericDispatcher { - - private final static Logger log = LoggerFactory.getLogger(GenericDispatcher.class); - - protected final MiddlewareRegistry middlewareRegistry; - protected final ImplementationRegistry implementationRegistry; - protected final InteractionRegistry interactionRegistry; - protected final TypeAdapterRegistry adapterRegistry; - protected final RuntimeSupervisor runtimeSupervisor; - - /** - * Constructs a new GenericDispatcher. - */ - public GenericDispatcher(MiddlewareRegistry middlewareRegistry, - ImplementationRegistry implementationRegistry, - InteractionRegistry interactionRegistry, - TypeAdapterRegistry adapterRegistry, - RuntimeSupervisor runtimeSupervisor) { - this.middlewareRegistry = middlewareRegistry; - this.implementationRegistry = implementationRegistry; - this.interactionRegistry = interactionRegistry; - this.adapterRegistry = adapterRegistry; - this.runtimeSupervisor = runtimeSupervisor; - } - - protected void executeMiddlewares(Context context) { - log.debug("Executing middlewares..."); - - for (Middleware middleware : middlewareRegistry.getMiddlewares(Priority.PERMISSIONS)) { - if (executeMiddleware(context, middleware)) return; - } - for (Middleware middleware : middlewareRegistry.getMiddlewares(Priority.HIGH)) { - if (executeMiddleware(context, middleware)) return; - } - for (Middleware middleware : middlewareRegistry.getMiddlewares(Priority.NORMAL)) { - if (executeMiddleware(context, middleware)) return; - } - for (Middleware middleware : middlewareRegistry.getMiddlewares(Priority.LOW)) { - if (executeMiddleware(context, middleware)) return; - } - } - - private boolean executeMiddleware(Context context, Middleware middleware) { - log.debug("Executing middleware {}", middleware.getClass().getSimpleName()); - middleware.accept(context); - return context.isCancelled(); - } - - /** - * Dispatches a {@link Context}. - * - * @param context the {@link Context} to dispatch. - */ - public abstract void onEvent(Context context); - -} diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/GenericEvent.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/GenericEvent.java deleted file mode 100644 index 40236932e..000000000 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/GenericEvent.java +++ /dev/null @@ -1,96 +0,0 @@ -package com.github.kaktushose.jda.commands.dispatching.interactions; - -import com.github.kaktushose.jda.commands.reflect.InteractionRegistry; -import com.github.kaktushose.jda.commands.reflect.interactions.components.ButtonDefinition; -import com.github.kaktushose.jda.commands.reflect.interactions.components.menus.GenericSelectMenuDefinition; -import net.dv8tion.jda.api.events.interaction.GenericInteractionCreateEvent; -import net.dv8tion.jda.api.interactions.components.buttons.Button; -import net.dv8tion.jda.api.interactions.components.selections.SelectMenu; - -/** - * Extension of JDAs {@link GenericInteractionCreateEvent} class. This is the base class for the different event classes. - * - * @see com.github.kaktushose.jda.commands.dispatching.interactions.commands.CommandEvent CommandEvent - * @see com.github.kaktushose.jda.commands.dispatching.interactions.components.ComponentEvent ComponentEvent - * @see com.github.kaktushose.jda.commands.dispatching.interactions.autocomplete.AutoCompleteEvent AutoCompleteEvent - * @see com.github.kaktushose.jda.commands.dispatching.interactions.modals.ModalEvent ModalEvent - * @since 4.0.0 - */ -public abstract class GenericEvent extends GenericInteractionCreateEvent { - - protected final Context context; - private final InteractionRegistry interactionRegistry; - - /** - * Constructs a new GenericEvent. - * - * @param context the underlying {@link Context} - */ - protected GenericEvent(Context context, InteractionRegistry interactionRegistry) { - super(context.getEvent().getJDA(), context.getEvent().getResponseNumber(), context.getEvent().getInteraction()); - this.context = context; - this.interactionRegistry = interactionRegistry; - } - - /** - * Get the {@link Context} object. - * - * @return the registered {@link Context} object - */ - public Context getContext() { - return context; - } - - /** - * Gets a JDA {@link Button} to use it for message builders based on the jda-commands id. The returned button will - * be linked to the runtime of this event. - * - *

    - * The id is made up of the simple class name and the method name. E.g. the id of a button defined by a - * {@code onButton(ComponentEvent event)} method inside an {@code ExampleButton} class would be - * {@code ExampleButton.onButton}. - *

    - * - * @param button the id of the button - * @return a JDA {@link Button} - */ - public Button getButton(String button) { - if (!button.matches("[a-zA-Z]+\\.[a-zA-Z]+")) { - throw new IllegalArgumentException("Unknown Button"); - } - - String sanitizedId = button.replaceAll("\\.", ""); - ButtonDefinition buttonDefinition = interactionRegistry.getButtons().stream() - .filter(it -> it.getDefinitionId().equals(sanitizedId)) - .findFirst().orElseThrow(() -> new IllegalArgumentException("Unknown Button")); - - return buttonDefinition.toButton().withId(buttonDefinition.createCustomId(context.getRuntime().getRuntimeId())); - } - - /** - * Gets a JDA {@link SelectMenu} to use it for message builders based on the jda-commands id. The returned - * SelectMenu will be linked to the runtime of this event. - * - *

    - * The id is made up of the simple class name and the method name. E.g. the id of a select menu defined by a - * {@code onSelectMenu(ComponentEvent event)} method inside an {@code ExampleMenu} class would be - * {@code ExampleMenu.onSelectMenu}. - *

    - * - * @param menu the id of the selectMenu - * @return a JDA {@link SelectMenu} - */ - @SuppressWarnings("unchecked") - public T getSelectMenu(String menu) { - if (!menu.matches("[a-zA-Z]+\\.[a-zA-Z]+")) { - throw new IllegalArgumentException("Unknown Select Menu"); - } - - String sanitizedId = menu.replaceAll("\\.", ""); - GenericSelectMenuDefinition selectMenuDefinition = interactionRegistry.getSelectMenus().stream() - .filter(it -> it.getDefinitionId().equals(sanitizedId)) - .findFirst().orElseThrow(() -> new IllegalArgumentException("Unknown Select Menu")); - - return (T) selectMenuDefinition.toSelectMenu(context.getRuntime().getRuntimeId(), true); - } -} diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/autocomplete/AutoCompleteDispatcher.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/autocomplete/AutoCompleteDispatcher.java deleted file mode 100644 index 34ef20298..000000000 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/autocomplete/AutoCompleteDispatcher.java +++ /dev/null @@ -1,81 +0,0 @@ -package com.github.kaktushose.jda.commands.dispatching.interactions.autocomplete; - -import com.github.kaktushose.jda.commands.dispatching.RuntimeSupervisor; -import com.github.kaktushose.jda.commands.dispatching.adapter.TypeAdapterRegistry; -import com.github.kaktushose.jda.commands.dispatching.interactions.Context; -import com.github.kaktushose.jda.commands.dispatching.interactions.GenericDispatcher; -import com.github.kaktushose.jda.commands.dispatching.middleware.MiddlewareRegistry; -import com.github.kaktushose.jda.commands.dispatching.reply.ReplyContext; -import com.github.kaktushose.jda.commands.reflect.ImplementationRegistry; -import com.github.kaktushose.jda.commands.reflect.InteractionRegistry; -import com.github.kaktushose.jda.commands.reflect.interactions.AutoCompleteDefinition; -import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.Optional; - -/** - * Dispatches auto complete by taking a {@link Context} and passing it through the execution chain. - * - * @since 4.0.0 - */ -public class AutoCompleteDispatcher extends GenericDispatcher { - - private static final Logger log = LoggerFactory.getLogger(AutoCompleteDispatcher.class); - - /** - * Constructs a new AutoCompleteDispatcher. - * - * @param middlewareRegistry - * @param implementationRegistry - * @param interactionRegistry - * @param adapterRegistry - * @param runtimeSupervisor - */ - public AutoCompleteDispatcher(MiddlewareRegistry middlewareRegistry, ImplementationRegistry implementationRegistry, InteractionRegistry interactionRegistry, TypeAdapterRegistry adapterRegistry, RuntimeSupervisor runtimeSupervisor) { - super(middlewareRegistry, implementationRegistry, interactionRegistry, adapterRegistry, runtimeSupervisor); - } - - @Override - public void onEvent(Context context) { - CommandAutoCompleteInteractionEvent event = (CommandAutoCompleteInteractionEvent) context.getEvent(); - Optional optionalAutoComplete = interactionRegistry.getAutoCompletes().stream() - .filter(it -> it.getCommandNames().stream().anyMatch(name -> event.getFullCommandName().startsWith(name))) - .findFirst(); - - if (optionalAutoComplete.isEmpty()) { - log.debug("No auto complete handler found for {}", event.getFullCommandName()); - return; - } - - AutoCompleteDefinition autoComplete = optionalAutoComplete.get(); - context.setInteractionDefinition(autoComplete); - - executeMiddlewares(context); - if (checkCancelled(context)) { - log.debug("Interaction execution cancelled by middleware"); - return; - } - - log.debug("Input matches auto complete: {}", autoComplete.getDefinitionId()); - log.info("Executing auto complete {} for user {}", autoComplete.getMethod().getName(), event.getMember()); - try { - autoComplete.getMethod().invoke(runtimeSupervisor.newRuntime(autoComplete).getInstance(), new AutoCompleteEvent(context, interactionRegistry)); - } catch (Exception exception) { - throw new IllegalStateException("Auto complete execution failed!", exception); - } - } - - @SuppressWarnings("DataFlowIssue") - private boolean checkCancelled(Context context) { - if (context.isCancelled()) { - ReplyContext replyContext = new ReplyContext(context); - replyContext.getBuilder().applyData(context.getErrorMessage()); - replyContext.queue(); - return true; - } - return false; - } - -} diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/commands/CommandDispatcher.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/commands/CommandDispatcher.java deleted file mode 100644 index 8df5ef346..000000000 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/commands/CommandDispatcher.java +++ /dev/null @@ -1,130 +0,0 @@ -package com.github.kaktushose.jda.commands.dispatching.interactions.commands; - -import com.github.kaktushose.jda.commands.dispatching.RuntimeSupervisor; -import com.github.kaktushose.jda.commands.dispatching.adapter.TypeAdapterRegistry; -import com.github.kaktushose.jda.commands.dispatching.interactions.Context; -import com.github.kaktushose.jda.commands.dispatching.interactions.GenericDispatcher; -import com.github.kaktushose.jda.commands.dispatching.middleware.MiddlewareRegistry; -import com.github.kaktushose.jda.commands.dispatching.reply.ReplyContext; -import com.github.kaktushose.jda.commands.embeds.ErrorMessageFactory; -import com.github.kaktushose.jda.commands.reflect.ImplementationRegistry; -import com.github.kaktushose.jda.commands.reflect.InteractionRegistry; -import com.github.kaktushose.jda.commands.reflect.interactions.commands.GenericCommandDefinition; -import com.github.kaktushose.jda.commands.reflect.interactions.commands.SlashCommandDefinition; -import net.dv8tion.jda.api.events.interaction.command.GenericCommandInteractionEvent; -import net.dv8tion.jda.api.events.interaction.command.GenericContextInteractionEvent; -import net.dv8tion.jda.api.interactions.commands.Command; -import net.dv8tion.jda.api.interactions.commands.OptionMapping; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.lang.reflect.InvocationTargetException; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -/** - * Dispatches command events. - * - * @since 4.0.0 - */ -public class CommandDispatcher extends GenericDispatcher { - - private static final Logger log = LoggerFactory.getLogger(CommandDispatcher.class); - - /** - * Constructs a new CommandDispatcher. - * - * @param middlewareRegistry - * @param implementationRegistry - * @param interactionRegistry - * @param adapterRegistry - * @param runtimeSupervisor - */ - public CommandDispatcher(MiddlewareRegistry middlewareRegistry, ImplementationRegistry implementationRegistry, InteractionRegistry interactionRegistry, TypeAdapterRegistry adapterRegistry, RuntimeSupervisor runtimeSupervisor) { - super(middlewareRegistry, implementationRegistry, interactionRegistry, adapterRegistry, runtimeSupervisor); - } - - @Override - public void onEvent(Context context) { - GenericCommandInteractionEvent event = (GenericCommandInteractionEvent) context.getEvent(); - ErrorMessageFactory messageFactory = implementationRegistry.getErrorMessageFactory(); - - Optional optional = interactionRegistry.getCommands().stream() - .filter(it -> it.getName().equals(event.getFullCommandName())) - .findFirst(); - - if (optional.isEmpty()) { - IllegalStateException exception = new IllegalStateException("No command found! Please report this error the the devs of jda-commands."); - context.setCancelled(messageFactory.getCommandExecutionFailedMessage(context, exception)); - checkCancelled(context); - throw exception; - } - - GenericCommandDefinition command = optional.get(); - context.setInteractionDefinition(command).setEphemeral(command.isEphemeral()); - log.debug("Input matches command: {}", command.getDefinitionId()); - - List arguments; - if (command.getCommandType() == Command.Type.SLASH) { - SlashCommandDefinition slashCommand = (SlashCommandDefinition) command; - SlashCommandContext slashContext = (SlashCommandContext) context; - slashContext.setCommand(slashCommand); - - Map options = slashContext.getOptionsAsMap(); - List parameters = new ArrayList<>(); - slashCommand.getActualParameters().forEach(param -> { - if (!options.containsKey(param.name())) { - return; - } - parameters.add(options.get(param.name()).getAsString()); - }); - slashContext.setInput(parameters.toArray(new String[]{})); - - adapterRegistry.adapt(slashContext); - if (checkCancelled(slashContext)) { - return; - } - - arguments = slashContext.getArguments(); - arguments.addFirst(new CommandEvent(context, interactionRegistry)); - } else { - arguments = List.of( - new CommandEvent(context, interactionRegistry), - ((GenericContextInteractionEvent) event).getTarget() - ); - } - - executeMiddlewares(context); - if (checkCancelled(context)) { - log.debug("Interaction execution cancelled by middleware"); - return; - } - - log.info("Executing command {} for user {}", command.getMethod().getName(), context.getEvent().getMember()); - try { - RuntimeSupervisor.InteractionRuntime runtime = runtimeSupervisor.newRuntime(event, command); - context.setRuntime(runtime); - log.debug("Invoking method with following arguments: {}", arguments); - command.getMethod().invoke(runtime.getInstance(), arguments.toArray()); - } catch (Exception exception) { - log.error("Command execution failed!", exception); - // this unwraps the underlying error in case of an exception inside the command class - Throwable throwable = exception instanceof InvocationTargetException ? exception.getCause() : exception; - context.setCancelled(messageFactory.getCommandExecutionFailedMessage(context, throwable)); - checkCancelled(context); - } - } - - @SuppressWarnings("DataFlowIssue") - private boolean checkCancelled(Context context) { - if (context.isCancelled()) { - ReplyContext replyContext = new ReplyContext(context); - replyContext.getBuilder().applyData(context.getErrorMessage()); - replyContext.queue(); - return true; - } - return false; - } -} diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/commands/CommandEvent.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/commands/CommandEvent.java deleted file mode 100644 index 80537979a..000000000 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/commands/CommandEvent.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.github.kaktushose.jda.commands.dispatching.interactions.commands; - -import com.github.kaktushose.jda.commands.dispatching.interactions.Context; -import com.github.kaktushose.jda.commands.dispatching.interactions.GenericEvent; -import com.github.kaktushose.jda.commands.dispatching.reply.ModalReplyable; -import com.github.kaktushose.jda.commands.dispatching.reply.ReplyContext; -import com.github.kaktushose.jda.commands.reflect.InteractionRegistry; -import org.jetbrains.annotations.NotNull; - -/** - * This class is a subclass of {@link GenericEvent}. - * It provides additional features for replying to a - * {@link net.dv8tion.jda.api.events.interaction.command.GenericCommandInteractionEvent GenericCommandInteractionEvent}. - * - * @see GenericEvent - * @since 4.0.0 - */ -public class CommandEvent extends GenericEvent implements ModalReplyable { - - private final ReplyContext replyContext; - - /** - * Constructs a new CommandEvent. - * - * @param context the underlying {@link Context} - */ - public CommandEvent(@NotNull Context context, InteractionRegistry interactionRegistry) { - super(context, interactionRegistry); - replyContext = new ReplyContext(context); - } - - @Override - public @NotNull ReplyContext getReplyContext() { - return replyContext; - } - - @Override - public void reply() { - replyContext.queue(); - context.getRuntime().setLatestReply(replyContext.toMessageCreateData()); - } -} diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/commands/SlashCommandContext.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/commands/SlashCommandContext.java deleted file mode 100644 index 68bfa9c87..000000000 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/commands/SlashCommandContext.java +++ /dev/null @@ -1,146 +0,0 @@ -package com.github.kaktushose.jda.commands.dispatching.interactions.commands; - -import com.github.kaktushose.jda.commands.dispatching.interactions.Context; -import com.github.kaktushose.jda.commands.reflect.ImplementationRegistry; -import com.github.kaktushose.jda.commands.reflect.InteractionRegistry; -import com.github.kaktushose.jda.commands.reflect.interactions.commands.SlashCommandDefinition; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; -import net.dv8tion.jda.api.interactions.commands.OptionMapping; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * Implementation of {@link Context} for {@link SlashCommandInteractionEvent}. - * - * @since 4.0.0 - */ -public class SlashCommandContext extends Context { - - private String[] input; - private List options; - private SlashCommandDefinition command; - private List arguments; - - /** - * Constructs a new CommandContext. - * - * @param event the corresponding {@link SlashCommandInteractionEvent} - */ - public SlashCommandContext(SlashCommandInteractionEvent event, InteractionRegistry interactionRegistry, ImplementationRegistry implementationRegistry) { - super(event, interactionRegistry, implementationRegistry); - setOptions(event.getOptions()); - } - - @Override - public SlashCommandInteractionEvent getEvent() { - return (SlashCommandInteractionEvent) super.getEvent(); - } - - /** - * Gets the raw user input. - * - * @return the raw user input - * @see #getOptions() - */ - @NotNull - public String[] getInput() { - return input; - } - - /** - * Set the user input. - * - * @param input the user input - * @return the current CommandContext instance - */ - @NotNull - public SlashCommandContext setInput(@NotNull String[] input) { - this.input = input; - return this; - } - - /** - * Gets the {@link OptionMapping OptionMappings}. - * - * @return the {@link OptionMapping OptionMappings} - * @see #getInput() - */ - @NotNull - public List getOptions() { - return options; - } - - /** - * Set the {@link OptionMapping OptionMappings}. - * - * @param options the {@link OptionMapping OptionMappings} - * @return the current CommandContext instance - */ - @NotNull - public SlashCommandContext setOptions(@NotNull List options) { - this.options = options; - return this; - } - - /** - * Gets the {@link OptionMapping OptionMappings} inserted in a {@code Map}. - * - * @return the {@link OptionMapping OptionMappings} - * @see #getInput() - */ - @NotNull - public Map getOptionsAsMap() { - Map result = new HashMap<>(); - options.forEach(option -> result.put(option.getName(), option)); - return result; - } - - /** - * Gets the {@link SlashCommandDefinition}. This will return null until the command got routed. - * - * @return the {@link SlashCommandDefinition} - */ - @Nullable - public SlashCommandDefinition getCommand() { - return command; - } - - /** - * Set the {@link SlashCommandDefinition}. - * - * @param command the {@link SlashCommandDefinition} - * @return the current CommandContext instance - */ - @NotNull - public SlashCommandContext setCommand(@Nullable SlashCommandDefinition command) { - this.command = command; - setInteractionDefinition(command); - return this; - } - - /** - * Gets the parsed arguments. - * - * @return the parsed arguments - */ - @NotNull - public List getArguments() { - return arguments; - } - - /** - * Set the arguments. - * - * @param arguments the parsed arguments - * @return the current CommandContext instance - */ - @NotNull - public SlashCommandContext setArguments(@NotNull List arguments) { - this.arguments = arguments; - return this; - } -} diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/components/ComponentDispatcher.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/components/ComponentDispatcher.java deleted file mode 100644 index c6f14199f..000000000 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/components/ComponentDispatcher.java +++ /dev/null @@ -1,128 +0,0 @@ -package com.github.kaktushose.jda.commands.dispatching.interactions.components; - -import com.github.kaktushose.jda.commands.dispatching.RuntimeSupervisor; -import com.github.kaktushose.jda.commands.dispatching.adapter.TypeAdapterRegistry; -import com.github.kaktushose.jda.commands.dispatching.interactions.Context; -import com.github.kaktushose.jda.commands.dispatching.interactions.GenericDispatcher; -import com.github.kaktushose.jda.commands.dispatching.middleware.MiddlewareRegistry; -import com.github.kaktushose.jda.commands.dispatching.reply.ReplyContext; -import com.github.kaktushose.jda.commands.embeds.ErrorMessageFactory; -import com.github.kaktushose.jda.commands.reflect.ImplementationRegistry; -import com.github.kaktushose.jda.commands.reflect.InteractionRegistry; -import com.github.kaktushose.jda.commands.reflect.interactions.CustomId; -import com.github.kaktushose.jda.commands.reflect.interactions.EphemeralInteractionDefinition; -import com.github.kaktushose.jda.commands.reflect.interactions.components.ButtonDefinition; -import com.github.kaktushose.jda.commands.reflect.interactions.components.menus.EntitySelectMenuDefinition; -import com.github.kaktushose.jda.commands.reflect.interactions.components.menus.StringSelectMenuDefinition; -import net.dv8tion.jda.api.events.interaction.component.EntitySelectInteractionEvent; -import net.dv8tion.jda.api.events.interaction.component.GenericComponentInteractionCreateEvent; -import net.dv8tion.jda.api.events.interaction.component.StringSelectInteractionEvent; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.lang.reflect.InvocationTargetException; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; - -/** - * Dispatches component events. - * - * @since 4.0.0 - */ -public class ComponentDispatcher extends GenericDispatcher { - - private static final Logger log = LoggerFactory.getLogger(ComponentDispatcher.class); - - /** - * Constructs a new ComponentDispatcher. - * - * @param middlewareRegistry - * @param implementationRegistry - * @param interactionRegistry - * @param adapterRegistry - * @param runtimeSupervisor - */ - public ComponentDispatcher(MiddlewareRegistry middlewareRegistry, ImplementationRegistry implementationRegistry, InteractionRegistry interactionRegistry, TypeAdapterRegistry adapterRegistry, RuntimeSupervisor runtimeSupervisor) { - super(middlewareRegistry, implementationRegistry, interactionRegistry, adapterRegistry, runtimeSupervisor); - } - - @Override - public void onEvent(Context context) { - GenericComponentInteractionCreateEvent event = (GenericComponentInteractionCreateEvent) context.getEvent(); - if (!event.getComponentId().matches(CustomId.CUSTOM_ID_REGEX)) { - log.debug("Ignoring non jda-commands event {}", event.getComponentId()); - return; - } - - ErrorMessageFactory messageFactory = implementationRegistry.getErrorMessageFactory(); - - Optional optionalRuntime = runtimeSupervisor.getRuntime(event); - if (optionalRuntime.isEmpty()) { - event.deferEdit().setComponents().queue(); - event.getHook().sendMessage(messageFactory.getUnknownInteractionMessage(context)).setEphemeral(true).queue(); - return; - } - RuntimeSupervisor.InteractionRuntime runtime = optionalRuntime.get(); - log.debug("Found corresponding runtime with id \"{}\"", runtime.getRuntimeId()); - - String componentId = event.getComponentId().split("\\.")[1]; - - List components = new ArrayList<>(); - components.addAll(interactionRegistry.getButtons()); - components.addAll(interactionRegistry.getSelectMenus()); - - Optional optionalComponent = components.stream().filter(it -> it.getDefinitionId().equals(componentId)).findFirst(); - if (optionalComponent.isEmpty()) { - IllegalStateException exception = new IllegalStateException( - "No component found! Please report this error the the devs of jda-commands." - ); - context.setCancelled(messageFactory.getCommandExecutionFailedMessage(context, exception)); - checkCancelled(context); - throw exception; - } - - EphemeralInteractionDefinition component = optionalComponent.get(); - context.setInteractionDefinition(component).setEphemeral(component.isEphemeral()); - - executeMiddlewares(context); - if (checkCancelled(context)) { - log.debug("Interaction execution cancelled by middleware"); - return; - } - - log.debug("Input matches component: {}", component.getDefinitionId()); - log.info("Executing component {} for user {}", component.getMethod().getName(), event.getMember()); - context.setRuntime(runtime); - try { - Class clazz = component.getClass(); - if (EntitySelectMenuDefinition.class.isAssignableFrom(clazz)) { - component.getMethod().invoke(runtime.getInstance(), new ComponentEvent(context, interactionRegistry), ((EntitySelectInteractionEvent) event).getMentions()); - } else if (StringSelectMenuDefinition.class.isAssignableFrom(clazz)) { - component.getMethod().invoke(runtime.getInstance(), new ComponentEvent(context, interactionRegistry), ((StringSelectInteractionEvent) event).getValues()); - } else if (ButtonDefinition.class.isAssignableFrom(clazz)) { - component.getMethod().invoke(runtime.getInstance(), new ComponentEvent(context, interactionRegistry)); - } else { - throw new IllegalStateException("Unknown component type! Please report this error the the devs of jda-commands."); - } - } catch (Exception exception) { - log.error("Select menu execution failed!", exception); - // this unwraps the underlying error in case of an exception inside the command class - Throwable throwable = exception instanceof InvocationTargetException ? exception.getCause() : exception; - - context.setCancelled(messageFactory.getCommandExecutionFailedMessage(context, throwable)); - checkCancelled(context); - } - } - - @SuppressWarnings("DataFlowIssue") - private boolean checkCancelled(Context context) { - if (context.isCancelled()) { - ReplyContext replyContext = new ReplyContext(context); - replyContext.getBuilder().applyData(context.getErrorMessage()); - replyContext.queue(); - return true; - } - return false; - } -} diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/components/ComponentEvent.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/components/ComponentEvent.java deleted file mode 100644 index 3b742398e..000000000 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/components/ComponentEvent.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.github.kaktushose.jda.commands.dispatching.interactions.components; - -import com.github.kaktushose.jda.commands.dispatching.interactions.Context; -import com.github.kaktushose.jda.commands.dispatching.interactions.GenericEvent; -import com.github.kaktushose.jda.commands.dispatching.reply.ModalReplyable; -import com.github.kaktushose.jda.commands.dispatching.reply.ReplyContext; -import com.github.kaktushose.jda.commands.reflect.InteractionRegistry; -import net.dv8tion.jda.api.utils.messages.MessageCreateData; -import org.jetbrains.annotations.NotNull; - -import java.util.Optional; - -/** - * This class is a subclass of {@link GenericEvent}. - * It provides additional features for replying to a - * {@link net.dv8tion.jda.api.events.interaction.component.GenericComponentInteractionCreateEvent GenericComponentInteractionCreateEvent}. - * - * @see GenericEvent - * @since 4.0.0 - */ -public class ComponentEvent extends GenericEvent implements ModalReplyable { - - private final ReplyContext replyContext; - - /** - * Constructs a ComponentEvent. - * - * @param context the underlying {@link Context} - */ - public ComponentEvent(@NotNull Context context, InteractionRegistry interactionRegistry) { - super(context, interactionRegistry); - replyContext = new ReplyContext(context); - } - - @NotNull - public ReplyContext getReplyContext() { - return replyContext; - } - - @Override - public void reply() { - Optional optional = context.getRuntime().getLatestReply(); - if (optional.isPresent()) { - MessageCreateData cached = optional.get(); - if (replyContext.isKeepComponents() && replyContext.getBuilder().getComponents().isEmpty()) { - replyContext.getBuilder().setComponents(cached.getComponents()); - } - - } - replyContext.queue(); - context.getRuntime().setLatestReply(replyContext.toMessageCreateData()); - } -} diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/modals/ModalDispatcher.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/modals/ModalDispatcher.java deleted file mode 100644 index 533159f74..000000000 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/modals/ModalDispatcher.java +++ /dev/null @@ -1,115 +0,0 @@ -package com.github.kaktushose.jda.commands.dispatching.interactions.modals; - -import com.github.kaktushose.jda.commands.dispatching.RuntimeSupervisor; -import com.github.kaktushose.jda.commands.dispatching.adapter.TypeAdapterRegistry; -import com.github.kaktushose.jda.commands.dispatching.interactions.Context; -import com.github.kaktushose.jda.commands.dispatching.interactions.GenericDispatcher; -import com.github.kaktushose.jda.commands.dispatching.middleware.MiddlewareRegistry; -import com.github.kaktushose.jda.commands.dispatching.reply.ReplyContext; -import com.github.kaktushose.jda.commands.embeds.ErrorMessageFactory; -import com.github.kaktushose.jda.commands.reflect.ImplementationRegistry; -import com.github.kaktushose.jda.commands.reflect.InteractionRegistry; -import com.github.kaktushose.jda.commands.reflect.interactions.CustomId; -import com.github.kaktushose.jda.commands.reflect.interactions.ModalDefinition; -import net.dv8tion.jda.api.events.interaction.ModalInteractionEvent; -import net.dv8tion.jda.api.interactions.modals.ModalMapping; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.lang.reflect.InvocationTargetException; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; - -/** - * Dispatches {@link ModalInteractionEvent ModalInteractionEvents}. - * - * @since 4.0.0 - */ -public class ModalDispatcher extends GenericDispatcher { - - private static final Logger log = LoggerFactory.getLogger(ModalDispatcher.class); - - /** - * Constructs a new ModalDispatcher. - * - * @param middlewareRegistry - * @param implementationRegistry - * @param interactionRegistry - * @param adapterRegistry - * @param runtimeSupervisor - */ - public ModalDispatcher(MiddlewareRegistry middlewareRegistry, ImplementationRegistry implementationRegistry, InteractionRegistry interactionRegistry, TypeAdapterRegistry adapterRegistry, RuntimeSupervisor runtimeSupervisor) { - super(middlewareRegistry, implementationRegistry, interactionRegistry, adapterRegistry, runtimeSupervisor); - } - - @Override - public void onEvent(Context context) { - ModalInteractionEvent event = (ModalInteractionEvent) context.getEvent(); - if (!event.getModalId().matches(CustomId.CUSTOM_ID_REGEX)) { - log.debug("Ignoring non jda-commands event {}", event.getModalId()); - return; - } - - ErrorMessageFactory messageFactory = implementationRegistry.getErrorMessageFactory(); - - Optional optionalRuntime = runtimeSupervisor.getRuntime(event); - if (optionalRuntime.isEmpty()) { - event.reply(messageFactory.getUnknownInteractionMessage(context)).setEphemeral(true).queue(); - return; - } - RuntimeSupervisor.InteractionRuntime runtime = optionalRuntime.get(); - log.debug("Found corresponding runtime with id \"{}\"", runtime.getRuntimeId()); - - String modalId = event.getModalId().split("\\.")[1]; - Optional optionalModal = interactionRegistry.getModals().stream() - .filter(it -> it.getDefinitionId().equals(modalId)) - .findFirst(); - - if (optionalModal.isEmpty()) { - IllegalStateException exception = new IllegalStateException( - "No Modal found! Please report this error the the devs of jda-commands." - ); - context.setCancelled(messageFactory.getCommandExecutionFailedMessage(context, exception)); - checkCancelled(context); - throw exception; - } - - ModalDefinition modal = optionalModal.get(); - context.setInteractionDefinition(modal).setEphemeral(modal.isEphemeral()); - - executeMiddlewares(context); - if (checkCancelled(context)) { - log.debug("Interaction execution cancelled by middleware"); - return; - } - - log.debug("Input matches Modal: {}", modal.getDefinitionId()); - log.info("Executing Modal {} for user {}", modal.getMethod().getName(), event.getMember()); - try { - context.setRuntime(runtime); - List arguments = new ArrayList<>(); - arguments.add(new ModalEvent(context, interactionRegistry)); - arguments.addAll(event.getValues().stream().map(ModalMapping::getAsString).collect(Collectors.toList())); - modal.getMethod().invoke(runtime.getInstance(), arguments.toArray()); - } catch (Exception exception) { - log.error("Modal execution failed!", exception); - // this unwraps the underlying error in case of an exception inside the command class - Throwable throwable = exception instanceof InvocationTargetException ? exception.getCause() : exception; - context.setCancelled(messageFactory.getCommandExecutionFailedMessage(context, throwable)); - checkCancelled(context); - } - } - - @SuppressWarnings("DataFlowIssue") - private boolean checkCancelled(Context context) { - if (context.isCancelled()) { - ReplyContext replyContext = new ReplyContext(context); - replyContext.getBuilder().applyData(context.getErrorMessage()); - replyContext.setEditReply(false).queue(); - return true; - } - return false; - } -} diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/modals/ModalEvent.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/modals/ModalEvent.java deleted file mode 100644 index 077007b3d..000000000 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/interactions/modals/ModalEvent.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.github.kaktushose.jda.commands.dispatching.interactions.modals; - -import com.github.kaktushose.jda.commands.dispatching.interactions.Context; -import com.github.kaktushose.jda.commands.dispatching.interactions.GenericEvent; -import com.github.kaktushose.jda.commands.dispatching.reply.ReplyContext; -import com.github.kaktushose.jda.commands.dispatching.reply.Replyable; -import com.github.kaktushose.jda.commands.reflect.InteractionRegistry; -import org.jetbrains.annotations.NotNull; - -/** - * This class is a subclass of {@link GenericEvent}. - * It provides additional features for replying to a - * {@link net.dv8tion.jda.api.events.interaction.ModalInteractionEvent ModalInteractionEvent}. - * - * @see GenericEvent - * @since 4.0.0 - */ -public class ModalEvent extends GenericEvent implements Replyable { - - private final ReplyContext replyContext; - - protected ModalEvent(Context context, InteractionRegistry interactionRegistry) { - super(context, interactionRegistry); - replyContext = new ReplyContext(context); - } - - @Override - public @NotNull ReplyContext getReplyContext() { - return replyContext; - } - - @Override - public void reply() { - replyContext.queue(); - } -} diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/internal/JDAEventListener.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/internal/JDAEventListener.java new file mode 100644 index 000000000..33c62f51b --- /dev/null +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/internal/JDAEventListener.java @@ -0,0 +1,76 @@ +package com.github.kaktushose.jda.commands.dispatching.internal; + +import com.github.kaktushose.jda.commands.dispatching.handling.DispatchingContext; +import com.github.kaktushose.jda.commands.reflect.interactions.CustomId; +import net.dv8tion.jda.api.events.interaction.GenericInteractionCreateEvent; +import net.dv8tion.jda.api.events.interaction.ModalInteractionEvent; +import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent; +import net.dv8tion.jda.api.events.interaction.command.GenericContextInteractionEvent; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; +import net.dv8tion.jda.api.events.interaction.component.GenericComponentInteractionCreateEvent; +import net.dv8tion.jda.api.hooks.ListenerAdapter; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +/// Handles incoming [GenericInteractionCreateEvent]s and maps them to their corresponding [Runtime], creating new ones if needed. +public final class JDAEventListener extends ListenerAdapter { + + private static final Logger log = LoggerFactory.getLogger(JDAEventListener.class); + private final Map runtimes = new ConcurrentHashMap<>(); + private final DispatchingContext context; + + public JDAEventListener(DispatchingContext context) { + this.context = context; + } + + @Override + public void onGenericInteractionCreate(@NotNull GenericInteractionCreateEvent jdaEvent) { + checkRuntimesAlive(); + + Runtime runtime = switch (jdaEvent) { + // always create new one (starter) + case SlashCommandInteractionEvent _, GenericContextInteractionEvent _, + CommandAutoCompleteInteractionEvent _ -> + runtimes.compute(UUID.randomUUID().toString(), (id, _) -> Runtime.startNew(id, context)); + + // always fetch runtime (bound to runtime) + case GenericComponentInteractionCreateEvent event when CustomId.isBound(event.getComponentId()) -> + runtimes.get(CustomId.runtimeId(event.getComponentId())); + case ModalInteractionEvent event when CustomId.isBound(event.getModalId()) -> + runtimes.get(CustomId.runtimeId(event.getModalId())); + + // independent components always get their own runtime + case GenericComponentInteractionCreateEvent event when CustomId.isIndependent(event.getComponentId()) -> + runtimes.compute(UUID.randomUUID().toString(), (id, _) -> Runtime.startNew(id, context)); + default -> null; + }; + + if (runtime == null) { + if (jdaEvent instanceof GenericComponentInteractionCreateEvent componentEvent && !CustomId.isInvalid(componentEvent.getComponentId())) { + componentEvent.deferEdit().setComponents().queue(); + componentEvent.getHook() + .setEphemeral(true) + .sendMessage(context.implementationRegistry().getErrorMessageFactory().getTimedOutComponentMessage()) + .queue(); + } else { + log.debug("Received unknown event: {}", jdaEvent); + } + return; + } + + log.debug("Found runtime with id {} for event {}", runtime.id(), jdaEvent); + + runtime.queueEvent(jdaEvent); + } + + private void checkRuntimesAlive() { + runtimes.values().removeIf(Runtime::isClosed); + } + + +} diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/internal/Runtime.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/internal/Runtime.java new file mode 100644 index 000000000..12082da78 --- /dev/null +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/internal/Runtime.java @@ -0,0 +1,147 @@ +package com.github.kaktushose.jda.commands.dispatching.internal; + +import com.github.kaktushose.jda.commands.dispatching.ExpirationStrategy; +import com.github.kaktushose.jda.commands.dispatching.context.KeyValueStore; +import com.github.kaktushose.jda.commands.dispatching.handling.*; +import com.github.kaktushose.jda.commands.dispatching.handling.command.ContextCommandHandler; +import com.github.kaktushose.jda.commands.dispatching.handling.command.SlashCommandHandler; +import com.github.kaktushose.jda.commands.reflect.interactions.GenericInteractionDefinition; +import net.dv8tion.jda.api.events.interaction.GenericInteractionCreateEvent; +import net.dv8tion.jda.api.events.interaction.ModalInteractionEvent; +import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent; +import net.dv8tion.jda.api.events.interaction.command.GenericContextInteractionEvent; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; +import net.dv8tion.jda.api.events.interaction.component.GenericComponentInteractionCreateEvent; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.Closeable; +import java.lang.reflect.InvocationTargetException; +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; + +/// A [Runtime] delegates the jda events to their corresponding [EventHandler] and manages the used virtual threads. +/// +/// A new [Runtime] is created each time an [SlashCommandInteractionEvent], [GenericContextInteractionEvent] or [CommandAutoCompleteInteractionEvent] is provided by jda +/// or if an interaction is marked as 'independent'. +/// Runtimes are executed in parallel, but events are processed sequentially by each runtime. +/// Every [EventHandler] called by this [Runtime] is executed in its own virtual thread, isolated from the runtime one. +/// +/// @implNote Each [Runtime] is based on a [BlockingQueue] in which jda events, belonging to this +/// runtime, are put by the [JDAEventListener] running on the jda event thread. +/// Each runtime than has its own virtual thread that takes events from this queue and executes them sequentially but +/// each in its own (sub) virtual thread. Therefore, the virtual thread in which the user code will be called, only exists for +/// the lifespan of one "interaction" and cannot interfere with other interactions on the same or other runtimes. +@ApiStatus.Internal +public final class Runtime implements Closeable { + + private static final Logger log = LoggerFactory.getLogger(Runtime.class); + private final ExpirationStrategy expirationStrategy; + private final SlashCommandHandler slashCommandHandler; + private final AutoCompleteHandler autoCompleteHandler; + private final ContextCommandHandler contextCommandHandler; + private final ComponentHandler componentHandler; + private final String id; + private final Map, Object> instances; + private final BlockingQueue blockingQueue; + private final Thread executionThread; + private final KeyValueStore keyValueStore = new KeyValueStore(); + private final ModalHandler modalHandler; + private LocalDateTime lastActivity = LocalDateTime.now(); + + private Runtime(@NotNull String id, @NotNull DispatchingContext dispatchingContext) { + this.id = id; + this.instances = new HashMap<>(); + expirationStrategy = dispatchingContext.expirationStrategy(); + blockingQueue = new LinkedBlockingQueue<>(); + slashCommandHandler = new SlashCommandHandler(dispatchingContext); + autoCompleteHandler = new AutoCompleteHandler(dispatchingContext); + contextCommandHandler = new ContextCommandHandler(dispatchingContext); + componentHandler = new ComponentHandler(dispatchingContext); + modalHandler = new ModalHandler(dispatchingContext); + this.executionThread = Thread.ofVirtual() + .name("JDAC Runtime-Thread %s".formatted(id)) + .uncaughtExceptionHandler((_, e) -> log.error("Error in JDA-Commands Runtime:", e)) + .unstarted(this::checkForEvents); + } + + @NotNull + public static Runtime startNew(String id, DispatchingContext dispatchingContext) { + var runtime = new Runtime(id, dispatchingContext); + runtime.executionThread.start(); + + log.debug("Created new runtime with id {}", id); + + return runtime; + } + + private void checkForEvents() { + try { + while (!Thread.interrupted()) { + GenericInteractionCreateEvent incomingEvent = blockingQueue.take(); + + Thread.ofVirtual().name("JDAC EventHandler-Thread %s".formatted(id)).start(() -> executeHandler(incomingEvent)).join(); + } + } catch (InterruptedException _) { + } + + log.debug("Runtime finished"); + } + + private void executeHandler(GenericInteractionCreateEvent incomingEvent) { + lastActivity = LocalDateTime.now(); + switch (incomingEvent) { + case SlashCommandInteractionEvent event -> slashCommandHandler.accept(event, this); + case GenericContextInteractionEvent event -> contextCommandHandler.accept(event, this); + case CommandAutoCompleteInteractionEvent event -> autoCompleteHandler.accept(event, this); + case GenericComponentInteractionCreateEvent event -> componentHandler.accept(event, this); + case ModalInteractionEvent event -> modalHandler.accept(event, this); + default -> + throw new IllegalStateException("Should not occur. Please report this error the the devs of jda-commands."); + } + } + + @NotNull + public String id() { + return id; + } + + public void queueEvent(GenericInteractionCreateEvent event) { + blockingQueue.add(event); + } + + @NotNull + public KeyValueStore keyValueStore() { + return keyValueStore; + } + + public Object instance(GenericInteractionDefinition definition) { + return instances.computeIfAbsent(definition.getMethod().getClass(), _ -> { + try { + return definition.newInstance(); + } catch (InvocationTargetException | InstantiationException | IllegalAccessException e) { + throw new RuntimeException(e); + } + }); + } + + @Override + public void close() { + executionThread.interrupt(); + } + + public boolean isClosed() { + if (expirationStrategy instanceof ExpirationStrategy.Inactivity(long minutes) && + lastActivity.isBefore(LocalDateTime.now().minusMinutes(minutes))) { + close(); + return true; + } + + return !executionThread.isAlive(); + } +} diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/middleware/Middleware.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/middleware/Middleware.java index 3660b336b..302867b57 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/middleware/Middleware.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/middleware/Middleware.java @@ -1,13 +1,13 @@ package com.github.kaktushose.jda.commands.dispatching.middleware; -import com.github.kaktushose.jda.commands.dispatching.interactions.Context; +import com.github.kaktushose.jda.commands.dispatching.context.InvocationContext; import net.dv8tion.jda.api.utils.messages.MessageCreateData; import java.util.function.Consumer; /** * Middlewares run just before an interaction event gets dispatched. They are used to perform additional checks or add - * more info the {@link Context}. Either register them at the {@link MiddlewareRegistry} or use the + * more info the {@link InvocationContext}. Either register them at the {@link MiddlewareRegistry} or use the * {@link com.github.kaktushose.jda.commands.annotations.Implementation Implementation} annotation. Middlewares can have * different {@link Priority Priorities} dictating their priority on execution. * @@ -16,14 +16,14 @@ * @since 4.0.0 */ @FunctionalInterface -public interface Middleware extends Consumer { +public interface Middleware extends Consumer> { /** - * Executes this middleware with the given {@link Context}. Use {@link Context#setCancelled(MessageCreateData)} + * Executes this middleware with the given {@link InvocationContext}. Use {@link InvocationContext#cancel(MessageCreateData)} * to cancel the execution chain. * - * @param context the {@link Context} of the current interaction event + * @param context the {@link InvocationContext} of the current interaction event */ - void accept(Context context); + void accept(InvocationContext context); } diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/middleware/MiddlewareRegistry.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/middleware/MiddlewareRegistry.java index 0cf968558..d98c94883 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/middleware/MiddlewareRegistry.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/middleware/MiddlewareRegistry.java @@ -1,12 +1,10 @@ package com.github.kaktushose.jda.commands.dispatching.middleware; -import com.github.kaktushose.jda.commands.dispatching.middleware.impl.ConstraintMiddleware; -import com.github.kaktushose.jda.commands.dispatching.middleware.impl.CooldownMiddleware; -import com.github.kaktushose.jda.commands.dispatching.middleware.impl.PermissionsMiddleware; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.*; +import java.util.function.Consumer; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -18,20 +16,12 @@ public class MiddlewareRegistry { private static final Logger log = LoggerFactory.getLogger(MiddlewareRegistry.class); - private final Map> middlewares; - - /** - * Constructs a new MiddlewareRegistry. - */ - public MiddlewareRegistry() { - middlewares = new HashMap<>(); - middlewares.put(Priority.LOW, new HashSet<>()); - middlewares.put(Priority.NORMAL, new HashSet<>()); - middlewares.put(Priority.HIGH, new HashSet<>()); - middlewares.put(Priority.PERMISSIONS, new HashSet<>()); - register(Priority.PERMISSIONS, new PermissionsMiddleware()); - register(Priority.NORMAL, new ConstraintMiddleware(), new CooldownMiddleware()); - } + private final SortedMap> middlewares = new TreeMap<>(Map.of( + Priority.PERMISSIONS, new HashSet<>(), + Priority.HIGH, new HashSet<>(), + Priority.NORMAL, new HashSet<>(), + Priority.LOW, new HashSet<>() + )); /** * Register {@link Middleware Middleware(s)} with the given {@link Priority}. @@ -91,7 +81,16 @@ public MiddlewareRegistry unregister(Priority priority, Collection m * @return a set of all registered middlewares {@link Middleware Middlewares} */ public Set getMiddlewares() { - return middlewares.values().stream().flatMap(Collection::stream).collect(Collectors.toUnmodifiableSet()); + return middlewares.sequencedValues() + .stream() + .flatMap(Collection::stream) + .collect(Collectors.toUnmodifiableSet()); + } + + public void forAllOrdered(Consumer task) { + for (Set value : middlewares.values()) { + value.forEach(task); + } } /** diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/middleware/Priority.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/middleware/Priority.java index d2671ea64..b2586056a 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/middleware/Priority.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/middleware/Priority.java @@ -6,21 +6,22 @@ * @see MiddlewareRegistry * @since 4.0.0 */ +// !! order matters, because enums are compared by ordinal number (java.lang.Comparable) public enum Priority { /** - * Lowest priority, will be executed at the end - */ - LOW, - /** - * Default priority. + * Middlewares with priority PERMISSIONS will always be executed first */ - NORMAL, + PERMISSIONS, /** * Highest priority for custom implementation, will be executed right after internal middlewares. */ HIGH, /** - * Middlewares with priority PERMISSIONS will always be executed first + * Default priority. + */ + NORMAL, + /** + * Lowest priority, will be executed at the end */ - PERMISSIONS + LOW } diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/middleware/impl/ConstraintMiddleware.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/middleware/impl/ConstraintMiddleware.java index c58799f31..92be28ce9 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/middleware/impl/ConstraintMiddleware.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/middleware/impl/ConstraintMiddleware.java @@ -1,18 +1,17 @@ package com.github.kaktushose.jda.commands.dispatching.middleware.impl; -import com.github.kaktushose.jda.commands.dispatching.interactions.Context; -import com.github.kaktushose.jda.commands.dispatching.interactions.commands.SlashCommandContext; +import com.github.kaktushose.jda.commands.dispatching.context.InvocationContext; import com.github.kaktushose.jda.commands.dispatching.middleware.Middleware; import com.github.kaktushose.jda.commands.reflect.ConstraintDefinition; +import com.github.kaktushose.jda.commands.reflect.ImplementationRegistry; import com.github.kaktushose.jda.commands.reflect.ParameterDefinition; import com.github.kaktushose.jda.commands.reflect.interactions.commands.SlashCommandDefinition; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.ArrayList; import java.util.List; -import java.util.Objects; /** * A {@link Middleware} implementation that will check the parameter constraints a @@ -25,20 +24,25 @@ public class ConstraintMiddleware implements Middleware { private static final Logger log = LoggerFactory.getLogger(ConstraintMiddleware.class); + private final ImplementationRegistry implementationRegistry; + + public ConstraintMiddleware(ImplementationRegistry implementationRegistry) { + this.implementationRegistry = implementationRegistry; + } + /** - * Checks if all parameters fulfill their constraints. Will cancel the {@link Context} if a parameter + * Checks if all parameters fulfill their constraints. Will cancel the {@link InvocationContext} if a parameter * constraint fails. * - * @param ctx the {@link Context} to filter + * @param context the {@link InvocationContext} to filter */ @Override - public void accept(@NotNull Context ctx) { - if (!SlashCommandInteractionEvent.class.isAssignableFrom(ctx.getEvent().getClass())) { + public void accept(@NotNull InvocationContext context) { + if (!(context.definition() instanceof SlashCommandDefinition command)) return; - } - SlashCommandContext context = (SlashCommandContext) ctx; - List arguments = context.getArguments(); - List parameters = Objects.requireNonNull(context.getCommand()).getParameters(); + + var arguments = new ArrayList<>(context.arguments()); + List parameters = command.getParameters(); log.debug("Applying parameter constraints..."); for (int i = 1; i < arguments.size(); i++) { @@ -50,10 +54,10 @@ public void accept(@NotNull Context ctx) { boolean validated = constraint.validator().apply(argument, constraint.annotation(), context); if (!validated) { - context.setCancelled( - context.getImplementationRegistry() + context.cancel( + implementationRegistry .getErrorMessageFactory() - .getConstraintFailedMessage(context, constraint) + .getConstraintFailedMessage(constraint) ); log.debug("Constraint failed!"); return; diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/middleware/impl/CooldownMiddleware.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/middleware/impl/CooldownMiddleware.java index 638b4aa1a..f56be324f 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/middleware/impl/CooldownMiddleware.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/middleware/impl/CooldownMiddleware.java @@ -1,17 +1,20 @@ package com.github.kaktushose.jda.commands.dispatching.middleware.impl; import com.github.kaktushose.jda.commands.annotations.interactions.Cooldown; -import com.github.kaktushose.jda.commands.dispatching.interactions.Context; -import com.github.kaktushose.jda.commands.dispatching.interactions.commands.SlashCommandContext; +import com.github.kaktushose.jda.commands.dispatching.context.InvocationContext; import com.github.kaktushose.jda.commands.dispatching.middleware.Middleware; import com.github.kaktushose.jda.commands.reflect.CooldownDefinition; +import com.github.kaktushose.jda.commands.reflect.ImplementationRegistry; import com.github.kaktushose.jda.commands.reflect.interactions.commands.SlashCommandDefinition; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.*; +import java.util.HashSet; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; /** * A {@link Middleware} implementation that contains the business logic behind command cooldowns. @@ -25,30 +28,25 @@ public class CooldownMiddleware implements Middleware { private static final Logger log = LoggerFactory.getLogger(CooldownMiddleware.class); - private final Map> activeCooldowns; + private final Map> activeCooldowns = new ConcurrentHashMap<>(); - public CooldownMiddleware() { - activeCooldowns = new HashMap<>(); + private final ImplementationRegistry implementationRegistry; + + public CooldownMiddleware(ImplementationRegistry implementationRegistry) { + this.implementationRegistry = implementationRegistry; } /** * Checks if an active cooldown for the given {@link SlashCommandDefinition} exists and will eventually cancel the * context. * - * @param context the {@link Context} to filter + * @param context the {@link InvocationContext} to filter */ @Override - public void accept(@NotNull Context context) { - if (!SlashCommandInteractionEvent.class.isAssignableFrom(context.getEvent().getClass())) { - return; - } - SlashCommandDefinition command = ((SlashCommandContext) context).getCommand(); - - if (!command.hasCooldown()) { - return; - } + public void accept(@NotNull InvocationContext context) { + if (!(context.definition() instanceof SlashCommandDefinition command) || !command.hasCooldown()) return; - long id = context.getEvent().getUser().getIdLong(); + long id = context.event().getUser().getIdLong(); activeCooldowns.putIfAbsent(id, new HashSet<>()); @@ -60,7 +58,7 @@ public void accept(@NotNull Context context) { if (remaining <= 0) { activeCooldowns.get(id).remove(entry); } else { - context.setCancelled(context.getImplementationRegistry().getErrorMessageFactory().getCooldownMessage(context, remaining)); + context.cancel(implementationRegistry.getErrorMessageFactory().getCooldownMessage(remaining)); log.debug("Command has a remaining cooldown of {} ms!", remaining); return; } diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/middleware/impl/PermissionsMiddleware.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/middleware/impl/PermissionsMiddleware.java index 13209a6ef..aa8149205 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/middleware/impl/PermissionsMiddleware.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/middleware/impl/PermissionsMiddleware.java @@ -1,9 +1,10 @@ package com.github.kaktushose.jda.commands.dispatching.middleware.impl; import com.github.kaktushose.jda.commands.annotations.interactions.Permissions; -import com.github.kaktushose.jda.commands.dispatching.interactions.Context; +import com.github.kaktushose.jda.commands.dispatching.context.InvocationContext; import com.github.kaktushose.jda.commands.dispatching.middleware.Middleware; import com.github.kaktushose.jda.commands.permissions.PermissionsProvider; +import com.github.kaktushose.jda.commands.reflect.ImplementationRegistry; import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.User; import net.dv8tion.jda.api.events.interaction.GenericInteractionCreateEvent; @@ -15,9 +16,9 @@ * A {@link Middleware} implementation that will check permissions. * The default implementation can only handle discord permissions. However, the {@link PermissionsProvider} can be * used for own implementations. - * This filter will first check against {@link PermissionsProvider#hasPermission(User, Context)} with a + * This filter will first check against {@link PermissionsProvider#hasPermission(User, InvocationContext)} with a * {@link User} object. This can be used for global permissions. Afterward - * {@link PermissionsProvider#hasPermission(Member, Context)} will be called. Since the {@link Member} is + * {@link PermissionsProvider#hasPermission(User, InvocationContext)} will be called. Since the {@link Member} is * available this might be used for guild related permissions. * * @see Permissions @@ -28,16 +29,22 @@ public class PermissionsMiddleware implements Middleware { private static final Logger log = LoggerFactory.getLogger(PermissionsMiddleware.class); + private final ImplementationRegistry implementationRegistry; + + public PermissionsMiddleware(ImplementationRegistry implementationRegistry) { + this.implementationRegistry = implementationRegistry; + } + /** * Checks if the {@link User} and respectively the {@link Member} has the permission to execute the command. * - * @param context the {@link Context} to filter + * @param context the {@link InvocationContext} to filter */ @Override - public void accept(@NotNull Context context) { + public void accept(@NotNull InvocationContext context) { log.debug("Checking permissions..."); - PermissionsProvider provider = context.getImplementationRegistry().getPermissionsProvider(); - GenericInteractionCreateEvent event = context.getEvent(); + PermissionsProvider provider = implementationRegistry.getPermissionsProvider(); + GenericInteractionCreateEvent event = context.event(); boolean hasPerms; if (event.getMember() != null) { @@ -46,7 +53,7 @@ public void accept(@NotNull Context context) { hasPerms = provider.hasPermission(event.getUser(), context); } if (!hasPerms) { - context.setCancelled(context.getImplementationRegistry().getErrorMessageFactory().getInsufficientPermissionsMessage(context)); + context.cancel(implementationRegistry.getErrorMessageFactory().getInsufficientPermissionsMessage(context.definition())); log.debug("Insufficient permissions!"); return; } diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/middleware/impl/package-info.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/middleware/impl/package-info.java new file mode 100644 index 000000000..1f2eb3433 --- /dev/null +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/middleware/impl/package-info.java @@ -0,0 +1,2 @@ +/// Implementations of commonly used Middlewares, such as permissions or cooldown handling. +package com.github.kaktushose.jda.commands.dispatching.middleware.impl; \ No newline at end of file diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/middleware/package-info.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/middleware/package-info.java new file mode 100644 index 000000000..64e2bf0bf --- /dev/null +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/middleware/package-info.java @@ -0,0 +1,2 @@ +/// Middlewares get executed between preparation and method invocation. +package com.github.kaktushose.jda.commands.dispatching.middleware; \ No newline at end of file diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/reply/Component.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/reply/Component.java new file mode 100644 index 000000000..b7ffc4fe6 --- /dev/null +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/reply/Component.java @@ -0,0 +1,65 @@ +package com.github.kaktushose.jda.commands.dispatching.reply; + +import com.github.kaktushose.jda.commands.annotations.interactions.Button; +import com.github.kaktushose.jda.commands.annotations.interactions.EntitySelectMenu; +import com.github.kaktushose.jda.commands.annotations.interactions.StringSelectMenu; + +import java.util.Arrays; + +/// Represents a component, namely [Button], [StringSelectMenu] or [EntitySelectMenu], that should be added to a reply. +/// +/// Also holds the following two settings: +/// - enabled: +/// +/// to enable or disable the component +/// - independent: +/// +/// whether the component should be executed in the same [`Runtime`]({@docRoot}/index.html#runtime-concept-heading) as the command it is bound to or not. If `true`, +/// every component interaction will create a new [`Runtime`]({@docRoot}/index.html#runtime-concept-heading). Furthermore, the component cannot expire and will always +/// get executed, even after a bot restart. +/// +/// ### Example: +/// ``` +/// @SlashCommand("example command") +/// public void onCommand(CommandEvent event) { +/// event.with().components(Components.of(true, false, "onButton")).reply(); +/// } +///``` +/// @since 2.3.0 +public record Component(boolean enabled, boolean independent, String name) { + + /// Adds enabled, runtime-bound [Component]s to the reply. + /// + /// @param components the name of the method that represents the component + public static Component[] enabled(String... components) { + return of(true, false, components); + } + + /// Adds disabled, runtime-bound [Component]s to the reply. + /// + /// @param components the name of the method that represents the component + public static Component[] disabled(String... components) { + return of(false, false, components); + } + + /// Adds enabled, runtime-independent [Component]s to the reply. + /// + /// Every component interaction will create a new [`Runtime`]({@docRoot}/index.html#runtime-concept-heading). Furthermore, the component cannot expire and + /// will always get executed, even after a bot restart. + /// + /// @param components the name of the method that represents the component + public static Component[] independent(String... components) { + return of(true, true, components); + } + + /// Adds [Component]s with the passed configuration to the reply. + /// + /// @param enabled whether the [Component] should be enabled or disabled + /// @param independent whether the [Component] should be runtime-bound or independent + /// @param components the name of the method that represents the component + public static Component[] of(boolean enabled, boolean independent, String... components) { + return Arrays.stream(components) + .map(it -> new Component(enabled, independent, it)) + .toArray(Component[]::new); + } +} diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/reply/ComponentReply.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/reply/ComponentReply.java new file mode 100644 index 000000000..459223fc3 --- /dev/null +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/reply/ComponentReply.java @@ -0,0 +1,23 @@ +package com.github.kaktushose.jda.commands.dispatching.reply; + +import net.dv8tion.jda.api.entities.Message; +import org.jetbrains.annotations.NotNull; + +/// Subtype of [ConfigurableReply] that allows replying only with a name without a message. +/// +/// @since 4.0.0 +public final class ComponentReply extends ConfigurableReply { + + /// Constructs a new ComponentReply. + /// + /// @param reply the underlying [ConfigurableReply] + public ComponentReply(@NotNull ConfigurableReply reply) { + super(reply); + } + + /// Sends the reply to Discord. + public Message reply() { + return complete(); + } + +} diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/reply/ConfigurableReply.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/reply/ConfigurableReply.java new file mode 100644 index 000000000..2d0177b60 --- /dev/null +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/reply/ConfigurableReply.java @@ -0,0 +1,198 @@ +package com.github.kaktushose.jda.commands.dispatching.reply; + +import com.github.kaktushose.jda.commands.annotations.interactions.ReplyConfig; +import com.github.kaktushose.jda.commands.dispatching.internal.Runtime; +import com.github.kaktushose.jda.commands.reflect.InteractionRegistry; +import com.github.kaktushose.jda.commands.reflect.interactions.components.ButtonDefinition; +import com.github.kaktushose.jda.commands.reflect.interactions.components.GenericComponentDefinition; +import com.github.kaktushose.jda.commands.reflect.interactions.components.menus.GenericSelectMenuDefinition; +import net.dv8tion.jda.api.interactions.components.ActionRow; +import net.dv8tion.jda.api.interactions.components.ItemComponent; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +/// Subtype of [MessageReply] that supports adding components to messages and changing the [ReplyConfig]. +/// +/// ### Example: +/// ``` +/// @Interaction +/// public class ExampleCommand { +/// +/// @SlashCommand(value = "example command") +/// public void onCommand(CommandEvent event) { +/// event.with().components(buttons("onButton")).reply("Hello World"); +/// } +/// +/// @Button("Press me!") +/// public void onButton(ComponentEvent event) { +/// event.reply("You pressed me!"); +/// } +/// } +/// ``` +/// +/// @since 4.0.0 +public sealed class ConfigurableReply extends MessageReply permits ComponentReply { + + protected final InteractionRegistry interactionRegistry; + protected final String runtimeId; + + /// Constructs a new ConfigurableReply. + /// + /// @param reply the underlying [MessageReply] + /// @param registry the corresponding [InteractionRegistry] + /// @param runtimeId the corresponding [Runtime] + public ConfigurableReply(@NotNull MessageReply reply, @NotNull InteractionRegistry registry, @NotNull String runtimeId) { + super(reply); + this.interactionRegistry = registry; + this.runtimeId = runtimeId; + } + + /// Constructs a new ConfigurableReply. + /// + /// @param reply the [ConfigurableReply] to copy + public ConfigurableReply(@NotNull ConfigurableReply reply) { + super(reply); + this.interactionRegistry = reply.interactionRegistry; + this.runtimeId = reply.runtimeId; + } + + /// Whether to send ephemeral replies. Default value is `false`. + /// + /// Ephemeral messages have some limitations and will be removed once the user restarts their client. + /// Limitations: + /// - Cannot contain any files/ attachments + /// - Cannot be reacted to + /// - Cannot be retrieved + /// + /// **This will override both [GlobalReplyConfig] and any [ReplyConfig] annotation!** + /// + /// @param ephemeral `true` if to send ephemeral replies + /// @return the current instance for fluent interface + @NotNull + public ConfigurableReply ephemeral(boolean ephemeral) { + this.ephemeral = ephemeral; + return this; + } + + /// Whether to keep the original components when editing a message. Default value is `true`. + /// + /// More formally, if editing a message and `keepComponents` is `true`, the original message will first be queried and + /// its components get added to the reply before it is sent. + /// + /// **This will override both [GlobalReplyConfig] and any [ReplyConfig] annotation!** + /// + /// @param editReply `true` if to keep the original components + /// @return the current instance for fluent interface + @NotNull + public ConfigurableReply editReply(boolean editReply) { + this.editReply = editReply; + return this; + } + + /// Whether to edit the original message or to send a new one. Default value is `true`. + /// + /// The original message is always the very first reply that was sent. E.g. for a slash command event, which was + /// replied to with a text message and a button, the original message is that very reply. + /// + /// Subsequent replies to the same slash command event or the button event cannot be edited. + /// + /// **This will override both [GlobalReplyConfig] and any [ReplyConfig] annotation!** + /// + /// @param keepComponents `true` if to edit the original method + /// @return the current instance for fluent interface + @NotNull + public ConfigurableReply keepComponents(boolean keepComponents) { + this.keepComponents = keepComponents; + return this; + } + + /// Adds an [ActionRow] to the reply and adds the passed components to it. + /// + /// The components will always be enabled and runtime-bound. Use [#components(Component...)] if you want to modify these + /// settings. + /// + /// **The components must be defined in the same class where this method gets called!** + /// + /// ### Example: + /// ``` + /// @Interaction + /// public class ExampleCommand { + /// + /// @SlashCommand(value = "example command") + /// public void onCommand(CommandEvent event) { + /// event.with().components("onButton").reply("Hello World"); + /// } + /// + /// @Button("Press me!") + /// public void onButton(ComponentEvent event) { + /// event.reply("You pressed me!"); + /// } + /// } + /// ``` + /// + /// @param components the name of the components to add + /// @return the current instance for fluent interface + @NotNull + public ComponentReply components(@NotNull String... components) { + return components(Component.of(true, false, components)); + } + + /// Adds an [ActionRow] to the reply and adds the passed [Component] to it. + /// + /// **The components must be defined in the same class where this method gets called!** + /// + /// ### Example: + /// ``` + /// @Interaction + /// public class ExampleCommand { + /// + /// @SlashCommand(value = "example command") + /// public void onCommand(CommandEvent event) { + /// event.with().components(Components.disabled("onButton")).reply("Hello World"); + /// } + /// + /// @Button("Press me!") + /// public void onButton(ComponentEvent event) { + /// event.reply("You pressed me!"); + /// } + /// } + /// ``` + /// + /// @see Component + /// @param components the [Component] to add + /// @return the current instance for fluent interface + @NotNull + public ComponentReply components(@NotNull Component... components) { + List items = new ArrayList<>(); + for (Component component : components) { + var definitionId = String.valueOf((definition.getMethod().getDeclaringClass().getName() + component.name()).hashCode()); + var definition = interactionRegistry.find(GenericComponentDefinition.class, false, it -> + it.getDefinitionId().equals(definitionId) + ); + log.debug("Reply Debug: Adding component \"{}\" to the reply", definition.getDisplayName()); + switch (definition) { + case ButtonDefinition buttonDefinition -> { + var button = buttonDefinition.toButton().withDisabled(!component.enabled()); + //only assign ids to non-link buttons + items.add(button.getUrl() == null ? button.withId(createId(definition, component.independent())) : button); + } + case GenericSelectMenuDefinition menuDefinition -> + items.add(menuDefinition.toSelectMenu(createId(definition, component.independent()), component.enabled())); + } + } + if (!items.isEmpty()) { + builder.addComponents(ActionRow.of(items)); + } + + return new ComponentReply(this); + } + + private String createId(GenericComponentDefinition definition, boolean staticComponent) { + return staticComponent + ? definition.independentCustomId() + : definition.boundCustomId(runtimeId); + } +} + diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/reply/GlobalReplyConfig.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/reply/GlobalReplyConfig.java new file mode 100644 index 000000000..b36322302 --- /dev/null +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/reply/GlobalReplyConfig.java @@ -0,0 +1,31 @@ +package com.github.kaktushose.jda.commands.dispatching.reply; + +import com.github.kaktushose.jda.commands.annotations.interactions.ReplyConfig; + +/// Will be used if no [ReplyConfig] annotation is present. +public class GlobalReplyConfig { + + /// Whether to send ephemeral replies. Default value is `false`. + /// + /// Ephemeral messages have some limitations and will be removed once the user restarts their client. + /// Limitations: + /// - Cannot contain any files/ attachments + /// - Cannot be reacted to + /// - Cannot be retrieved + public static boolean ephemeral = false; + + /// Whether to keep the original components when editing a message. Default value is `true`. + /// + /// More formally, if editing a message and `keepComponents` is `true`, the original message will first be queried and + /// its components get added to the reply before it is sent. + public static boolean keepComponents = true; + + /// Whether to edit the original message or to send a new one. Default value is `true`. + /// + /// The original message is always the very first reply that was sent. E.g. for a slash command event, which was + /// replied to with a text message and a button, the original message is that very reply. + /// + /// Subsequent replies to the same slash command event or the button event are not affected. + public static boolean editReply = true; + +} diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/reply/MessageReply.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/reply/MessageReply.java new file mode 100644 index 000000000..b5cf325d3 --- /dev/null +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/reply/MessageReply.java @@ -0,0 +1,147 @@ +package com.github.kaktushose.jda.commands.dispatching.reply; + +import com.github.kaktushose.jda.commands.dispatching.events.ReplyableEvent; +import com.github.kaktushose.jda.commands.reflect.interactions.EphemeralInteractionDefinition; +import com.github.kaktushose.jda.commands.reflect.interactions.GenericInteractionDefinition; +import com.github.kaktushose.jda.commands.reflect.interactions.ReplyConfig; +import net.dv8tion.jda.api.EmbedBuilder; +import net.dv8tion.jda.api.entities.Message; +import net.dv8tion.jda.api.events.interaction.GenericInteractionCreateEvent; +import net.dv8tion.jda.api.events.interaction.ModalInteractionEvent; +import net.dv8tion.jda.api.interactions.InteractionHook; +import net.dv8tion.jda.api.interactions.callbacks.IDeferrableCallback; +import net.dv8tion.jda.api.interactions.callbacks.IMessageEditCallback; +import net.dv8tion.jda.api.interactions.callbacks.IReplyCallback; +import net.dv8tion.jda.api.utils.messages.MessageCreateBuilder; +import net.dv8tion.jda.api.utils.messages.MessageCreateData; +import net.dv8tion.jda.api.utils.messages.MessageEditData; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/// Simple builder for sending text messages based on a [GenericInteractionCreateEvent]. +/// +/// More formally, can be used to +/// send an arbitrary amount of message replies to the text channel the [GenericInteractionCreateEvent] was executed in. +/// +/// Example: +/// ``` +/// new MessageReply(event, definition, new ReplyConfig()).reply(errorMessage); +///``` +/// +/// @see ConfigurableReply +/// @see ReplyableEvent +/// @since 4.0.0 +public sealed class MessageReply implements Reply permits ConfigurableReply { + + protected static final Logger log = LoggerFactory.getLogger(MessageReply.class); + protected final GenericInteractionCreateEvent event; + protected final GenericInteractionDefinition definition; + protected final MessageCreateBuilder builder; + protected boolean ephemeral; + protected boolean editReply; + protected boolean keepComponents; + + /// Constructs a new MessageReply. + /// + /// @param event the corresponding [GenericInteractionCreateEvent] + /// @param definition the corresponding [GenericInteractionDefinition]. This is mostly needed by the + /// [ConfigurableReply] + /// @param replyConfig the [ReplyConfig] to use + public MessageReply(@NotNull GenericInteractionCreateEvent event, + @NotNull GenericInteractionDefinition definition, + @NotNull ReplyConfig replyConfig) { + this.event = event; + this.definition = definition; + this.ephemeral = replyConfig.ephemeral(); + this.editReply = replyConfig.editReply(); + this.keepComponents = replyConfig.keepComponents(); + this.builder = new MessageCreateBuilder(); + } + + /// Constructs a new MessageReply. + /// + /// @param event the corresponding [GenericInteractionCreateEvent] + /// @param definition the corresponding [EphemeralInteractionDefinition]. This is mostly needed by the + /// [ConfigurableReply] + public MessageReply(@NotNull GenericInteractionCreateEvent event, @NotNull EphemeralInteractionDefinition definition) { + this(event, definition, definition.replyConfig()); + } + + /// Constructs a new MessageReply. + /// + /// @param reply the [MessageReply] to copy + public MessageReply(@NotNull MessageReply reply) { + this.event = reply.event; + this.builder = reply.builder; + this.definition = reply.definition; + this.ephemeral = reply.ephemeral; + this.editReply = reply.editReply; + this.keepComponents = reply.keepComponents; + } + + public Message reply(@NotNull String message) { + builder.setContent(message); + return complete(); + } + + public Message reply(@NotNull MessageCreateData message) { + builder.applyData(message); + return complete(); + } + + public Message reply(@NotNull EmbedBuilder builder) { + this.builder.setEmbeds(builder.build()); + return complete(); + } + + /// Sends the reply to Discord and blocks the current thread until the message was sent. + /// + /// @return the [Message] that got created + /// @implNote This method can handle both message replies and message edits. it will check if the interaction got + /// acknowledged and will acknowledge it if necessary before sending or editing a message. After that, + /// [InteractionHook#sendMessage(MessageCreateData)] or respectively [InteractionHook#editOriginal(MessageEditData)] + /// will be called. + /// + /// If editing a message and `keepComponents` is `true`, queries the original message first and adds its components + /// to the reply before sending it. + @NotNull + protected Message complete() { + switch (event) { + case ModalInteractionEvent modalEvent when modalEvent.getMessage() != null && editReply -> + deferEdit(modalEvent); + case IMessageEditCallback callback when editReply -> deferEdit(callback); + case IReplyCallback callback -> deferReply(callback); + default -> throw new IllegalArgumentException( + "Cannot reply to '%s'! Please report this error to the devs of jda-commands!".formatted(event.getClass().getName()) + ); + } + if (event instanceof ModalInteractionEvent modalEvent) { + editReply = modalEvent.getMessage() != null; + } + log.debug( + "Replying to interaction \"{}\" with content: {} [ephemeral={}, editReply={}, keepComponents={}]", + definition.getDisplayName(), builder.build().toData(), ephemeral, editReply, keepComponents + ); + var hook = ((IDeferrableCallback) event).getHook(); + if (editReply) { + if (keepComponents) { + builder.addComponents(hook.retrieveOriginal().complete().getComponents()); + } + return hook.editOriginal(MessageEditData.fromCreateData(builder.build())).complete(); + } + return hook.setEphemeral(ephemeral).sendMessage(builder.build()).complete(); + } + + private void deferReply(@NotNull IReplyCallback callback) { + if (!event.isAcknowledged()) { + callback.deferReply(ephemeral).queue(); + } + } + + private void deferEdit(@NotNull IMessageEditCallback callback) { + if (!event.isAcknowledged()) { + callback.deferEdit().queue(); + } + } +} diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/reply/ModalReplyable.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/reply/ModalReplyable.java deleted file mode 100644 index c49e6f403..000000000 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/reply/ModalReplyable.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.github.kaktushose.jda.commands.dispatching.reply; - -import com.github.kaktushose.jda.commands.dispatching.interactions.Context; -import com.github.kaktushose.jda.commands.reflect.interactions.ModalDefinition; -import net.dv8tion.jda.api.events.interaction.GenericInteractionCreateEvent; -import net.dv8tion.jda.api.interactions.callbacks.IModalCallback; - -/** - * Generic interface holding reply methods for modal replies. - * - * @since 4.0.0 - */ -public interface ModalReplyable extends Replyable { - - /** - * Replies to this Interaction with a Modal. This will open a popup on the target user's Discord client. - * - * @param modal the id of the modal to reply with - */ - default void replyModal(String modal) { - IModalCallback callback; - Context context = getContext(); - GenericInteractionCreateEvent event = context.getEvent(); - if (event instanceof IModalCallback) { - callback = (IModalCallback) event; - } else { - throw new IllegalArgumentException( - String.format("Cannot reply to '%s'! Please report this error to the jda-commands devs!", event.getClass().getName()) - ); - } - - ModalDefinition modalDefinition = context.getInteractionRegistry().getModals().stream() - .filter(it -> it.getDefinitionId().equals(String.format("%s%s", context.getInteractionDefinition().getMethod().getDeclaringClass().getSimpleName(), modal))) - .findFirst() - .orElseThrow(() -> new IllegalArgumentException("Unknown Modal")); - - callback.replyModal(modalDefinition.toModal(context.getRuntime().getRuntimeId())).queue(); - } -} diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/reply/Reply.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/reply/Reply.java new file mode 100644 index 000000000..b11c0ae39 --- /dev/null +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/reply/Reply.java @@ -0,0 +1,87 @@ +package com.github.kaktushose.jda.commands.dispatching.reply; + +import com.github.kaktushose.jda.commands.embeds.EmbedDTO; +import com.github.kaktushose.jda.commands.dispatching.events.ReplyableEvent; +import net.dv8tion.jda.api.EmbedBuilder; +import net.dv8tion.jda.api.entities.Message; +import net.dv8tion.jda.api.events.interaction.GenericInteractionCreateEvent; +import net.dv8tion.jda.api.requests.RestAction; +import net.dv8tion.jda.api.utils.messages.MessageCreateData; +import org.jetbrains.annotations.NotNull; + +/// Common interface for classes that support simple message replies to [GenericInteractionCreateEvent]. +/// +/// This interface ensures that [ReplyableEvent] and [MessageReply], which is internally used by [ReplyableEvent], +/// always share the same reply methods. +/// +/// @see ReplyableEvent +/// @since 4.0.0 +public sealed interface Reply permits MessageReply, ReplyableEvent { + + /// Acknowledgement of this event with a text message. + /// + /// @implSpec Internally this method must call [RestAction#complete()], thus the [Message] object can get + /// returned directly. + /// + /// This might throw [RuntimeException]s if JDA fails to send the message. + /// + /// @param message the message to send + /// @return the [Message] that got created + Message reply(@NotNull String message); + + /// Acknowledgement of this event with a text message. + /// + /// @implSpec Internally this method must call [RestAction#complete()], thus the [Message] object can get + /// returned directly. + /// + /// This might throw [RuntimeException]s if JDA fails to send the message. + /// + /// @param format the message to send + /// @param args Arguments referenced by the format specifiers in the format string. If there are more arguments than + /// format specifiers, the extra arguments are ignored. The number of arguments is variable and may be + /// zero. + /// @return the [Message] that got created + /// @throws java.util.IllegalFormatException If a format string contains an illegal syntax, a format specifier that + /// is incompatible with the given arguments, insufficient arguments given + /// the format string, or other illegal conditions + /// is incompatible with the given arguments, insufficient arguments given + /// the format string, or other illegal conditions. + default Message reply(@NotNull String format, @NotNull Object... args) { + return reply(format.formatted(args)); + } + + /// Acknowledgement of this event with a text message. + /// + /// @implSpec Internally this method must call [RestAction#complete()], thus the [Message] object can get + /// returned directly. + /// + /// This might throw [RuntimeException]s if JDA fails to send the message. + /// + /// @param message the [MessageCreateData] to send + /// @return the [Message] that got created + Message reply(@NotNull MessageCreateData message); + + /// Acknowledgement of this event with a text message. + /// + /// @implSpec Internally this method must call [RestAction#complete()], thus the [Message] object can get + /// returned directly. + /// + /// This might throw [RuntimeException]s if JDA fails to send the message. + /// + /// @param builder the [EmbedBuilder] to send + /// @return the [Message] that got created + Message reply(@NotNull EmbedBuilder builder); + + /// Acknowledgement of this event with a text message. + /// + /// @implSpec Internally this method must call [RestAction#complete()], thus the [Message] object can get + /// returned directly. + /// + /// This might throw [RuntimeException]s if JDA fails to send the message. + /// + /// @param embedDTO the [EmbedDTO] to send + /// @return the [Message] that got created + default Message reply(@NotNull EmbedDTO embedDTO) { + return reply(embedDTO.toEmbedBuilder()); + } +} diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/reply/ReplyContext.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/reply/ReplyContext.java deleted file mode 100644 index 940c83d70..000000000 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/reply/ReplyContext.java +++ /dev/null @@ -1,246 +0,0 @@ -package com.github.kaktushose.jda.commands.dispatching.reply; - -import com.github.kaktushose.jda.commands.dispatching.interactions.Context; -import com.github.kaktushose.jda.commands.dispatching.interactions.commands.SlashCommandContext; -import net.dv8tion.jda.api.entities.Message; -import net.dv8tion.jda.api.events.interaction.GenericInteractionCreateEvent; -import net.dv8tion.jda.api.events.interaction.ModalInteractionEvent; -import net.dv8tion.jda.api.interactions.callbacks.IMessageEditCallback; -import net.dv8tion.jda.api.interactions.callbacks.IReplyCallback; -import net.dv8tion.jda.api.utils.messages.MessageCreateBuilder; -import net.dv8tion.jda.api.utils.messages.MessageCreateData; -import net.dv8tion.jda.api.utils.messages.MessageEditData; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.lang.reflect.InvocationTargetException; -import java.util.function.Consumer; - -/** - * This class models a reply to an interaction. It is mainly used by the {@link Replyable} interface to construct a - * reply but can also be accessed directly. - * - * @since 4.0.0 - */ -public class ReplyContext { - - private static final Logger log = LoggerFactory.getLogger(ReplyContext.class); - private final GenericInteractionCreateEvent event; - private final MessageCreateBuilder builder; - private Consumer success; - private Consumer failure; - private boolean editReply; - private boolean keepComponents; - private boolean ephemeralReply; - - /** - * Constructs a new ReplyContext. - * - * @param context the corresponding {@link SlashCommandContext} - */ - public ReplyContext(Context context) { - event = context.getEvent(); - builder = new MessageCreateBuilder(); - success = (message) -> { - }; - failure = (throwable) -> { - log.error("The response request encountered an exception at its execution point!", new InvocationTargetException(throwable)); - }; - editReply = true; - keepComponents = true; - ephemeralReply = context.isEphemeral(); - } - - /** - * Transforms this ReplyContext to a {@link MessageCreateData}. This is equal to {@code getBuilder().build()}. - * - * @return the {@link MessageCreateData} - */ - public MessageCreateData toMessageCreateData() { - return builder.build(); - } - - /** - * Transforms this ReplyContext to a {@link MessageEditData}. - * - * @return the {@link MessageEditData} - */ - public MessageEditData toMessageEditData() { - return MessageEditData.fromCreateData(toMessageCreateData()); - } - - /** - * Returns the underlying {@link MessageCreateBuilder}. - * - * @return the underlying {@link MessageCreateBuilder} - */ - public MessageCreateBuilder getBuilder() { - return builder; - } - - /** - * Returns the success callback. - * - * @return the success callback - */ - public Consumer getSuccessCallback() { - return success; - } - - /** - * Sets the success callback. - * - * @return this instance - */ - public ReplyContext setSuccessCallback(Consumer consumer) { - this.success = consumer; - return this; - } - - /** - * Returns the failure callback. - * - * @return the failure callback - */ - public Consumer getFailureCallback() { - return failure; - } - - /** - * Returns the failure callback. - * - * @return this instance - */ - public ReplyContext setFailureCallback(Consumer consumer) { - this.failure = consumer; - return this; - } - - /** - * Whether this reply should edit the original message. - * - * @return {@code true} if this reply should edit the original message - */ - public boolean isEditReply() { - return editReply; - } - - /** - * Whether this reply should edit the original message. - * - * @param editReply {@code true} if this reply should edit the original message - * @return this instance - */ - public ReplyContext setEditReply(boolean editReply) { - this.editReply = editReply; - return this; - } - - /** - * Whether this reply should keep all components of the original message. - * - * @return {@code true} this reply should keep all components of the original message - */ - public boolean isKeepComponents() { - return keepComponents; - } - - /** - * Whether this reply should keep all components of the original message. - * - * @param keepComponents {@code true} this reply should keep all components of the original message - * @return this instance - */ - public ReplyContext setKeepComponents(boolean keepComponents) { - this.keepComponents = keepComponents; - return this; - } - - /** - * Whether this reply should be ephemeral. - * - * @return this instance - */ - public boolean isEphemeralReply() { - return ephemeralReply; - } - - /** - * Whether this reply should be ephemeral. - * - * @return {@code true} if this reply should be ephemeral - */ - public ReplyContext setEphemeralReply(boolean ephemeralReply) { - this.ephemeralReply = ephemeralReply; - return this; - } - - /** - * Removes all components from the last message that was sent. This will only work with - * {@link #setEditReply(boolean)} set to true. - */ - public void removeComponents() { - IReplyCallback callback; - if (event instanceof IReplyCallback) { - callback = (IReplyCallback) event; - } else { - throw new IllegalArgumentException( - String.format("Cannot reply to '%s'! Please report this error to the jda-commands devs!", event.getClass().getName()) - ); - } - if (!event.isAcknowledged()) { - callback.deferReply(false).queue(); - } - if (editReply) { - callback.getHook().editOriginalComponents().queue(); - } else { - log.warn("Cannot remove components with 'editReply' set to 'false'!"); - } - } - - /** - * Sends the reply to Discord, taking into account all the settings that were previously made to this context. - */ - public void queue() { - if (editReply) { - queueEdit(); - } else { - queueReply(); - } - } - - private void queueEdit() { - IMessageEditCallback callback; - if (event instanceof IMessageEditCallback) { - callback = (IMessageEditCallback) event; - } else { - queueReply(); - return; - } - if (event instanceof ModalInteractionEvent) { - if (((ModalInteractionEvent) event).getMessage() == null) { - queueReply(); - return; - } - } - if (!event.isAcknowledged()) { - callback.deferEdit().queue(); - } - callback.getHook().editOriginal(toMessageEditData()).queue(success, failure); - } - - private void queueReply() { - IReplyCallback callback; - if (event instanceof IReplyCallback) { - callback = (IReplyCallback) event; - } else { - throw new IllegalArgumentException( - String.format("Cannot reply to '%s'! Please report this error to the jda-commands devs!", event.getClass().getName()) - ); - } - if (!event.isAcknowledged()) { - callback.deferReply(ephemeralReply).queue(); - } - callback.getHook().sendMessage(toMessageCreateData()).queue(success, failure); - } -} diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/reply/Replyable.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/reply/Replyable.java deleted file mode 100644 index 1faa089bd..000000000 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/reply/Replyable.java +++ /dev/null @@ -1,347 +0,0 @@ -package com.github.kaktushose.jda.commands.dispatching.reply; - -import com.github.kaktushose.jda.commands.annotations.interactions.SlashCommand; -import com.github.kaktushose.jda.commands.data.EmbedDTO; -import com.github.kaktushose.jda.commands.dispatching.KeyValueStore; -import com.github.kaktushose.jda.commands.dispatching.interactions.Context; -import com.github.kaktushose.jda.commands.dispatching.interactions.GenericEvent; -import com.github.kaktushose.jda.commands.dispatching.reply.components.Buttons; -import com.github.kaktushose.jda.commands.dispatching.reply.components.Component; -import com.github.kaktushose.jda.commands.dispatching.reply.components.SelectMenus; -import net.dv8tion.jda.api.EmbedBuilder; -import net.dv8tion.jda.api.entities.Message; -import net.dv8tion.jda.api.interactions.components.ActionRow; -import net.dv8tion.jda.api.interactions.components.ItemComponent; -import net.dv8tion.jda.api.interactions.components.buttons.Button; -import net.dv8tion.jda.api.utils.messages.MessageCreateData; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.ArrayList; -import java.util.List; -import java.util.function.Consumer; - -/** - * Generic interface holding reply methods. - * - * @since 2.3.0 - */ -public interface Replyable { - - Logger log = LoggerFactory.getLogger(GenericEvent.class); - - /** - * Sends a message to the TextChannel where the interaction was executed. - * - * @param message the message to send - */ - default void reply(@NotNull String message) { - getReplyContext().getBuilder().setContent(message); - reply(); - } - - /** - * Sends a formatted message using the specified format string and arguments to the TextChannel where the interaction was executed. - * - * @param format the message to send - * @param args Arguments referenced by the format specifiers in the format string. If there are more arguments than - * format specifiers, the extra arguments are ignored. The number of arguments is variable and may be - * zero. - * @throws java.util.IllegalFormatException If a format string contains an illegal syntax, a format specifier that - * is incompatible with the given arguments, insufficient arguments given - * the format string, or other illegal conditions. - */ - default void reply(@NotNull String format, @NotNull Object... args) { - getReplyContext().getBuilder().setContent(String.format(format, args)); - reply(); - } - - /** - * Sends a message to the TextChannel where the interaction was executed. - * - * @param message the {@code Message} to send - */ - default void reply(@NotNull MessageCreateData message) { - getReplyContext().getBuilder().applyData(message); - reply(); - } - - /** - * Sends a message to the TextChannel where the interaction was executed. - * - * @param builder the {@code EmbedBuilder} to send - */ - default void reply(@NotNull EmbedBuilder builder) { - getReplyContext().getBuilder().setEmbeds(builder.build()); - reply(); - } - - /** - * Sends a message to the TextChannel where the interaction was executed. - * - * @param embedDTO the {@link EmbedDTO} to send - */ - default void reply(@NotNull EmbedDTO embedDTO) { - getReplyContext().getBuilder().setEmbeds(embedDTO.toMessageEmbed()); - reply(); - } - - /** - * Sends a message to the TextChannel where the interaction was executed. This method also allows to access the JDA RestAction - * consumer. - * - * @param message the {@link String} message to send - * @param success the JDA RestAction success consumer - * @see JDA RestAction Documentation - */ - default void reply(@NotNull String message, @Nullable Consumer success) { - getReplyContext().getBuilder().setContent(message); - setSuccessCallback(success); - reply(); - } - - /** - * Sends a message to the TextChannel where the interaction was executed. This method also allows to access the JDA RestAction - * consumer. - * - * @param message the {@link Message} to send - * @param success the JDA RestAction success consumer - * @see JDA RestAction Documentation - */ - default void reply(@NotNull MessageCreateData message, @Nullable Consumer success) { - getReplyContext().getBuilder().applyData(message); - setSuccessCallback(success); - reply(); - } - - /** - * Sends a message to the TextChannel where the interaction was executed. This method also allows to access the JDA RestAction - * consumer. - * - * @param builder the {@link EmbedBuilder} to send - * @param success the JDA RestAction success consumer - * @see JDA RestAction Documentation - */ - default void reply(@NotNull EmbedBuilder builder, @Nullable Consumer success) { - getReplyContext().getBuilder().setEmbeds(builder.build()); - setSuccessCallback(success); - reply(); - } - - /** - * Sends a message to the TextChannel where the interaction was executed. This method also allows to access the JDA RestAction - * consumer. - * - * @param embedDTO the {@link EmbedDTO} to send - * @param success the JDA RestAction success consumer - * @see JDA RestAction Documentation - */ - default void reply(@NotNull EmbedDTO embedDTO, @Nullable Consumer success) { - getReplyContext().getBuilder().setEmbeds(embedDTO.toMessageEmbed()); - setSuccessCallback(success); - reply(); - } - - /** - * Adds an {@link ActionRow} to the reply and adds the passed {@link Component Components} to it. - * For buttonContainers, they must be defined in the same - * {@link com.github.kaktushose.jda.commands.annotations.interactions.Interaction Interaction} as the referring - * {@link SlashCommand Command}. - * - * @param components the {@link Component Components} to add - * @return the current instance for fluent interface - */ - default Replyable with(@NotNull Component... components) { - List items = new ArrayList<>(); - Context context = getContext(); - for (Component component : components) { - if (component instanceof Buttons) { - Buttons buttons = (Buttons) component; - buttons.buttonContainers().forEach(container -> { - String id = String.format("%s%s", context.getInteractionDefinition().getMethod().getDeclaringClass().getSimpleName(), container.name()); - context.getInteractionRegistry().getButtons() - .stream() - .filter(it -> it.getDefinitionId().equals(id)) - .findFirst() - .map(it -> { - Button jdaButton = it.toButton().withDisabled(!container.enabled()); - //only assign ids to non-link buttonContainers - if (jdaButton.getUrl() == null) { - jdaButton = jdaButton.withId(it.createCustomId(context.getRuntime().getRuntimeId())); - } - return jdaButton; - }).ifPresent(items::add); - }); - } - if (component instanceof SelectMenus) { - SelectMenus menus = (SelectMenus) component; - menus.selectMenuContainers().forEach(container -> { - String id = String.format("%s%s", context.getInteractionDefinition().getMethod().getDeclaringClass().getSimpleName(), container.getName()); - context.getInteractionRegistry().getSelectMenus() - .stream() - .filter(it -> it.getDefinitionId().equals(id)) - .findFirst().map(it -> it.toSelectMenu(context.getRuntime().getRuntimeId(), container.isEnabled())) - .ifPresent(items::add); - }); - } - } - - if (!items.isEmpty()) { - getReplyContext().getBuilder().addComponents(ActionRow.of(items)); - } - return this; - } - - /** - * Get the {@link Context} object. - * - * @return the registered {@link Context} object - */ - Context getContext(); - - /** - * Gets the {@link KeyValueStore} that is bound to this execution. - * - * @return the {@link KeyValueStore} bound to this execution - */ - default KeyValueStore kv() { - return getContext().getKeyValueStore(); - } - - /** - * Adds an {@link ActionRow} to the reply and adds the passed {@link Component Components} to it. - * The buttonContainers must be defined in the same - * {@link com.github.kaktushose.jda.commands.annotations.interactions.Interaction Interaction} as the referring - * {@link SlashCommand Command}. This will enable all buttonContainers. To add - * disabled buttonContainers, use {@link #with(Component...)}. - * - * @param buttons the id of the buttonContainers to add - * @return the current instance for fluent interface - */ - default Replyable withButtons(@NotNull String... buttons) { - with(Buttons.enabled(buttons)); - return this; - } - - /** - * Adds an {@link ActionRow} to the reply and adds the passed {@link Component Components} to it. - * The select menus must be defined in the same - * {@link com.github.kaktushose.jda.commands.annotations.interactions.Interaction Interaction} as the referring - * {@link SlashCommand Command}. This will enable all select menus. To add - * disabled select menus, use {@link #with(Component...)}. - * - * @param selectMenus the id of the selectMenus to add - * @return the current instance for fluent interface - */ - default Replyable withSelectMenus(@NotNull String... selectMenus) { - with(SelectMenus.enabled(selectMenus)); - return this; - } - - /** - * Whether to send ephemeral replies. - * - * @param ephemeral {@code true} if replies should be ephemeral - * @return the current instance for fluent interface - */ - default Replyable setEphemeral(boolean ephemeral) { - getReplyContext().setEphemeralReply(ephemeral); - return this; - } - - /** - * Sets the success callback consumer. - * - * @param success the callback consumer - * @return the current instance for fluent interface - */ - default Replyable setSuccessCallback(Consumer success) { - getReplyContext().setSuccessCallback(success); - return this; - } - - /** - * Sets the failure callback consumer. - * - * @param failure the callback consumer - * @return the current instance for fluent interface - */ - default Replyable setFailureCallback(Consumer failure) { - getReplyContext().setFailureCallback(failure); - return this; - } - - /** - * Whether this reply should edit the existing message or send a new one. The default value is - * {@code true}. - * - * @param edit {@code true} if this reply should edit the existing message - * @return the current instance for fluent interface - */ - default Replyable editReply(boolean edit) { - getReplyContext().setEditReply(edit); - return this; - } - - /** - * Whether this reply should keep all components that are attached to the previous message. The default value is - * {@code true}. - * - * @param keep {@code true} if this reply should keep all components - * @return the current instance for fluent interface - */ - default Replyable keepComponents(boolean keep) { - getReplyContext().setKeepComponents(keep); - return this; - } - - /** - * Sends the reply to Discord. Use this if you only want to edit components or if you have constructed the reply - * message by using {@link #getReplyContext()}. This method also allows to access the JDA RestAction - * consumer. - * - * @param success the JDA RestAction success consumer - */ - default void reply(Consumer success) { - setSuccessCallback(success); - reply(); - } - - /** - * Sends the reply to Discord. Use this if you only want to edit components or if you have constructed the reply - * message by using {@link #getReplyContext()}. This method also allows to access the JDA RestAction - * consumer. - * - * @param success the JDA RestAction success consumer - * @param failure the JDA RestAction failure consumer - */ - default void reply(Consumer success, Consumer failure) { - setSuccessCallback(success); - setFailureCallback(failure); - reply(); - } - - /** - * Removes all components from the last message that was sent. This will only work with - * {@link #editReply(boolean)} set to true. - */ - default void removeComponents() { - getReplyContext().removeComponents(); - } - - /** - * Sends the reply to Discord. Use this if you only want to edit components or if you have constructed the reply - * message by using {@link #getReplyContext()}. - */ - void reply(); - - /** - * Gets the {@link ReplyContext} to use. - * - * @return the {@link ReplyContext} - */ - ReplyContext getReplyContext(); - -} diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/reply/components/Buttons.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/reply/components/Buttons.java deleted file mode 100644 index ddc8e0be0..000000000 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/reply/components/Buttons.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.github.kaktushose.jda.commands.dispatching.reply.components; - -import com.github.kaktushose.jda.commands.dispatching.reply.Replyable; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; - -/** - * {@link Component} implementation for buttonContainers. This class can be used to add - * {@link com.github.kaktushose.jda.commands.annotations.interactions.Button Buttons} to messages while defining their - * state (enabled or disabled). - * - * @see Replyable#with(Component...) - * @since 2.3.0 - */ -public record Buttons(Collection buttonContainers) implements Component { - - public Buttons(Collection buttonContainers) { - this.buttonContainers = Collections.unmodifiableCollection(buttonContainers); - } - - /** - * Add the buttonContainers with the given ids to the reply message as enabled. - * - * @param buttons the id of the buttonContainers to add - * @return instance of this class used inside the - * {@link com.github.kaktushose.jda.commands.dispatching.reply.ReplyContext} - */ - public static Buttons enabled(String... buttons) { - return build(true, buttons); - } - - /** - * Add the buttonContainers with the given ids to the reply message as disabled. - * - * @param buttons the id of the buttonContainers to add - * @return instance of this class used inside the - * {@link com.github.kaktushose.jda.commands.dispatching.reply.ReplyContext} - */ - public static Buttons disabled(String... buttons) { - return build(false, buttons); - } - - private static Buttons build(boolean enabled, String... buttons) { - List result = new ArrayList<>(); - for (String button : buttons) { - result.add(new ButtonContainer(button, enabled)); - } - return new Buttons(result); - } - - /** - * Contains information about a single {@link com.github.kaktushose.jda.commands.annotations.interactions.Button Button}. - */ - public record ButtonContainer(String name, boolean enabled) { - } -} diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/reply/components/Component.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/reply/components/Component.java deleted file mode 100644 index 3e8ec4c33..000000000 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/reply/components/Component.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.github.kaktushose.jda.commands.dispatching.reply.components; - -import com.github.kaktushose.jda.commands.dispatching.reply.Replyable; - -/** - * Marker interface for components that can be added to messages. - * - * @see Replyable#with(Component...) ReplyAction#with(Component...) - * @see Buttons - * @see SelectMenus - * @since 2.3.0 - */ -public sealed interface Component permits Buttons, SelectMenus { -} diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/reply/components/SelectMenus.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/reply/components/SelectMenus.java deleted file mode 100644 index 1ffac30c4..000000000 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/reply/components/SelectMenus.java +++ /dev/null @@ -1,85 +0,0 @@ -package com.github.kaktushose.jda.commands.dispatching.reply.components; - -import com.github.kaktushose.jda.commands.dispatching.reply.Replyable; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; - -/** - * {@link Component} implementation for select menus. This class can be used to add - * {@link com.github.kaktushose.jda.commands.annotations.interactions.StringSelectMenu StringSelectMenus} or - * {@link com.github.kaktushose.jda.commands.annotations.interactions.EntitySelectMenu EntitySelectMenus} - * to messages while defining their state (enabled or disabled). - * - * @see Replyable#with(Component...) - * @since 2.3.0 - */ -public record SelectMenus(Collection selectMenuContainers) implements Component { - - public SelectMenus(Collection selectMenuContainers) { - this.selectMenuContainers = Collections.unmodifiableCollection(selectMenuContainers); - } - - /** - * Add the select menus with the given ids to the reply message as enabled. - * - * @param menus the id of the select menus to add - * @return instance of this class used inside the - * {@link com.github.kaktushose.jda.commands.dispatching.reply.ReplyContext} - */ - public static SelectMenus enabled(String... menus) { - return build(true, menus); - } - - /** - * Add the select menus with the given ids to the reply message as disabled. - * - * @param menus the id of the select menus to add - * @return instance of this class used inside the - * {@link com.github.kaktushose.jda.commands.dispatching.reply.ReplyContext} - */ - public static SelectMenus disabled(String... menus) { - return build(false, menus); - } - - private static SelectMenus build(boolean enabled, String... menus) { - List result = new ArrayList<>(); - for (String menu : menus) { - result.add(new SelectMenus.SelectMenuContainer(menu, enabled)); - } - return new SelectMenus(result); - } - - /** - * Contains information about a single select menu (either StringSelectMenu or EntitySelectMenu). - */ - public static class SelectMenuContainer { - private final String name; - private final boolean enabled; - - private SelectMenuContainer(String name, boolean enabled) { - this.name = name; - this.enabled = enabled; - } - - /** - * Gets the button id. - * - * @return the button id - */ - public String getName() { - return name; - } - - /** - * Whether the button is enabled or not. - * - * @return {@code true} if the button is enabled - */ - public boolean isEnabled() { - return enabled; - } - } -} diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/reply/components/package-info.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/reply/components/package-info.java deleted file mode 100644 index dd7a87984..000000000 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/reply/components/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * Components that can be added to messages. - */ -package com.github.kaktushose.jda.commands.dispatching.reply.components; diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/validation/Validator.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/validation/Validator.java index 7734c2a5f..0ff78da04 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/validation/Validator.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/validation/Validator.java @@ -1,6 +1,6 @@ package com.github.kaktushose.jda.commands.dispatching.validation; -import com.github.kaktushose.jda.commands.dispatching.interactions.Context; +import com.github.kaktushose.jda.commands.dispatching.context.InvocationContext; import org.jetbrains.annotations.NotNull; /** @@ -18,9 +18,9 @@ public interface Validator { * * @param argument the argument to validate * @param annotation the corresponding annotation - * @param context the corresponding {@link Context} + * @param context the corresponding {@link InvocationContext} * @return {@code true} if the argument passes the constraints */ - boolean apply(@NotNull Object argument, @NotNull Object annotation, @NotNull Context context); + boolean apply(@NotNull Object argument, @NotNull Object annotation, @NotNull InvocationContext context); } diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/validation/impl/MaximumValidator.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/validation/impl/MaximumValidator.java index 6e7902469..aa7db9d91 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/validation/impl/MaximumValidator.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/validation/impl/MaximumValidator.java @@ -1,7 +1,7 @@ package com.github.kaktushose.jda.commands.dispatching.validation.impl; import com.github.kaktushose.jda.commands.annotations.constraints.Max; -import com.github.kaktushose.jda.commands.dispatching.interactions.Context; +import com.github.kaktushose.jda.commands.dispatching.context.InvocationContext; import com.github.kaktushose.jda.commands.dispatching.validation.Validator; import org.jetbrains.annotations.NotNull; @@ -18,11 +18,11 @@ public class MaximumValidator implements Validator { * * @param argument the argument to validate * @param annotation the corresponding annotation - * @param context the corresponding {@link Context} + * @param context the corresponding {@link InvocationContext} * @return {@code true} if the argument is a number whose value is lower or equal to the specified maximum */ @Override - public boolean apply(@NotNull Object argument, @NotNull Object annotation, @NotNull Context context) { + public boolean apply(@NotNull Object argument, @NotNull Object annotation, @NotNull InvocationContext context) { Max max = (Max) annotation; return ((Number) argument).longValue() <= max.value(); } diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/validation/impl/MinimumValidator.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/validation/impl/MinimumValidator.java index 2f7982bb3..65146f562 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/validation/impl/MinimumValidator.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/validation/impl/MinimumValidator.java @@ -1,7 +1,7 @@ package com.github.kaktushose.jda.commands.dispatching.validation.impl; import com.github.kaktushose.jda.commands.annotations.constraints.Min; -import com.github.kaktushose.jda.commands.dispatching.interactions.Context; +import com.github.kaktushose.jda.commands.dispatching.context.InvocationContext; import com.github.kaktushose.jda.commands.dispatching.validation.Validator; import org.jetbrains.annotations.NotNull; @@ -19,11 +19,11 @@ public class MinimumValidator implements Validator { * * @param argument the argument to validate * @param annotation the corresponding annotation - * @param context the corresponding {@link Context} + * @param context the corresponding {@link InvocationContext} * @return {@code true} if the argument is a number whose value is greater or equal to the specified minimum */ @Override - public boolean apply(@NotNull Object argument, @NotNull Object annotation, @NotNull Context context) { + public boolean apply(@NotNull Object argument, @NotNull Object annotation, @NotNull InvocationContext context) { Min min = (Min) annotation; return ((Number) argument).longValue() >= min.value(); } diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/validation/impl/NotPermissionValidator.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/validation/impl/NotPermissionValidator.java index 541b0236e..fceaa5518 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/validation/impl/NotPermissionValidator.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/validation/impl/NotPermissionValidator.java @@ -1,7 +1,7 @@ package com.github.kaktushose.jda.commands.dispatching.validation.impl; import com.github.kaktushose.jda.commands.annotations.constraints.NotPerm; -import com.github.kaktushose.jda.commands.dispatching.interactions.Context; +import com.github.kaktushose.jda.commands.dispatching.context.InvocationContext; import com.github.kaktushose.jda.commands.dispatching.validation.Validator; import net.dv8tion.jda.api.Permission; import net.dv8tion.jda.api.entities.Member; @@ -24,12 +24,12 @@ public class NotPermissionValidator implements Validator { * * @param argument the argument to validate * @param annotation the corresponding annotation - * @param context the corresponding {@link Context} + * @param context the corresponding {@link InvocationContext} * @return {@code true} if the argument is a user or member that doesn't have the specified discord * permission */ @Override - public boolean apply(@NotNull Object argument, @NotNull Object annotation, @NotNull Context context) { + public boolean apply(@NotNull Object argument, @NotNull Object annotation, @NotNull InvocationContext context) { Set permissions = new HashSet<>(); NotPerm perm = (NotPerm) annotation; diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/validation/impl/NotRoleValidator.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/validation/impl/NotRoleValidator.java index 635f57d8a..cc2d53b3d 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/validation/impl/NotRoleValidator.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/validation/impl/NotRoleValidator.java @@ -2,7 +2,7 @@ import com.github.kaktushose.jda.commands.annotations.constraints.NotRole; import com.github.kaktushose.jda.commands.dispatching.adapter.impl.RoleAdapter; -import com.github.kaktushose.jda.commands.dispatching.interactions.Context; +import com.github.kaktushose.jda.commands.dispatching.context.InvocationContext; import com.github.kaktushose.jda.commands.dispatching.validation.Validator; import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Role; @@ -23,14 +23,14 @@ public class NotRoleValidator implements Validator { * * @param argument the argument to validate * @param annotation the corresponding annotation - * @param context the corresponding {@link Context} + * @param context the corresponding {@link InvocationContext} * @return {@code true} if the argument is a user or member that doesn't have the specified guild role */ @Override - public boolean apply(@NotNull Object argument, @NotNull Object annotation, @NotNull Context context) { + public boolean apply(@NotNull Object argument, @NotNull Object annotation, @NotNull InvocationContext context) { NotRole roleAnnotation = (NotRole) annotation; - Optional optional = new RoleAdapter().apply(roleAnnotation.value(), context); + Optional optional = new RoleAdapter().apply(roleAnnotation.value(), context.event()); Member member = (Member) argument; return optional.filter(role -> member.getRoles().contains(role)).isEmpty(); diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/validation/impl/NotUserValidator.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/validation/impl/NotUserValidator.java index 8f10a0af9..b26acaee2 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/validation/impl/NotUserValidator.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/validation/impl/NotUserValidator.java @@ -2,7 +2,7 @@ import com.github.kaktushose.jda.commands.annotations.constraints.NotUser; import com.github.kaktushose.jda.commands.dispatching.adapter.impl.MemberAdapter; -import com.github.kaktushose.jda.commands.dispatching.interactions.Context; +import com.github.kaktushose.jda.commands.dispatching.context.InvocationContext; import com.github.kaktushose.jda.commands.dispatching.validation.Validator; import net.dv8tion.jda.api.entities.Member; import org.jetbrains.annotations.NotNull; @@ -22,14 +22,14 @@ public class NotUserValidator implements Validator { * * @param argument the argument to validate * @param annotation the corresponding annotation - * @param context the corresponding {@link Context} + * @param context the corresponding {@link InvocationContext} * @return {@code true} if the argument isn't the specified user or member */ @Override - public boolean apply(@NotNull Object argument, @NotNull Object annotation, @NotNull Context context) { + public boolean apply(@NotNull Object argument, @NotNull Object annotation, @NotNull InvocationContext context) { Member member = (Member) argument; NotUser user = (NotUser) annotation; - Optional optional = new MemberAdapter().apply(user.value(), context); + Optional optional = new MemberAdapter().apply(user.value(), context.event()); return optional.filter(member::equals).isEmpty(); } } diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/validation/impl/PermissionValidator.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/validation/impl/PermissionValidator.java index b2bdef7ac..906d1b0c6 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/validation/impl/PermissionValidator.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/validation/impl/PermissionValidator.java @@ -1,7 +1,7 @@ package com.github.kaktushose.jda.commands.dispatching.validation.impl; import com.github.kaktushose.jda.commands.annotations.constraints.Perm; -import com.github.kaktushose.jda.commands.dispatching.interactions.Context; +import com.github.kaktushose.jda.commands.dispatching.context.InvocationContext; import com.github.kaktushose.jda.commands.dispatching.validation.Validator; import net.dv8tion.jda.api.Permission; import net.dv8tion.jda.api.entities.Member; @@ -24,12 +24,12 @@ public class PermissionValidator implements Validator { * * @param argument the argument to validate * @param annotation the corresponding annotation - * @param context the corresponding {@link Context} + * @param context the corresponding {@link InvocationContext} * @return {@code true} if the argument is a user or member that has the specified discord * permission */ @Override - public boolean apply(@NotNull Object argument, @NotNull Object annotation, @NotNull Context context) { + public boolean apply(@NotNull Object argument, @NotNull Object annotation, @NotNull InvocationContext context) { Set permissions = new HashSet<>(); Perm perm = (Perm) annotation; try { diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/validation/impl/RoleValidator.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/validation/impl/RoleValidator.java index 517bdac63..d18c74cc8 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/validation/impl/RoleValidator.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/validation/impl/RoleValidator.java @@ -1,7 +1,7 @@ package com.github.kaktushose.jda.commands.dispatching.validation.impl; import com.github.kaktushose.jda.commands.dispatching.adapter.impl.RoleAdapter; -import com.github.kaktushose.jda.commands.dispatching.interactions.Context; +import com.github.kaktushose.jda.commands.dispatching.context.InvocationContext; import com.github.kaktushose.jda.commands.dispatching.validation.Validator; import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Role; @@ -23,15 +23,15 @@ public class RoleValidator implements Validator { * * @param argument the argument to validate * @param annotation the corresponding annotation - * @param context the corresponding {@link Context} + * @param context the corresponding {@link InvocationContext} * @return {@code true} if the argument is a user or member that has the specified guild role */ @Override - public boolean apply(@NotNull Object argument, @NotNull Object annotation, @NotNull Context context) { + public boolean apply(@NotNull Object argument, @NotNull Object annotation, @NotNull InvocationContext context) { com.github.kaktushose.jda.commands.annotations.constraints.Role roleAnnotation = (com.github.kaktushose.jda.commands.annotations.constraints.Role) annotation; - Optional optional = new RoleAdapter().apply(roleAnnotation.value(), context); + Optional optional = new RoleAdapter().apply(roleAnnotation.value(), context.event()); Member member = (Member) argument; return optional.filter(role -> member.getRoles().contains(role)).isPresent(); diff --git a/src/main/java/com/github/kaktushose/jda/commands/dispatching/validation/impl/UserValidator.java b/src/main/java/com/github/kaktushose/jda/commands/dispatching/validation/impl/UserValidator.java index 2df8a190e..7976bc50a 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/dispatching/validation/impl/UserValidator.java +++ b/src/main/java/com/github/kaktushose/jda/commands/dispatching/validation/impl/UserValidator.java @@ -2,7 +2,7 @@ import com.github.kaktushose.jda.commands.annotations.constraints.User; import com.github.kaktushose.jda.commands.dispatching.adapter.impl.MemberAdapter; -import com.github.kaktushose.jda.commands.dispatching.interactions.Context; +import com.github.kaktushose.jda.commands.dispatching.context.InvocationContext; import com.github.kaktushose.jda.commands.dispatching.validation.Validator; import net.dv8tion.jda.api.entities.Member; import org.jetbrains.annotations.NotNull; @@ -22,14 +22,14 @@ public class UserValidator implements Validator { * * @param argument the argument to validate * @param annotation the corresponding annotation - * @param context the corresponding {@link Context} + * @param context the corresponding {@link InvocationContext} * @return {@code true} if the argument is the specified user or member */ @Override - public boolean apply(@NotNull Object argument, @NotNull Object annotation, @NotNull Context context) { + public boolean apply(@NotNull Object argument, @NotNull Object annotation, @NotNull InvocationContext context) { Member member = (Member) argument; User user = (User) annotation; - Optional optional = new MemberAdapter().apply(user.value(), context); + Optional optional = new MemberAdapter().apply(user.value(), context.event()); return optional.filter(member::equals).isPresent(); } } diff --git a/src/main/java/com/github/kaktushose/jda/commands/data/EmbedCache.java b/src/main/java/com/github/kaktushose/jda/commands/embeds/EmbedCache.java similarity index 98% rename from src/main/java/com/github/kaktushose/jda/commands/data/EmbedCache.java rename to src/main/java/com/github/kaktushose/jda/commands/embeds/EmbedCache.java index 34c769c34..bdccce0be 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/data/EmbedCache.java +++ b/src/main/java/com/github/kaktushose/jda/commands/embeds/EmbedCache.java @@ -1,4 +1,4 @@ -package com.github.kaktushose.jda.commands.data; +package com.github.kaktushose.jda.commands.embeds; import com.google.gson.Gson; import com.google.gson.JsonIOException; diff --git a/src/main/java/com/github/kaktushose/jda/commands/data/EmbedDTO.java b/src/main/java/com/github/kaktushose/jda/commands/embeds/EmbedDTO.java similarity index 98% rename from src/main/java/com/github/kaktushose/jda/commands/data/EmbedDTO.java rename to src/main/java/com/github/kaktushose/jda/commands/embeds/EmbedDTO.java index f4b561366..a48aad66b 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/data/EmbedDTO.java +++ b/src/main/java/com/github/kaktushose/jda/commands/embeds/EmbedDTO.java @@ -1,4 +1,4 @@ -package com.github.kaktushose.jda.commands.data; +package com.github.kaktushose.jda.commands.embeds; import net.dv8tion.jda.api.EmbedBuilder; import net.dv8tion.jda.api.entities.MessageEmbed; @@ -6,6 +6,7 @@ import net.dv8tion.jda.api.utils.messages.MessageCreateData; import java.awt.*; +import java.io.Serial; import java.io.Serializable; import java.time.ZonedDateTime; import java.util.Arrays; @@ -14,7 +15,7 @@ import java.util.regex.Pattern; /** - * This class is a DTO to serialize and deserialize JDA's embed objects to json. Checkout the discord docs to get + * This class is a DTO to serialize and deserialize JDAs embed objects to json. Checkout the discord docs to get * information about what each field does exactly. * *

    The json object can contain {@code {placeholders}} which can then be injected with values at runtime by calling @@ -27,6 +28,7 @@ */ public class EmbedDTO implements Serializable { + @Serial private static final long serialVersionUID = 0L; private String title; private String description; diff --git a/src/main/java/com/github/kaktushose/jda/commands/embeds/ErrorMessageFactory.java b/src/main/java/com/github/kaktushose/jda/commands/embeds/ErrorMessageFactory.java deleted file mode 100644 index ea3e7cae8..000000000 --- a/src/main/java/com/github/kaktushose/jda/commands/embeds/ErrorMessageFactory.java +++ /dev/null @@ -1,77 +0,0 @@ -package com.github.kaktushose.jda.commands.embeds; - -import com.github.kaktushose.jda.commands.dispatching.interactions.Context; -import com.github.kaktushose.jda.commands.dispatching.interactions.commands.SlashCommandContext; -import com.github.kaktushose.jda.commands.reflect.ConstraintDefinition; -import net.dv8tion.jda.api.entities.Message; -import net.dv8tion.jda.api.utils.messages.MessageCreateData; -import org.jetbrains.annotations.NotNull; - -/** - * Generic interface for factory classes that generate error messages. - * - * @see com.github.kaktushose.jda.commands.annotations.Implementation - * @see DefaultErrorMessageFactory - * @since 2.0.0 - */ -public interface ErrorMessageFactory { - - /** - * Gets a {@link MessageCreateData} to send when type adapting of the user input failed. - * - * @param context the corresponding {@link SlashCommandContext} - * @return a {@link MessageCreateData} to send when type adapting failed - */ - MessageCreateData getTypeAdaptingFailedMessage(@NotNull SlashCommandContext context); - - /** - * Gets a {@link MessageCreateData} to send when a user is missing permissions. - * - * @param context the corresponding {@link Context} - * @return a {@link MessageCreateData} to send when a user is missing permissions - */ - MessageCreateData getInsufficientPermissionsMessage(@NotNull Context context); - - /** - * Gets a {@link MessageCreateData} to send when a parameter constraint fails. - * - * @param context the corresponding {@link Context} - * @param constraint the corresponding {@link ConstraintDefinition} that failed - * @return a {@link MessageCreateData} to send when a parameter constraint fails - */ - MessageCreateData getConstraintFailedMessage(@NotNull Context context, @NotNull ConstraintDefinition constraint); - - /** - * Gets a {@link Message} to send when a command still has a cooldown. - * - * @param context the corresponding {@link Context} - * @return a {@link MessageCreateData} to send when a command still has a cooldown - */ - MessageCreateData getCooldownMessage(@NotNull Context context, long ms); - - /** - * Gets a {@link MessageCreateData} to send when the channel type isn't suitable for the command. - * - * @param context the corresponding {@link Context} - * @return a {@link MessageCreateData} to send when the channel type isn't suitable for the command - */ - MessageCreateData getWrongChannelTypeMessage(@NotNull Context context); - - /** - * Gets a {@link MessageCreateData} to send when the command execution failed. - * - * @param context the corresponding {@link Context} - * @param exception the Exception that made the command execution fail - * @return a {@link MessageCreateData} to send when the command execution failed - */ - MessageCreateData getCommandExecutionFailedMessage(@NotNull Context context, @NotNull Throwable exception); - - /** - * Gets a {@link MessageCreateData} to send when an incoming interaction already timed out. - * - * @param context the corresponding {@link Context} - * @return a {@link MessageCreateData} to send when an incoming interaction already timed out - */ - MessageCreateData getUnknownInteractionMessage(@NotNull Context context); - -} diff --git a/src/main/java/com/github/kaktushose/jda/commands/embeds/DefaultErrorMessageFactory.java b/src/main/java/com/github/kaktushose/jda/commands/embeds/error/DefaultErrorMessageFactory.java similarity index 82% rename from src/main/java/com/github/kaktushose/jda/commands/embeds/DefaultErrorMessageFactory.java rename to src/main/java/com/github/kaktushose/jda/commands/embeds/error/DefaultErrorMessageFactory.java index feed5a152..39033ac18 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/embeds/DefaultErrorMessageFactory.java +++ b/src/main/java/com/github/kaktushose/jda/commands/embeds/error/DefaultErrorMessageFactory.java @@ -1,20 +1,18 @@ -package com.github.kaktushose.jda.commands.embeds; +package com.github.kaktushose.jda.commands.embeds.error; -import com.github.kaktushose.jda.commands.dispatching.interactions.Context; -import com.github.kaktushose.jda.commands.dispatching.interactions.commands.CommandEvent; -import com.github.kaktushose.jda.commands.dispatching.interactions.commands.SlashCommandContext; +import com.github.kaktushose.jda.commands.dispatching.events.interactions.CommandEvent; import com.github.kaktushose.jda.commands.reflect.ConstraintDefinition; import com.github.kaktushose.jda.commands.reflect.interactions.GenericInteractionDefinition; import com.github.kaktushose.jda.commands.reflect.interactions.commands.SlashCommandDefinition; import net.dv8tion.jda.api.EmbedBuilder; import net.dv8tion.jda.api.entities.MessageEmbed; +import net.dv8tion.jda.api.events.interaction.GenericInteractionCreateEvent; import net.dv8tion.jda.api.utils.messages.MessageCreateBuilder; import net.dv8tion.jda.api.utils.messages.MessageCreateData; import org.jetbrains.annotations.NotNull; import java.awt.*; import java.text.SimpleDateFormat; -import java.util.Arrays; import java.util.List; import java.util.concurrent.TimeUnit; @@ -26,11 +24,13 @@ */ public class DefaultErrorMessageFactory implements ErrorMessageFactory { + @NotNull @Override - public MessageCreateData getTypeAdaptingFailedMessage(@NotNull SlashCommandContext context) { + public MessageCreateData getTypeAdaptingFailedMessage(@NotNull GenericInteractionCreateEvent event, + @NotNull GenericInteractionDefinition definition, + @NotNull List userInput) { StringBuilder sbExpected = new StringBuilder(); - SlashCommandDefinition command = context.getCommand(); - List arguments = Arrays.asList(context.getInput()); + SlashCommandDefinition command = (SlashCommandDefinition) definition; command.getParameters().forEach(parameter -> { if (CommandEvent.class.isAssignableFrom(parameter.type())) { @@ -45,7 +45,7 @@ public MessageCreateData getTypeAdaptingFailedMessage(@NotNull SlashCommandConte String expected = sbExpected.toString().isEmpty() ? " " : sbExpected.substring(0, sbExpected.length() - 2); StringBuilder sbActual = new StringBuilder(); - arguments.forEach(argument -> sbActual.append(argument).append(", ")); + userInput.forEach(argument -> sbActual.append(argument).append(", ")); String actual = sbActual.toString().isEmpty() ? " " : sbActual.substring(0, sbActual.length() - 2); MessageEmbed embed = new EmbedBuilder() @@ -59,10 +59,10 @@ public MessageCreateData getTypeAdaptingFailedMessage(@NotNull SlashCommandConte return new MessageCreateBuilder().setEmbeds(embed).build(); } + @NotNull @Override - public MessageCreateData getInsufficientPermissionsMessage(@NotNull Context context) { + public MessageCreateData getInsufficientPermissionsMessage(@NotNull GenericInteractionDefinition interaction) { StringBuilder sbPermissions = new StringBuilder(); - GenericInteractionDefinition interaction = context.getInteractionDefinition(); interaction.getPermissions().forEach(permission -> sbPermissions.append(permission).append(", ")); String permissions = sbPermissions.toString().isEmpty() ? "N/A" : sbPermissions.substring(0, sbPermissions.length() - 2); MessageEmbed embed = new EmbedBuilder() @@ -76,8 +76,9 @@ public MessageCreateData getInsufficientPermissionsMessage(@NotNull Context cont return new MessageCreateBuilder().setEmbeds(embed).build(); } + @NotNull @Override - public MessageCreateData getConstraintFailedMessage(@NotNull Context context, @NotNull ConstraintDefinition constraint) { + public MessageCreateData getConstraintFailedMessage(@NotNull ConstraintDefinition constraint) { return new MessageCreateBuilder().setEmbeds(new EmbedBuilder() .setColor(Color.ORANGE) .setTitle("Parameter Error") @@ -86,8 +87,9 @@ public MessageCreateData getConstraintFailedMessage(@NotNull Context context, @N ).build(); } + @NotNull @Override - public MessageCreateData getCooldownMessage(@NotNull Context context, long ms) { + public MessageCreateData getCooldownMessage(long ms) { long secs = TimeUnit.MILLISECONDS.toSeconds(ms); long seconds = secs % 60; long minutes = (secs / 60) % 60; @@ -98,13 +100,13 @@ public MessageCreateData getCooldownMessage(@NotNull Context context, long ms) { cooldown.append(hours).append(hours == 1 ? " hour" : " hours"); } if (minutes > 0) { - if (cooldown.length() > 0) { + if (!cooldown.isEmpty()) { cooldown.append(" "); } cooldown.append(minutes).append(minutes == 1 ? " minute" : " minutes"); } if (seconds > 0) { - if (cooldown.length() > 0) { + if (!cooldown.isEmpty()) { cooldown.append(" "); } cooldown.append(seconds).append(seconds == 1 ? " second" : " seconds"); @@ -117,8 +119,9 @@ public MessageCreateData getCooldownMessage(@NotNull Context context, long ms) { ).build(); } + @NotNull @Override - public MessageCreateData getWrongChannelTypeMessage(@NotNull Context context) { + public MessageCreateData getWrongChannelTypeMessage() { return new MessageCreateBuilder().setEmbeds(new EmbedBuilder() .setColor(Color.RED) .setTitle("Wrong Channel Type") @@ -127,15 +130,16 @@ public MessageCreateData getWrongChannelTypeMessage(@NotNull Context context) { ).build(); } + @NotNull @Override - public MessageCreateData getCommandExecutionFailedMessage(@NotNull Context context, @NotNull Throwable exception) { + public MessageCreateData getCommandExecutionFailedMessage(@NotNull GenericInteractionCreateEvent event, @NotNull Throwable exception) { String error; error = String.format("```The user \"%s\" attempted to execute an \"%s\" interaction at %s, " + "but a \"%s\" occurred. " + "Please refer to the logs for further information.```", - context.getEvent().getUser().toString(), - context.getEvent().getInteraction().getType(), + event.getUser(), + event.getInteraction().getType(), new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(System.currentTimeMillis()), exception.getClass().getName() ); @@ -149,8 +153,9 @@ public MessageCreateData getCommandExecutionFailedMessage(@NotNull Context conte ).build(); } + @NotNull @Override - public MessageCreateData getUnknownInteractionMessage(@NotNull Context context) { + public MessageCreateData getTimedOutComponentMessage() { return new MessageCreateBuilder().setEmbeds(new EmbedBuilder() .setColor(Color.RED) .setTitle("Unknown Interaction") diff --git a/src/main/java/com/github/kaktushose/jda/commands/embeds/error/ErrorMessageFactory.java b/src/main/java/com/github/kaktushose/jda/commands/embeds/error/ErrorMessageFactory.java new file mode 100644 index 000000000..d765ffc59 --- /dev/null +++ b/src/main/java/com/github/kaktushose/jda/commands/embeds/error/ErrorMessageFactory.java @@ -0,0 +1,72 @@ +package com.github.kaktushose.jda.commands.embeds.error; + +import com.github.kaktushose.jda.commands.annotations.Implementation; +import com.github.kaktushose.jda.commands.reflect.ConstraintDefinition; +import com.github.kaktushose.jda.commands.reflect.interactions.GenericInteractionDefinition; +import net.dv8tion.jda.api.entities.Message; +import net.dv8tion.jda.api.events.interaction.GenericInteractionCreateEvent; +import net.dv8tion.jda.api.utils.messages.MessageCreateData; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +/// Generic interface for factory classes that generate error messages. +/// +/// @see Implementation +/// @see DefaultErrorMessageFactory +/// @since 2.0.0 +public interface ErrorMessageFactory { + + /// Gets a [MessageCreateData] to send when type adapting of the user input failed. + /// + /// @param event the [GenericInteractionCreateEvent] that was attempted to type adapt + /// @param definition the underlying [GenericInteractionDefinition] + /// @param userInput the input the user provided + /// @return a [MessageCreateData] to send when type adapting failed + @NotNull + MessageCreateData getTypeAdaptingFailedMessage(@NotNull GenericInteractionCreateEvent event, + @NotNull GenericInteractionDefinition definition, + @NotNull List userInput); + + /// Gets a [MessageCreateData] to send when a user is missing permissions. + /// + /// @param definition the corresponding [GenericInteractionDefinition] + /// @return a [MessageCreateData] to send when a user is missing permissions + @NotNull + MessageCreateData getInsufficientPermissionsMessage(@NotNull GenericInteractionDefinition definition); + + /// Gets a [MessageCreateData] to send when a parameter constraint fails. + /// + /// @param constraint the corresponding [ConstraintDefinition] that failed + /// @return a [MessageCreateData] to send when a parameter constraint fails + @NotNull + MessageCreateData getConstraintFailedMessage(@NotNull ConstraintDefinition constraint); + + /// Gets a [Message] to send when a command still has a cooldown. + /// + /// @param ms the remaining cooldown in milliseconds + /// @return a [MessageCreateData] to send when a command still has a cooldown + @NotNull + MessageCreateData getCooldownMessage(long ms); + + /// Gets a [MessageCreateData] to send when the channel type isn't suitable for the command. + /// + /// @return a [MessageCreateData] to send when the channel type isn't suitable for the command + @NotNull + MessageCreateData getWrongChannelTypeMessage(); + + /// Gets a [MessageCreateData] to send when the command execution failed. + /// + /// @param event the corresponding [GenericInteractionCreateEvent] + /// @param exception the [Throwable] that made the command execution fail + /// @return a [MessageCreateData] to send when the command execution failed + @NotNull + MessageCreateData getCommandExecutionFailedMessage(@NotNull GenericInteractionCreateEvent event, @NotNull Throwable exception); + + /// Gets a [MessageCreateData] to send when an incoming component interaction already timed out. + /// + /// @return a [MessageCreateData] to send when an incoming component interaction already timed out + @NotNull + MessageCreateData getTimedOutComponentMessage(); + +} diff --git a/src/main/java/com/github/kaktushose/jda/commands/embeds/JsonErrorMessageFactory.java b/src/main/java/com/github/kaktushose/jda/commands/embeds/error/JsonErrorMessageFactory.java similarity index 72% rename from src/main/java/com/github/kaktushose/jda/commands/embeds/JsonErrorMessageFactory.java rename to src/main/java/com/github/kaktushose/jda/commands/embeds/error/JsonErrorMessageFactory.java index 94964f3d9..fb0550194 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/embeds/JsonErrorMessageFactory.java +++ b/src/main/java/com/github/kaktushose/jda/commands/embeds/error/JsonErrorMessageFactory.java @@ -1,17 +1,15 @@ -package com.github.kaktushose.jda.commands.embeds; +package com.github.kaktushose.jda.commands.embeds.error; -import com.github.kaktushose.jda.commands.data.EmbedCache; -import com.github.kaktushose.jda.commands.dispatching.interactions.Context; -import com.github.kaktushose.jda.commands.dispatching.interactions.commands.CommandEvent; -import com.github.kaktushose.jda.commands.dispatching.interactions.commands.SlashCommandContext; +import com.github.kaktushose.jda.commands.dispatching.events.interactions.CommandEvent; +import com.github.kaktushose.jda.commands.embeds.EmbedCache; import com.github.kaktushose.jda.commands.reflect.ConstraintDefinition; import com.github.kaktushose.jda.commands.reflect.interactions.GenericInteractionDefinition; import com.github.kaktushose.jda.commands.reflect.interactions.commands.SlashCommandDefinition; +import net.dv8tion.jda.api.events.interaction.GenericInteractionCreateEvent; import net.dv8tion.jda.api.utils.messages.MessageCreateData; import org.jetbrains.annotations.NotNull; import java.text.SimpleDateFormat; -import java.util.Arrays; import java.util.List; import java.util.concurrent.TimeUnit; @@ -30,15 +28,17 @@ public JsonErrorMessageFactory(EmbedCache embedCache) { this.embedCache = embedCache; } + @NotNull @Override - public MessageCreateData getTypeAdaptingFailedMessage(@NotNull SlashCommandContext context) { + public MessageCreateData getTypeAdaptingFailedMessage(@NotNull GenericInteractionCreateEvent event, + @NotNull GenericInteractionDefinition definition, + @NotNull List userInput) { if (!embedCache.containsEmbed("typeAdaptingFailed")) { - return super.getTypeAdaptingFailedMessage(context); + return super.getTypeAdaptingFailedMessage(event, definition, userInput); } StringBuilder sbExpected = new StringBuilder(); - SlashCommandDefinition command = context.getCommand(); - List arguments = Arrays.asList(context.getInput()); + SlashCommandDefinition command = (SlashCommandDefinition) definition; command.getParameters().forEach(parameter -> { if (CommandEvent.class.isAssignableFrom(parameter.type())) { @@ -53,7 +53,7 @@ public MessageCreateData getTypeAdaptingFailedMessage(@NotNull SlashCommandConte String expected = sbExpected.toString().isEmpty() ? " " : sbExpected.substring(0, sbExpected.length() - 2); StringBuilder sbActual = new StringBuilder(); - arguments.forEach(argument -> sbActual.append(argument).append(", ")); + userInput.forEach(argument -> sbActual.append(argument).append(", ")); String actual = sbActual.toString().isEmpty() ? " " : sbActual.substring(0, sbActual.length() - 2); return embedCache.getEmbed("typeAdaptingFailed") @@ -63,13 +63,13 @@ public MessageCreateData getTypeAdaptingFailedMessage(@NotNull SlashCommandConte .toMessageCreateData(); } + @NotNull @Override - public MessageCreateData getInsufficientPermissionsMessage(@NotNull Context context) { + public MessageCreateData getInsufficientPermissionsMessage(@NotNull GenericInteractionDefinition interaction) { if (!embedCache.containsEmbed("insufficientPermissions")) { - return super.getInsufficientPermissionsMessage(context); + return super.getInsufficientPermissionsMessage(interaction); } - GenericInteractionDefinition interaction = context.getInteractionDefinition(); StringBuilder sbPermissions = new StringBuilder(); interaction.getPermissions().forEach(permission -> sbPermissions.append(permission).append(", ")); String permissions = sbPermissions.toString().isEmpty() ? "N/A" : sbPermissions.substring(0, sbPermissions.length() - 2); @@ -80,20 +80,22 @@ public MessageCreateData getInsufficientPermissionsMessage(@NotNull Context cont .toMessageCreateData(); } + @NotNull @Override - public MessageCreateData getConstraintFailedMessage(@NotNull Context context, @NotNull ConstraintDefinition constraint) { + public MessageCreateData getConstraintFailedMessage(@NotNull ConstraintDefinition constraint) { if (!embedCache.containsEmbed("constraintFailed")) { - return super.getConstraintFailedMessage(context, constraint); + return super.getConstraintFailedMessage(constraint); } return embedCache.getEmbed("constraintFailed") .injectValue("message", constraint.message()) .toMessageCreateData(); } + @NotNull @Override - public MessageCreateData getCooldownMessage(@NotNull Context context, long ms) { + public MessageCreateData getCooldownMessage(long ms) { if (!embedCache.containsEmbed("cooldown")) { - return super.getCooldownMessage(context, ms); + return super.getCooldownMessage(ms); } long seconds = TimeUnit.MILLISECONDS.toSeconds(ms); long s = seconds % 60; @@ -106,24 +108,26 @@ public MessageCreateData getCooldownMessage(@NotNull Context context, long ms) { .toMessageCreateData(); } + @NotNull @Override - public MessageCreateData getWrongChannelTypeMessage(@NotNull Context context) { + public MessageCreateData getWrongChannelTypeMessage() { if (!embedCache.containsEmbed("wrongChannel")) { - return super.getWrongChannelTypeMessage(context); + return super.getWrongChannelTypeMessage(); } return embedCache.getEmbed("wrongChannel").toMessageCreateData(); } + @NotNull @Override - public MessageCreateData getCommandExecutionFailedMessage(@NotNull Context context, @NotNull Throwable exception) { + public MessageCreateData getCommandExecutionFailedMessage(@NotNull GenericInteractionCreateEvent event, @NotNull Throwable exception) { if (!embedCache.containsEmbed("executionFailed")) { - return super.getCommandExecutionFailedMessage(context, exception); + return super.getCommandExecutionFailedMessage(event, exception); } String error = String.format("```The user \"%s\" attempted to execute an \"%s\" interaction at %s, " + "but a \"%s\" occurred. " + "Please refer to the logs for further information.```", - context.getEvent().getUser().toString(), - context.getEvent().getInteraction().getType(), + event.getUser(), + event.getInteraction().getType(), new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(System.currentTimeMillis()), exception.getClass().getName() ); @@ -132,10 +136,11 @@ public MessageCreateData getCommandExecutionFailedMessage(@NotNull Context conte .toMessageCreateData(); } + @NotNull @Override - public MessageCreateData getUnknownInteractionMessage(@NotNull Context context) { + public MessageCreateData getTimedOutComponentMessage() { if (!embedCache.containsEmbed("unknownInteraction")) { - return super.getUnknownInteractionMessage(context); + return super.getTimedOutComponentMessage(); } return embedCache.getEmbed("unknownInteraction").toMessageCreateData(); } diff --git a/src/main/java/com/github/kaktushose/jda/commands/embeds/error/package-info.java b/src/main/java/com/github/kaktushose/jda/commands/embeds/error/package-info.java new file mode 100644 index 000000000..f073afe16 --- /dev/null +++ b/src/main/java/com/github/kaktushose/jda/commands/embeds/error/package-info.java @@ -0,0 +1,4 @@ +/** + * Factory classes for generating error embeds. + */ +package com.github.kaktushose.jda.commands.embeds.error; diff --git a/src/main/java/com/github/kaktushose/jda/commands/embeds/package-info.java b/src/main/java/com/github/kaktushose/jda/commands/embeds/package-info.java index 3fd364b2e..2236b3ade 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/embeds/package-info.java +++ b/src/main/java/com/github/kaktushose/jda/commands/embeds/package-info.java @@ -1,4 +1,2 @@ -/** - * Factory classes for generating error embeds. - */ -package com.github.kaktushose.jda.commands.embeds; +/// Helper and factory classes for handling embeds. +package com.github.kaktushose.jda.commands.embeds; \ No newline at end of file diff --git a/src/main/java/com/github/kaktushose/jda/commands/internal/Helpers.java b/src/main/java/com/github/kaktushose/jda/commands/internal/Helpers.java new file mode 100644 index 000000000..9145cc7c2 --- /dev/null +++ b/src/main/java/com/github/kaktushose/jda/commands/internal/Helpers.java @@ -0,0 +1,139 @@ +package com.github.kaktushose.jda.commands.internal; + +import com.github.kaktushose.jda.commands.annotations.interactions.Permissions; +import com.github.kaktushose.jda.commands.annotations.interactions.ReplyConfig; +import com.github.kaktushose.jda.commands.reflect.MethodBuildContext; +import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.entities.channel.middleman.GuildChannel; +import net.dv8tion.jda.api.events.interaction.GenericInteractionCreateEvent; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; + +/// Collection of helper methods that are used inside the framework. +/// +/// @since 4.0.0 +@ApiStatus.Internal +public final class Helpers { + + private static final Logger log = LoggerFactory.getLogger(Helpers.class); + + /// Sanitizes a String containing a raw mention. This will remove all markdown characters namely + /// _`<`, `@`, `#`, `&`, `!`, `>`_. + /// For instance: `<@!393843637437464588>` gets sanitized to `393843637437464588`. + /// + /// @param mention the raw String to sanitize + /// @return the sanitized String + @NotNull + public static String sanitizeMention(@NotNull String mention) { + if (mention.matches("<[@#][&!]?([0-9]{4,})>")) { + return mention.replaceAll("<[@#][&!]?", "").replace(">", ""); + } + return mention; + } + + /// Attempts to resolve a [GuildChannel] based on user input. + /// + /// @param raw the String the [GuildChannel] should be resolved from + /// @param event the corresponding [GenericInteractionCreateEvent] + /// @return an [Optional] holding the resolved [GuildChannel] or an empty [Optional] if the resolving failed + @NotNull + public static Optional resolveGuildChannel(@NotNull String raw, @NotNull GenericInteractionCreateEvent event) { + GuildChannel guildChannel; + raw = sanitizeMention(raw); + + Guild guild = event.getGuild(); + if (guild == null) { + return Optional.empty(); + } + if (raw.matches("\\d+")) { + guildChannel = guild.getGuildChannelById(raw); + } else { + String finalRaw = raw; + guildChannel = guild.getChannels().stream().filter(it -> it.getName().equalsIgnoreCase(finalRaw)) + .findFirst().orElse(null); + } + return Optional.ofNullable(guildChannel); + } + + /// Extracts the permissions from a [MethodBuildContext]. This combines the permissions of the method and the class. + /// + /// @param context the [MethodBuildContext] to extract the permissions from + /// @return a possibly-empty set of all permissions + @NotNull + public static Set permissions(@NotNull MethodBuildContext context) { + Permissions permission = context.method().getAnnotation(Permissions.class); + + if (permission != null) { + HashSet mergedPermissions = new HashSet<>(context.permissions()); + mergedPermissions.addAll(Set.of(permission.value())); + return Collections.unmodifiableSet(mergedPermissions); + } + return context.permissions(); + } + + /// Constructs the [com.github.kaktushose.jda.commands.reflect.interactions.ReplyConfig ReplyConfig] based on the + /// passed [Method]. + /// + /// This will first attempt to use the [ReplyConfig] annotation of the method and then of the class. If neither is + /// present will fall back to the + /// [com.github.kaktushose.jda.commands.dispatching.reply.GlobalReplyConfig GlobalReplyConfig]. + /// + /// @param method the [Method] to use + /// @return the [com.github.kaktushose.jda.commands.reflect.interactions.ReplyConfig ReplyConfig] + @NotNull + public static com.github.kaktushose.jda.commands.reflect.interactions.ReplyConfig replyConfig(@NotNull Method method) { + var global = method.getDeclaringClass().getAnnotation(ReplyConfig.class); + var local = method.getAnnotation(ReplyConfig.class); + + if (global == null && local == null) + return new com.github.kaktushose.jda.commands.reflect.interactions.ReplyConfig(); + if (local == null) + return new com.github.kaktushose.jda.commands.reflect.interactions.ReplyConfig(global); + + return new com.github.kaktushose.jda.commands.reflect.interactions.ReplyConfig(local); + } + + /// Checks if the given parameter is present at the [Method] at the given index. + /// + /// @param method the [Method] to check + /// @param index the index the parameter is expected to be at + /// @param type the type of the parameter + /// @return `true` if the parameter is present + public static boolean isIncorrectParameterType(@NotNull Method method, int index, @NotNull Class type) { + if (!type.isAssignableFrom(method.getParameters()[index].getType())) { + log.error("An error has occurred! Skipping Interaction {}.{}:", + method.getDeclaringClass().getSimpleName(), + method.getName(), + new IllegalArgumentException(String.format("%d. parameter must be of type %s", index + 1, type.getSimpleName()))); + return true; + } + return false; + } + + /// Checks if a [Method] has the given parameter count. + /// + /// @param count the parameter count + /// @return `true` if the method has the given parameter count + public static boolean isIncorrectParameterAmount(@NotNull Method method, int count) { + if (method.getParameters().length != count) { + log.error("An error has occurred! Skipping Interaction {}.{}:", + method.getDeclaringClass().getSimpleName(), + method.getName(), + new IllegalArgumentException(String.format( + "Invalid amount of parameters!. Expected: %d Actual: %d", + count, + method.getParameters().length + ))); + return true; + } + return false; + } +} diff --git a/src/main/java/com/github/kaktushose/jda/commands/JDAContext.java b/src/main/java/com/github/kaktushose/jda/commands/internal/JDAContext.java similarity index 98% rename from src/main/java/com/github/kaktushose/jda/commands/JDAContext.java rename to src/main/java/com/github/kaktushose/jda/commands/internal/JDAContext.java index 612ccf373..145a57e7e 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/JDAContext.java +++ b/src/main/java/com/github/kaktushose/jda/commands/internal/JDAContext.java @@ -1,4 +1,4 @@ -package com.github.kaktushose.jda.commands; +package com.github.kaktushose.jda.commands.internal; import net.dv8tion.jda.api.JDA; import net.dv8tion.jda.api.entities.Guild; diff --git a/src/main/java/com/github/kaktushose/jda/commands/data/CommandTree.java b/src/main/java/com/github/kaktushose/jda/commands/internal/register/CommandTree.java similarity index 96% rename from src/main/java/com/github/kaktushose/jda/commands/data/CommandTree.java rename to src/main/java/com/github/kaktushose/jda/commands/internal/register/CommandTree.java index e82ed4a24..cd492d494 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/data/CommandTree.java +++ b/src/main/java/com/github/kaktushose/jda/commands/internal/register/CommandTree.java @@ -1,7 +1,8 @@ -package com.github.kaktushose.jda.commands.data; +package com.github.kaktushose.jda.commands.internal.register; import com.github.kaktushose.jda.commands.reflect.interactions.commands.SlashCommandDefinition; import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData; +import org.jetbrains.annotations.ApiStatus; import java.util.Collection; import java.util.List; @@ -14,6 +15,7 @@ * @see Discord Subcommands and Subcommand Groups Documentation * @since 2.3.0 */ +@ApiStatus.Internal public record CommandTree( TreeNode root ) { diff --git a/src/main/java/com/github/kaktushose/jda/commands/SlashCommandUpdater.java b/src/main/java/com/github/kaktushose/jda/commands/internal/register/SlashCommandUpdater.java similarity index 96% rename from src/main/java/com/github/kaktushose/jda/commands/SlashCommandUpdater.java rename to src/main/java/com/github/kaktushose/jda/commands/internal/register/SlashCommandUpdater.java index b3be2c4b2..2efe3fc4b 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/SlashCommandUpdater.java +++ b/src/main/java/com/github/kaktushose/jda/commands/internal/register/SlashCommandUpdater.java @@ -1,7 +1,7 @@ -package com.github.kaktushose.jda.commands; +package com.github.kaktushose.jda.commands.internal.register; import com.github.kaktushose.jda.commands.annotations.interactions.SlashCommand; -import com.github.kaktushose.jda.commands.data.CommandTree; +import com.github.kaktushose.jda.commands.internal.JDAContext; import com.github.kaktushose.jda.commands.reflect.InteractionRegistry; import com.github.kaktushose.jda.commands.reflect.interactions.commands.GenericCommandDefinition; import com.github.kaktushose.jda.commands.reflect.interactions.commands.SlashCommandDefinition; @@ -10,6 +10,7 @@ import net.dv8tion.jda.api.interactions.commands.Command; import net.dv8tion.jda.api.interactions.commands.build.CommandData; import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData; +import org.jetbrains.annotations.ApiStatus; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -23,6 +24,7 @@ * @see CommandTree * @since 2.3.0 */ +@ApiStatus.Internal public class SlashCommandUpdater { private static final Logger log = LoggerFactory.getLogger(SlashCommandUpdater.class); diff --git a/src/main/java/com/github/kaktushose/jda/commands/data/TreeNode.java b/src/main/java/com/github/kaktushose/jda/commands/internal/register/TreeNode.java similarity index 97% rename from src/main/java/com/github/kaktushose/jda/commands/data/TreeNode.java rename to src/main/java/com/github/kaktushose/jda/commands/internal/register/TreeNode.java index 7fa68c38b..eae95bb9b 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/data/TreeNode.java +++ b/src/main/java/com/github/kaktushose/jda/commands/internal/register/TreeNode.java @@ -1,6 +1,5 @@ -package com.github.kaktushose.jda.commands.data; +package com.github.kaktushose.jda.commands.internal.register; -import com.github.kaktushose.jda.commands.SlashCommandUpdater; import com.github.kaktushose.jda.commands.reflect.interactions.commands.SlashCommandDefinition; import net.dv8tion.jda.api.Permission; import net.dv8tion.jda.api.interactions.commands.DefaultMemberPermissions; @@ -8,6 +7,7 @@ import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData; import net.dv8tion.jda.api.interactions.commands.build.SubcommandGroupData; import net.dv8tion.jda.api.interactions.commands.localization.LocalizationFunction; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; @@ -21,6 +21,7 @@ * @see CommandTree * @since 2.3.0 */ +@ApiStatus.Internal public record TreeNode( String name, SlashCommandDefinition command, @@ -181,7 +182,7 @@ private void toCommandData(Collection commands) { private SlashCommandData createRootCommand(String name, List children) { SlashCommandData result = Commands.slash(name, "empty description"); List subCommands = unwrapDefinitions(children); - LocalizationFunction function = subCommands.get(0).getLocalizationFunction(); + LocalizationFunction function = subCommands.getFirst().getLocalizationFunction(); boolean isNSFW = false; boolean isGuildOnly = false; diff --git a/src/main/java/com/github/kaktushose/jda/commands/permissions/DefaultPermissionsProvider.java b/src/main/java/com/github/kaktushose/jda/commands/permissions/DefaultPermissionsProvider.java index 5e4e80be9..45240803d 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/permissions/DefaultPermissionsProvider.java +++ b/src/main/java/com/github/kaktushose/jda/commands/permissions/DefaultPermissionsProvider.java @@ -1,6 +1,6 @@ package com.github.kaktushose.jda.commands.permissions; -import com.github.kaktushose.jda.commands.dispatching.interactions.Context; +import com.github.kaktushose.jda.commands.dispatching.context.InvocationContext; import net.dv8tion.jda.api.Permission; import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.User; @@ -10,32 +10,37 @@ import java.util.Arrays; -/** - * Default implementation of {@link PermissionsProvider} with the following behaviour: - *

      - *
    • {@link PermissionsProvider#hasPermission(User, Context)} will always return {@code true}
    • - *
    • - * {@link PermissionsProvider#hasPermission(Member, Context)} will check against the default Discord permissions. More - * formally, this method will work with any permission provided by {@link Permission#values()}, ignoring the - * case. Any other permission String will be ignored. - *
    • - *
    - * - * @see PermissionsProvider - * @since 2.0.0 - */ +/// Default implementation of [PermissionsProvider] with the following behaviour: +/// +/// - [#hasPermission(User,InvocationContext)] will always return `true` +/// - +/// [#hasPermission(Member,InvocationContext)] will check against the default Discord permissions. More +/// formally, this method will work with any permission provided by [Permission#values()], ignoring the +/// case. Any other permission String will be ignored. +/// +/// +/// +/// @see PermissionsProvider +/// @since 2.0.0 public class DefaultPermissionsProvider implements PermissionsProvider { private static final Logger log = LoggerFactory.getLogger(DefaultPermissionsProvider.class); + /// Doesn't perform checks and will always return `true`. + /// + /// @return always `true` @Override - public boolean hasPermission(@NotNull User user, @NotNull Context context) { + public boolean hasPermission(@NotNull User user, @NotNull InvocationContext context) { return true; } + /// Checks against the default Discord permissions. + /// + /// More formally, this method will work with any permission provided by [Permission#values()], case-insensitive. + /// Any other permission String will be ignored. @Override - public boolean hasPermission(@NotNull Member member, @NotNull Context context) { - for (String s : context.getInteractionDefinition().getPermissions()) { + public boolean hasPermission(@NotNull Member member, @NotNull InvocationContext context) { + for (String s : context.definition().getPermissions()) { // not a discord perm, continue if (Arrays.stream(Permission.values()).noneMatch(p -> p.name().equalsIgnoreCase(s))) { continue; diff --git a/src/main/java/com/github/kaktushose/jda/commands/permissions/PermissionsProvider.java b/src/main/java/com/github/kaktushose/jda/commands/permissions/PermissionsProvider.java index 54cd209b1..8a4f9ea44 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/permissions/PermissionsProvider.java +++ b/src/main/java/com/github/kaktushose/jda/commands/permissions/PermissionsProvider.java @@ -1,39 +1,33 @@ package com.github.kaktushose.jda.commands.permissions; import com.github.kaktushose.jda.commands.annotations.interactions.Permissions; -import com.github.kaktushose.jda.commands.dispatching.interactions.Context; +import com.github.kaktushose.jda.commands.dispatching.context.InvocationContext; import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.User; import org.jetbrains.annotations.NotNull; -/** - * Interface for performing permission checks. - * - * @see com.github.kaktushose.jda.commands.annotations.Implementation - * @see DefaultPermissionsProvider - * @see Permissions Permission - * @since 2.0.0 - */ +/// Interface for performing permission checks. +/// +/// @see com.github.kaktushose.jda.commands.annotations.Implementation +/// @see DefaultPermissionsProvider +/// @see Permissions Permission +/// @since 2.0.0 public interface PermissionsProvider { - /** - * Checks if a {@link User} has permissions. Compared to {@link #hasPermission(Member, Context)} this method will be - * called if the command gets executed in a non-guild context, where no member object is available. - * - * @param user the {@link User} to perform the check against - * @param context the corresponding {@link Context} - * @return {@code true} if the user has the permission to execute the command - * @see #hasPermission(Member, Context) - */ - boolean hasPermission(@NotNull User user, @NotNull Context context); + /// Checks if a [User] has permissions. Compared to [#hasPermission(Member, InvocationContext)] this method will be + /// called if the command gets executed in a non-guild context, where no member object is available. + /// + /// @param user the [User] to perform the check against + /// @param context the corresponding [InvocationContext] + /// @return `true` if the user has the permission to execute the command + /// @see #hasPermission(Member, InvocationContext) + boolean hasPermission(@NotNull User user, @NotNull InvocationContext context); - /** - * Checks if a {@link Member} has permissions. - * - * @param member the {@link Member} to perform the check against - * @param context the corresponding {@link Context} - * @return {@code true} if the user has the permission to execute the command - */ - boolean hasPermission(@NotNull Member member, @NotNull Context context); + /// Checks if a [Member] has permissions. + /// + /// @param member the [Member] to perform the check against + /// @param context the corresponding [InvocationContext] + /// @return `true` if the user has the permission to execute the command + boolean hasPermission(@NotNull Member member, @NotNull InvocationContext context); } diff --git a/src/main/java/com/github/kaktushose/jda/commands/reflect/ImplementationRegistry.java b/src/main/java/com/github/kaktushose/jda/commands/reflect/ImplementationRegistry.java index df806e3e7..ff3f4eaa9 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/reflect/ImplementationRegistry.java +++ b/src/main/java/com/github/kaktushose/jda/commands/reflect/ImplementationRegistry.java @@ -11,8 +11,8 @@ import com.github.kaktushose.jda.commands.dispatching.middleware.Priority; import com.github.kaktushose.jda.commands.dispatching.validation.Validator; import com.github.kaktushose.jda.commands.dispatching.validation.ValidatorRegistry; -import com.github.kaktushose.jda.commands.embeds.DefaultErrorMessageFactory; -import com.github.kaktushose.jda.commands.embeds.ErrorMessageFactory; +import com.github.kaktushose.jda.commands.embeds.error.DefaultErrorMessageFactory; +import com.github.kaktushose.jda.commands.embeds.error.ErrorMessageFactory; import com.github.kaktushose.jda.commands.permissions.DefaultPermissionsProvider; import com.github.kaktushose.jda.commands.permissions.PermissionsProvider; import com.github.kaktushose.jda.commands.scope.DefaultGuildScopeProvider; diff --git a/src/main/java/com/github/kaktushose/jda/commands/reflect/InteractionRegistry.java b/src/main/java/com/github/kaktushose/jda/commands/reflect/InteractionRegistry.java index b50f04e7c..aabad13a8 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/reflect/InteractionRegistry.java +++ b/src/main/java/com/github/kaktushose/jda/commands/reflect/InteractionRegistry.java @@ -24,6 +24,7 @@ import java.util.Collection; import java.util.HashSet; import java.util.Set; +import java.util.function.Predicate; /** * Central registry for all {@link SlashCommandDefinition CommandDefinitions}. @@ -94,6 +95,33 @@ public void index(@NotNull Class clazz, @NotNull String... packages) { definitions.size()); } + /** + * Attempts to find a {@link GenericInteractionDefinition} of type {@link T} based on the given {@link Predicate}. + * + * @param type the type of the {@link GenericInteractionDefinition} to find + * @param internalError {@code true} if the {@link GenericInteractionDefinition} must be found and not finding it + * indicates a framework bug + * @param predicate the {@link Predicate} used to find the {@link GenericInteractionDefinition} + * @param a subtype of {@link GenericInteractionDefinition} + * @return {@link T} + * @throws IllegalStateException if no {@link GenericInteractionDefinition} was found, although this mandatory + * should have been the case. This is a rare occasion and can be considered a + * framework bug + * @throws IllegalArgumentException if no {@link GenericInteractionDefinition} was found, because the + * {@link Predicate} didn't include any elements + */ + public T find(Class type, boolean internalError, Predicate predicate) { + return definitions.stream() + .filter(type::isInstance) + .map(type::cast) + .filter(predicate) + .findFirst() + .orElseThrow(() -> internalError + ? new IllegalStateException("No interaction found! Please report this error the the devs of jda-commands.") + : new IllegalArgumentException("No interaction found! Please check that the referenced interaction method exists.") + ); + } + /** * Gets a possibly-empty list of all {@link GenericCommandDefinition CommandDefinitions}. * diff --git a/src/main/java/com/github/kaktushose/jda/commands/reflect/MethodBuildContext.java b/src/main/java/com/github/kaktushose/jda/commands/reflect/MethodBuildContext.java index 0ca916293..f92c549bd 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/reflect/MethodBuildContext.java +++ b/src/main/java/com/github/kaktushose/jda/commands/reflect/MethodBuildContext.java @@ -3,13 +3,17 @@ import com.github.kaktushose.jda.commands.annotations.interactions.Interaction; import com.github.kaktushose.jda.commands.dispatching.validation.ValidatorRegistry; import com.github.kaktushose.jda.commands.reflect.interactions.AutoCompleteDefinition; +import com.github.kaktushose.jda.commands.reflect.interactions.GenericInteractionDefinition; import net.dv8tion.jda.api.interactions.commands.localization.LocalizationFunction; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import java.lang.reflect.Method; import java.util.Collection; import java.util.Set; +/// Holds all objects needed to create a [GenericInteractionDefinition]. +@ApiStatus.Internal public record MethodBuildContext( @NotNull ValidatorRegistry validatorRegistry, @NotNull LocalizationFunction localizationFunction, diff --git a/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/AutoCompleteDefinition.java b/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/AutoCompleteDefinition.java index 6566bcdc9..a994f97fa 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/AutoCompleteDefinition.java +++ b/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/AutoCompleteDefinition.java @@ -1,9 +1,9 @@ package com.github.kaktushose.jda.commands.reflect.interactions; -import com.github.kaktushose.jda.commands.Helpers; +import com.github.kaktushose.jda.commands.internal.Helpers; import com.github.kaktushose.jda.commands.annotations.interactions.AutoComplete; import com.github.kaktushose.jda.commands.annotations.interactions.Interaction; -import com.github.kaktushose.jda.commands.dispatching.interactions.autocomplete.AutoCompleteEvent; +import com.github.kaktushose.jda.commands.dispatching.events.interactions.AutoCompleteEvent; import org.jetbrains.annotations.NotNull; import java.lang.reflect.Method; diff --git a/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/CustomId.java b/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/CustomId.java index 977e0ef46..eb4e0dfb5 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/CustomId.java +++ b/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/CustomId.java @@ -1,21 +1,90 @@ package com.github.kaktushose.jda.commands.reflect.interactions; -/** - * Indicates that this class can create a component or modal which Discord requires a custom id for. - * - * @see 4.0.0 - */ -public interface CustomId { +import com.github.kaktushose.jda.commands.dispatching.internal.Runtime; +import com.github.kaktushose.jda.commands.reflect.interactions.components.GenericComponentDefinition; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +/// Indicates that this class can create a component or modal which Discord requires a custom id for. +/// +/// @implSpec the custom id has the following structure: `[prefix.runtimeId.definitionId]`. The prefix will always be `jdac`, +/// indicating that this id was created by jda-commands. The runtime id is a UUID or the String literal `independent`, +/// if the component or modal works runtime independent. The definition id is the hash value of the full class name and +/// method name combined: `(method.getDeclaringClass.getName() + method.getName()).hash()` +/// @see GenericComponentDefinition +/// @see ModalDefinition +/// @since 4.0.0 +@ApiStatus.Internal +public sealed interface CustomId permits GenericComponentDefinition, ModalDefinition { String PREFIX = "jdac"; - String CUSTOM_ID_REGEX = String.format("%s.%s", PREFIX, "[aA-zZ]+.s?-?\\d*"); + String BOUND_CUSTOM_ID_REGEX = "^jdac\\.[0-9a-fA-F-]{36}\\.-?\\d+$"; + String INDEPENDENT_CUSTOM_ID_REGEX = "^jdac\\.independent\\.-?\\d+$"; + + /// Extracts the runtime id from the passed custom id. + /// + /// @param customId the custom id to extract the runtime id from + /// @return the runtime id + @NotNull + static String runtimeId(@NotNull String customId) { + if (isIndependent(customId)) { + throw new IllegalArgumentException("Provided custom id is independent!"); + } + return getId(customId, 1); + } + + /// Extracts the definition id from the passed custom id. + /// + /// @param customId the definition id to extract the runtime id from + /// @return the runtime id + /// @throws IllegalArgumentException if the passed custom id is a runtime-independent id + @NotNull + static String definitionId(@NotNull String customId) { + return getId(customId, 2); + } + + /// Checks if the passed custom id is runtime-independent. + /// + /// @param customId the custom id to check + /// @return `true` if the custom id is runtime-independent + static boolean isIndependent(@NotNull String customId) { + return customId.matches(INDEPENDENT_CUSTOM_ID_REGEX); + } + + /// Checks if the passed custom id is runtime-bound. + /// + /// @param customId the custom id to check + /// @return `true` if the custom id is runtime-bound + static boolean isBound(@NotNull String customId) { + return customId.matches(BOUND_CUSTOM_ID_REGEX); + } + + /// Checks if the passed custom id conforms to the defined format of jda-commands. + /// + /// @param customId the custom id to check + /// @return `true` if the passed custom id conforms to the jda-commands format + static boolean isInvalid(@NotNull String customId) { + return !(isIndependent(customId) || isBound(customId)); + } + + private static String getId(String customId, int index) { + if (isInvalid(customId)) { + throw new IllegalArgumentException("Illegal id format!"); + } + return customId.split("\\.")[index]; + } - /** - * Gets the custom id for this component. - * - * @param runtimeId the runtimeId of this component execution - * @return the runtime id - */ - String createCustomId(String runtimeId); + /// Gets a custom id for this definition that is bound to the passed [Runtime] id. + /// + /// @param runtimeId the id of the [Runtime] + /// @return the custom id + @NotNull + String boundCustomId(@NotNull String runtimeId); + /// Gets a custom id for this definition that is runtime-independent. + /// + /// @return the custom id + /// @throws UnsupportedOperationException if the interaction this definition wraps doesn't support independent execution + @NotNull + String independentCustomId(); } diff --git a/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/EphemeralInteractionDefinition.java b/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/EphemeralInteractionDefinition.java index 0b26b1541..0106211b2 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/EphemeralInteractionDefinition.java +++ b/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/EphemeralInteractionDefinition.java @@ -13,11 +13,11 @@ */ public abstract sealed class EphemeralInteractionDefinition extends GenericInteractionDefinition permits ModalDefinition, GenericCommandDefinition, GenericComponentDefinition { - protected boolean ephemeral; + protected final ReplyConfig replyConfig; - protected EphemeralInteractionDefinition(Method method, Set permissions, boolean ephemeral) { + protected EphemeralInteractionDefinition(Method method, Set permissions, ReplyConfig replyConfig) { super(method, permissions); - this.ephemeral = ephemeral; + this.replyConfig = replyConfig; } /** @@ -25,17 +25,8 @@ protected EphemeralInteractionDefinition(Method method, Set permissions, * * @return {@code true} if replies should be ephemeral */ - public boolean isEphemeral() { - return ephemeral; - } - - /** - * Sets whether replies should be ephemeral. - * - * @param ephemeral {@code true} if replies should be ephemeral - */ - public void setEphemeral(boolean ephemeral) { - this.ephemeral = ephemeral; + public ReplyConfig replyConfig() { + return replyConfig; } } diff --git a/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/GenericInteractionDefinition.java b/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/GenericInteractionDefinition.java index 8f5d411be..b97dc5805 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/GenericInteractionDefinition.java +++ b/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/GenericInteractionDefinition.java @@ -1,11 +1,13 @@ package com.github.kaktushose.jda.commands.reflect.interactions; +import com.github.kaktushose.jda.commands.dispatching.context.InvocationContext; import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.util.SequencedCollection; import java.util.Set; /** @@ -22,11 +24,17 @@ public sealed abstract class GenericInteractionDefinition permits AutoCompleteDe protected final Set permissions; protected GenericInteractionDefinition(Method method, Set permissions) { - this.definitionId = String.format("%s%s", method.getDeclaringClass().getSimpleName(), method.getName()); + this.definitionId = String.valueOf((method.getDeclaringClass().getName() + method.getName()).hashCode()); this.method = method; this.permissions = permissions; } + public final void invoke(Object instance, InvocationContext invocation) throws InvocationTargetException, IllegalAccessException { + SequencedCollection arguments = invocation.arguments(); + + method.invoke(instance, arguments.toArray()); + } + /** * Returns the id of the interaction definition. * diff --git a/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/ModalDefinition.java b/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/ModalDefinition.java index 05b486136..9ffb95824 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/ModalDefinition.java +++ b/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/ModalDefinition.java @@ -1,10 +1,11 @@ package com.github.kaktushose.jda.commands.reflect.interactions; -import com.github.kaktushose.jda.commands.Helpers; +import com.github.kaktushose.jda.commands.internal.Helpers; import com.github.kaktushose.jda.commands.annotations.interactions.Interaction; import com.github.kaktushose.jda.commands.annotations.interactions.Modal; import com.github.kaktushose.jda.commands.annotations.interactions.Permissions; -import com.github.kaktushose.jda.commands.dispatching.interactions.modals.ModalEvent; +import com.github.kaktushose.jda.commands.dispatching.events.interactions.ModalEvent; +import com.github.kaktushose.jda.commands.dispatching.internal.Runtime; import com.github.kaktushose.jda.commands.reflect.TextInputDefinition; import net.dv8tion.jda.api.interactions.modals.Modal.Builder; import org.jetbrains.annotations.NotNull; @@ -24,8 +25,8 @@ public final class ModalDefinition extends EphemeralInteractionDefinition implem private final String title; private final List textInputs; - private ModalDefinition(Method method, Set permissions, boolean ephemeral, String title, List textInputs) { - super(method, permissions, ephemeral); + private ModalDefinition(Method method, Set permissions, ReplyConfig replyConfig, String title, List textInputs) { + super(method, permissions, replyConfig); this.title = title; this.textInputs = textInputs; } @@ -76,19 +77,19 @@ public static Optional build(@NotNull Method method) { Modal modal = method.getAnnotation(Modal.class); - return Optional.of(new ModalDefinition(method, permissions, modal.ephemeral(), modal.value(), textInputs)); + return Optional.of(new ModalDefinition(method, permissions, Helpers.replyConfig(method), modal.value(), textInputs)); } /** * Transforms this ModalDefinition to a {@link net.dv8tion.jda.api.interactions.modals.Modal Modal}. * * @param runtimeId the runtimeId of the - * {@link com.github.kaktushose.jda.commands.dispatching.RuntimeSupervisor.InteractionRuntime InteractionRuntime} + * {@link Runtime Runtime} * of this interaction execution * @return the transformed {@link net.dv8tion.jda.api.interactions.modals.Modal Modal} */ public net.dv8tion.jda.api.interactions.modals.Modal toModal(String runtimeId) { - Builder modal = net.dv8tion.jda.api.interactions.modals.Modal.create(createCustomId(runtimeId), title); + Builder modal = net.dv8tion.jda.api.interactions.modals.Modal.create(boundCustomId(runtimeId), title); textInputs.forEach(textInput -> modal.addActionRow(textInput.toTextInput())); @@ -119,13 +120,13 @@ public String getDisplayName() { } @Override - public String createCustomId(String runtimeId) { - return String.format("%s.%s%s.%s", - PREFIX, - method.getDeclaringClass().getSimpleName(), - method.getName(), - runtimeId - ); + public @NotNull String boundCustomId(@NotNull String runtimeId) { + return "%s.%s.%s".formatted(PREFIX, runtimeId, definitionId); + } + + @Override + public @NotNull String independentCustomId() { + throw new UnsupportedOperationException("Modals cannot be independent!"); } @Override @@ -133,7 +134,7 @@ public String toString() { return "ModalDefinition{" + "title='" + title + '\'' + ", textInputs=" + textInputs + - ", ephemeral=" + ephemeral + + ", replyConfig=" + replyConfig + ", id='" + definitionId + '\'' + ", method=" + method + ", permissions=" + permissions + diff --git a/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/ReplyConfig.java b/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/ReplyConfig.java new file mode 100644 index 000000000..0270f899c --- /dev/null +++ b/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/ReplyConfig.java @@ -0,0 +1,24 @@ +package com.github.kaktushose.jda.commands.reflect.interactions; + +import com.github.kaktushose.jda.commands.dispatching.reply.GlobalReplyConfig; +import org.jetbrains.annotations.NotNull; + +/// Stores the configuration values for sending replies. This acts as a representation of +/// [com.github.kaktushose.jda.commands.annotations.interactions.ReplyConfig]. +/// +/// @see [com.github.kaktushose.jda.commands.annotations.interactions.ReplyConfig] +/// @since 4.0.0 +public record ReplyConfig(boolean ephemeral, boolean keepComponents, boolean editReply) { + + /// Constructs a new ReplyConfig using the default values specified by [GlobalReplyConfig]. + public ReplyConfig() { + this(GlobalReplyConfig.ephemeral, GlobalReplyConfig.keepComponents, GlobalReplyConfig.editReply); + } + + /// Constructs a new ReplyConfig. + /// + /// @param replyConfig the [com.github.kaktushose.jda.commands.annotations.interactions.ReplyConfig] to represent + public ReplyConfig(@NotNull com.github.kaktushose.jda.commands.annotations.interactions.ReplyConfig replyConfig) { + this(replyConfig.ephemeral(), replyConfig.keepComponents(), replyConfig.editReply()); + } +} diff --git a/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/commands/ContextCommandDefinition.java b/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/commands/ContextCommandDefinition.java index fe7a41ce6..c486fad9d 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/commands/ContextCommandDefinition.java +++ b/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/commands/ContextCommandDefinition.java @@ -1,11 +1,15 @@ package com.github.kaktushose.jda.commands.reflect.interactions.commands; -import com.github.kaktushose.jda.commands.Helpers; +import com.github.kaktushose.jda.commands.internal.Helpers; import com.github.kaktushose.jda.commands.annotations.interactions.ContextCommand; import com.github.kaktushose.jda.commands.annotations.interactions.Interaction; import com.github.kaktushose.jda.commands.annotations.interactions.SlashCommand; +import com.github.kaktushose.jda.commands.dispatching.events.interactions.CommandEvent; import com.github.kaktushose.jda.commands.reflect.MethodBuildContext; +import com.github.kaktushose.jda.commands.reflect.interactions.ReplyConfig; import net.dv8tion.jda.api.Permission; +import net.dv8tion.jda.api.entities.Message; +import net.dv8tion.jda.api.entities.User; import net.dv8tion.jda.api.interactions.commands.Command; import net.dv8tion.jda.api.interactions.commands.DefaultMemberPermissions; import net.dv8tion.jda.api.interactions.commands.build.CommandData; @@ -28,7 +32,7 @@ public final class ContextCommandDefinition extends GenericCommandDefinition { public ContextCommandDefinition(Method method, - boolean ephemeral, + ReplyConfig replyConfig, String name, Set permissions, boolean isGuildOnly, @@ -37,7 +41,7 @@ public ContextCommandDefinition(Method method, Set enabledPermissions, SlashCommand.CommandScope scope, LocalizationFunction localizationFunction) { - super(method, ephemeral, name, permissions, isGuildOnly, isNSFW, commandType, enabledPermissions, scope, localizationFunction); + super(method, replyConfig, name, permissions, isGuildOnly, isNSFW, commandType, enabledPermissions, scope, localizationFunction); } public static Optional build(@NotNull MethodBuildContext context) { @@ -46,6 +50,17 @@ public static Optional build(@NotNull MethodBuildConte return Optional.empty(); } + if (Helpers.isIncorrectParameterAmount(method, 2)) { + return Optional.empty(); + } + if (Helpers.isIncorrectParameterType(method, 0, CommandEvent.class)) { + return Optional.empty(); + } + if (Helpers.isIncorrectParameterType(method, 1, User.class) && Helpers.isIncorrectParameterType(method, 1, Message.class)) { + return Optional.empty(); + } + + ContextCommand command = method.getAnnotation(ContextCommand.class); Set enabledFor = Arrays.stream(command.enabledFor()).collect(Collectors.toSet()); @@ -55,7 +70,7 @@ public static Optional build(@NotNull MethodBuildConte return Optional.of(new ContextCommandDefinition( method, - Helpers.ephemeral(context, command.ephemeral()), + Helpers.replyConfig(method), command.value(), Helpers.permissions(context), command.isGuildOnly(), @@ -90,7 +105,7 @@ public String toString() { ", enabledPermissions=" + enabledPermissions + ", scope=" + scope + ", localizationFunction=" + localizationFunction + - ", ephemeral=" + ephemeral + + ", replyConfig=" + replyConfig + '}'; } } diff --git a/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/commands/GenericCommandDefinition.java b/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/commands/GenericCommandDefinition.java index 2345e04e5..cf84968a3 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/commands/GenericCommandDefinition.java +++ b/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/commands/GenericCommandDefinition.java @@ -2,6 +2,7 @@ import com.github.kaktushose.jda.commands.annotations.interactions.SlashCommand; import com.github.kaktushose.jda.commands.reflect.interactions.EphemeralInteractionDefinition; +import com.github.kaktushose.jda.commands.reflect.interactions.ReplyConfig; import net.dv8tion.jda.api.Permission; import net.dv8tion.jda.api.interactions.commands.Command; import net.dv8tion.jda.api.interactions.commands.build.CommandData; @@ -18,7 +19,10 @@ * @see ContextCommandDefinition * @since 4.0.0 */ -public abstract sealed class GenericCommandDefinition extends EphemeralInteractionDefinition implements Comparable permits ContextCommandDefinition, SlashCommandDefinition { +public abstract sealed class GenericCommandDefinition + extends EphemeralInteractionDefinition + implements Comparable + permits ContextCommandDefinition, SlashCommandDefinition { protected final String name; protected final boolean isGuildOnly; @@ -29,7 +33,7 @@ public abstract sealed class GenericCommandDefinition extends EphemeralInteracti protected final LocalizationFunction localizationFunction; protected GenericCommandDefinition(Method method, - boolean ephemeral, + ReplyConfig replyConfig, String name, Set permissions, boolean isGuildOnly, @@ -38,7 +42,7 @@ protected GenericCommandDefinition(Method method, Set enabledPermissions, SlashCommand.CommandScope scope, LocalizationFunction localizationFunction) { - super(method, permissions, ephemeral); + super(method, permissions, replyConfig); this.name = name; this.isGuildOnly = isGuildOnly; this.isNSFW = isNSFW; @@ -130,7 +134,7 @@ public String toString() { ", enabledPermissions=" + enabledPermissions + ", scope=" + scope + ", localizationFunction=" + localizationFunction + - ", ephemeral=" + ephemeral + + ", replyConfig=" + replyConfig + '}'; } diff --git a/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/commands/SlashCommandDefinition.java b/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/commands/SlashCommandDefinition.java index 35f04a035..94f51cd0f 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/commands/SlashCommandDefinition.java +++ b/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/commands/SlashCommandDefinition.java @@ -1,14 +1,15 @@ package com.github.kaktushose.jda.commands.reflect.interactions.commands; -import com.github.kaktushose.jda.commands.Helpers; +import com.github.kaktushose.jda.commands.internal.Helpers; import com.github.kaktushose.jda.commands.annotations.interactions.Cooldown; import com.github.kaktushose.jda.commands.annotations.interactions.Interaction; import com.github.kaktushose.jda.commands.annotations.interactions.SlashCommand; -import com.github.kaktushose.jda.commands.dispatching.interactions.commands.CommandEvent; +import com.github.kaktushose.jda.commands.dispatching.events.interactions.CommandEvent; import com.github.kaktushose.jda.commands.reflect.CooldownDefinition; import com.github.kaktushose.jda.commands.reflect.MethodBuildContext; import com.github.kaktushose.jda.commands.reflect.ParameterDefinition; import com.github.kaktushose.jda.commands.reflect.interactions.AutoCompleteDefinition; +import com.github.kaktushose.jda.commands.reflect.interactions.ReplyConfig; import net.dv8tion.jda.api.Permission; import net.dv8tion.jda.api.interactions.commands.Command; import net.dv8tion.jda.api.interactions.commands.DefaultMemberPermissions; @@ -34,7 +35,7 @@ public final class SlashCommandDefinition extends GenericCommandDefinition { private final boolean isAutoComplete; public SlashCommandDefinition(Method method, - boolean ephemeral, + ReplyConfig replyConfig, String name, Set permissions, boolean isGuildOnly, @@ -47,7 +48,7 @@ public SlashCommandDefinition(Method method, List parameters, CooldownDefinition cooldown, boolean isAutoComplete) { - super(method, ephemeral, name, permissions, isGuildOnly, isNSFW, commandType, enabledPermissions, scope, localizationFunction); + super(method, replyConfig, name, permissions, isGuildOnly, isNSFW, commandType, enabledPermissions, scope, localizationFunction); this.description = description; this.parameters = parameters; this.cooldown = cooldown; @@ -120,7 +121,7 @@ public static Optional build(MethodBuildContext context) return Optional.of(new SlashCommandDefinition( method, - Helpers.ephemeral(context, command.ephemeral()), + Helpers.replyConfig(method), name, Helpers.permissions(context), command.isGuildOnly(), @@ -264,7 +265,7 @@ public String toString() { ", enabledPermissions=" + enabledPermissions + ", scope=" + scope + ", localizationFunction=" + localizationFunction + - ", ephemeral=" + ephemeral + + ", replyConfig=" + replyConfig + '}'; } } diff --git a/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/components/ButtonDefinition.java b/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/components/ButtonDefinition.java index b5b3e2911..08ebf3042 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/components/ButtonDefinition.java +++ b/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/components/ButtonDefinition.java @@ -1,10 +1,11 @@ package com.github.kaktushose.jda.commands.reflect.interactions.components; -import com.github.kaktushose.jda.commands.Helpers; +import com.github.kaktushose.jda.commands.internal.Helpers; import com.github.kaktushose.jda.commands.annotations.interactions.Button; import com.github.kaktushose.jda.commands.annotations.interactions.Interaction; -import com.github.kaktushose.jda.commands.dispatching.interactions.components.ComponentEvent; +import com.github.kaktushose.jda.commands.dispatching.events.interactions.ComponentEvent; import com.github.kaktushose.jda.commands.reflect.MethodBuildContext; +import com.github.kaktushose.jda.commands.reflect.interactions.ReplyConfig; import net.dv8tion.jda.api.entities.emoji.Emoji; import net.dv8tion.jda.api.interactions.components.buttons.ButtonStyle; import org.jetbrains.annotations.NotNull; @@ -28,12 +29,12 @@ public final class ButtonDefinition extends GenericComponentDefinition { private ButtonDefinition(Method method, Set permissions, - boolean ephemeral, + ReplyConfig replyConfig, String label, Emoji emoji, String link, ButtonStyle style) { - super(method, permissions, ephemeral); + super(method, permissions, replyConfig); this.label = label; this.emoji = emoji; this.link = link; @@ -71,7 +72,7 @@ public static Optional build(MethodBuildContext context) { return Optional.of(new ButtonDefinition( method, Helpers.permissions(context), - Helpers.ephemeral(context, button.ephemeral()), + Helpers.replyConfig(method), button.value(), emoji, button.link(), @@ -147,7 +148,7 @@ public String toString() { ", emoji=" + emoji + ", link='" + link + '\'' + ", style=" + style + - ", ephemeral=" + ephemeral + + ", replyConfig=" + replyConfig + ", permissions=" + permissions + ", id='" + definitionId + '\'' + ", method=" + method + diff --git a/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/components/GenericComponentDefinition.java b/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/components/GenericComponentDefinition.java index a965afa70..7a09ad808 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/components/GenericComponentDefinition.java +++ b/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/components/GenericComponentDefinition.java @@ -2,31 +2,34 @@ import com.github.kaktushose.jda.commands.reflect.interactions.CustomId; import com.github.kaktushose.jda.commands.reflect.interactions.EphemeralInteractionDefinition; +import com.github.kaktushose.jda.commands.reflect.interactions.ReplyConfig; import com.github.kaktushose.jda.commands.reflect.interactions.components.menus.GenericSelectMenuDefinition; +import org.jetbrains.annotations.NotNull; import java.lang.reflect.Method; import java.util.Set; /** - * Marker class for component definitions. + * Marker class for name definitions. * * @see ButtonDefinition * @see com.github.kaktushose.jda.commands.reflect.interactions.components.menus.GenericSelectMenuDefinition GenericSelectMenuDefinition * @since 4.0.0 */ -public abstract sealed class GenericComponentDefinition extends EphemeralInteractionDefinition implements CustomId permits ButtonDefinition, GenericSelectMenuDefinition { +public abstract sealed class GenericComponentDefinition extends EphemeralInteractionDefinition implements CustomId + permits ButtonDefinition, GenericSelectMenuDefinition { - protected GenericComponentDefinition(Method method, Set permissions, boolean ephemeral) { - super(method, permissions, ephemeral); + protected GenericComponentDefinition(Method method, Set permissions, ReplyConfig replyConfig) { + super(method, permissions, replyConfig); } @Override - public String createCustomId(String runtimeId) { - return String.format("%s.%s%s.%s", - PREFIX, - method.getDeclaringClass().getSimpleName(), - method.getName(), - runtimeId - ); + public @NotNull String boundCustomId(@NotNull String runtimeId) { + return "%s.%s.%s".formatted(PREFIX, runtimeId, definitionId); + } + + @Override + public @NotNull String independentCustomId() { + return "%s.independent.%s".formatted(PREFIX, definitionId); } } diff --git a/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/components/menus/EntitySelectMenuDefinition.java b/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/components/menus/EntitySelectMenuDefinition.java index 520c8500a..197978167 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/components/menus/EntitySelectMenuDefinition.java +++ b/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/components/menus/EntitySelectMenuDefinition.java @@ -1,17 +1,18 @@ package com.github.kaktushose.jda.commands.reflect.interactions.components.menus; -import com.github.kaktushose.jda.commands.Helpers; +import com.github.kaktushose.jda.commands.internal.Helpers; import com.github.kaktushose.jda.commands.annotations.interactions.EntitySelectMenu; import com.github.kaktushose.jda.commands.annotations.interactions.Interaction; -import com.github.kaktushose.jda.commands.dispatching.interactions.components.ComponentEvent; +import com.github.kaktushose.jda.commands.dispatching.events.interactions.ComponentEvent; import com.github.kaktushose.jda.commands.reflect.MethodBuildContext; +import com.github.kaktushose.jda.commands.reflect.interactions.ReplyConfig; +import net.dv8tion.jda.api.entities.Mentions; import net.dv8tion.jda.api.entities.channel.ChannelType; import net.dv8tion.jda.api.interactions.components.selections.EntitySelectMenu.DefaultValue; import net.dv8tion.jda.api.interactions.components.selections.EntitySelectMenu.SelectTarget; import java.lang.reflect.Method; import java.util.HashSet; -import java.util.List; import java.util.Optional; import java.util.Set; @@ -29,14 +30,14 @@ public final class EntitySelectMenuDefinition extends GenericSelectMenuDefinitio private EntitySelectMenuDefinition(Method method, Set permissions, - boolean ephemeral, + ReplyConfig replyConfig, Set selectTargets, Set defaultValues, Set channelTypes, String placeholder, int minValue, int maxValue) { - super(method, permissions, ephemeral, placeholder, minValue, maxValue); + super(method, permissions, replyConfig, placeholder, minValue, maxValue); this.selectTargets = selectTargets; this.defaultValues = defaultValues; this.channelTypes = channelTypes; @@ -58,7 +59,7 @@ public static Optional build(MethodBuildContext cont } if (Helpers.isIncorrectParameterType(method, 0, ComponentEvent.class) || - Helpers.isIncorrectParameterType(method, 1, List.class)) { + Helpers.isIncorrectParameterType(method, 1, Mentions.class)) { return Optional.empty(); } @@ -81,7 +82,7 @@ public static Optional build(MethodBuildContext cont return Optional.of(new EntitySelectMenuDefinition( method, Helpers.permissions(context), - Helpers.ephemeral(context, selectMenu.ephemeral()), + Helpers.replyConfig(method), Set.of(selectMenu.value()), defaultValueSet, new HashSet<>(Set.of(selectMenu.channelTypes())), @@ -92,8 +93,8 @@ public static Optional build(MethodBuildContext cont } @Override - public net.dv8tion.jda.api.interactions.components.selections.EntitySelectMenu toSelectMenu(String runtimeId, boolean enabled) { - var menu = net.dv8tion.jda.api.interactions.components.selections.EntitySelectMenu.create(createCustomId(runtimeId), selectTargets) + public net.dv8tion.jda.api.interactions.components.selections.EntitySelectMenu toSelectMenu(String customId, boolean enabled) { + var menu = net.dv8tion.jda.api.interactions.components.selections.EntitySelectMenu.create(customId, selectTargets) .setDefaultValues(defaultValues) .setPlaceholder(placeholder) .setRequiredRange(minValue, maxValue) @@ -145,7 +146,7 @@ public String toString() { ", minValue=" + minValue + ", maxValue=" + maxValue + ", permissions=" + permissions + - ", ephemeral=" + ephemeral + + ", replyConfig=" + replyConfig + ", id='" + definitionId + '\'' + ", method=" + method + '}'; diff --git a/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/components/menus/GenericSelectMenuDefinition.java b/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/components/menus/GenericSelectMenuDefinition.java index e0a638ce8..94508bada 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/components/menus/GenericSelectMenuDefinition.java +++ b/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/components/menus/GenericSelectMenuDefinition.java @@ -1,5 +1,7 @@ package com.github.kaktushose.jda.commands.reflect.interactions.components.menus; +import com.github.kaktushose.jda.commands.dispatching.internal.Runtime; +import com.github.kaktushose.jda.commands.reflect.interactions.ReplyConfig; import com.github.kaktushose.jda.commands.reflect.interactions.components.GenericComponentDefinition; import net.dv8tion.jda.api.interactions.components.selections.SelectMenu; @@ -13,29 +15,35 @@ * @see StringSelectMenuDefinition * @since 4.0.0 */ -public abstract sealed class GenericSelectMenuDefinition extends GenericComponentDefinition permits EntitySelectMenuDefinition, StringSelectMenuDefinition { +public abstract sealed class GenericSelectMenuDefinition extends GenericComponentDefinition + permits EntitySelectMenuDefinition, StringSelectMenuDefinition { protected final String placeholder; protected final int minValue; protected final int maxValue; - protected GenericSelectMenuDefinition(Method method, Set permissions, boolean ephemeral, String placeholder, int minValue, int maxValue) { - super(method, permissions, ephemeral); + protected GenericSelectMenuDefinition(Method method, + Set permissions, + ReplyConfig replyConfig, + String placeholder, + int minValue, + int maxValue) { + super(method, permissions, replyConfig); this.placeholder = placeholder; this.minValue = minValue; this.maxValue = maxValue; } /** - * Transforms this definition to a select menu component. + * Transforms this definition to a select menu name. * - * @param runtimeId the runtimeId of the - * {@link com.github.kaktushose.jda.commands.dispatching.RuntimeSupervisor.InteractionRuntime InteractionRuntime} - * of this interaction execution - * @param enabled {@code true} if the component should be enabled - * @return the select menu component + * @param customId the runtimeId of the + * {@link Runtime Runtime} + * of this interaction execution + * @param enabled {@code true} if the name should be enabled + * @return the select menu name */ - public abstract T toSelectMenu(String runtimeId, boolean enabled); + public abstract T toSelectMenu(String customId, boolean enabled); /** * Gets the placeholder string. diff --git a/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/components/menus/StringSelectMenuDefinition.java b/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/components/menus/StringSelectMenuDefinition.java index e22c0c5f4..965651000 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/components/menus/StringSelectMenuDefinition.java +++ b/src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/components/menus/StringSelectMenuDefinition.java @@ -1,11 +1,12 @@ package com.github.kaktushose.jda.commands.reflect.interactions.components.menus; -import com.github.kaktushose.jda.commands.Helpers; +import com.github.kaktushose.jda.commands.internal.Helpers; import com.github.kaktushose.jda.commands.annotations.interactions.Interaction; import com.github.kaktushose.jda.commands.annotations.interactions.SelectOption; import com.github.kaktushose.jda.commands.annotations.interactions.StringSelectMenu; -import com.github.kaktushose.jda.commands.dispatching.interactions.components.ComponentEvent; +import com.github.kaktushose.jda.commands.dispatching.events.interactions.ComponentEvent; import com.github.kaktushose.jda.commands.reflect.MethodBuildContext; +import com.github.kaktushose.jda.commands.reflect.interactions.ReplyConfig; import java.lang.reflect.Method; import java.util.HashSet; @@ -26,12 +27,12 @@ public final class StringSelectMenuDefinition extends GenericSelectMenuDefinitio private StringSelectMenuDefinition(Method method, Set permissions, - boolean ephemeral, + ReplyConfig replyConfig, Set selectOptions, String placeholder, int minValue, int maxValue) { - super(method, permissions, ephemeral, placeholder, minValue, maxValue); + super(method, permissions, replyConfig, placeholder, minValue, maxValue); this.selectOptions = selectOptions; } @@ -65,7 +66,7 @@ public static Optional build(MethodBuildContext cont return Optional.of(new StringSelectMenuDefinition( method, Helpers.permissions(context), - Helpers.ephemeral(context, selectMenu.ephemeral()), + Helpers.replyConfig(method), selectOptions, selectMenu.value(), selectMenu.minValue(), @@ -73,8 +74,8 @@ public static Optional build(MethodBuildContext cont )); } - public net.dv8tion.jda.api.interactions.components.selections.StringSelectMenu toSelectMenu(String runtimeId, boolean enabled) { - return net.dv8tion.jda.api.interactions.components.selections.StringSelectMenu.create(createCustomId(runtimeId)) + public net.dv8tion.jda.api.interactions.components.selections.StringSelectMenu toSelectMenu(String customId, boolean enabled) { + return net.dv8tion.jda.api.interactions.components.selections.StringSelectMenu.create(customId) .setPlaceholder(placeholder) .setRequiredRange(minValue, maxValue) .addOptions(selectOptions.stream().map(SelectOptionDefinition::toSelectOption).collect(Collectors.toSet())) @@ -103,7 +104,7 @@ public String toString() { ", placeholder='" + placeholder + '\'' + ", minValue=" + minValue + ", maxValue=" + maxValue + - ", ephemeral=" + ephemeral + + ", replyConfig=" + replyConfig + ", permissions=" + permissions + ", id='" + definitionId + '\'' + ", method=" + method + diff --git a/src/main/java/com/github/kaktushose/jda/commands/scope/DefaultGuildScopeProvider.java b/src/main/java/com/github/kaktushose/jda/commands/scope/DefaultGuildScopeProvider.java index d7ffab805..7f8e2b0bc 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/scope/DefaultGuildScopeProvider.java +++ b/src/main/java/com/github/kaktushose/jda/commands/scope/DefaultGuildScopeProvider.java @@ -13,6 +13,7 @@ */ public class DefaultGuildScopeProvider implements GuildScopeProvider { + /// @return always an empty set @Override public Set apply(CommandData commandData) { return Collections.emptySet(); diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 33a136ec6..c3c72b9aa 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -1,3 +1,4 @@ +/// The base module of jda-commands module jda.commands { requires com.google.gson; requires jsr305; @@ -6,4 +7,43 @@ requires org.reflections; requires java.desktop; requires org.slf4j; + requires kotlin.stdlib; + + // base package + exports com.github.kaktushose.jda.commands; + + // annotations + exports com.github.kaktushose.jda.commands.annotations; + exports com.github.kaktushose.jda.commands.annotations.constraints; + exports com.github.kaktushose.jda.commands.annotations.interactions; + + // dependency injection + exports com.github.kaktushose.jda.commands.dependency; + + // dispatching api + exports com.github.kaktushose.jda.commands.dispatching; + exports com.github.kaktushose.jda.commands.dispatching.adapter; + exports com.github.kaktushose.jda.commands.dispatching.adapter.impl; + + exports com.github.kaktushose.jda.commands.dispatching.context; + exports com.github.kaktushose.jda.commands.dispatching.events; + exports com.github.kaktushose.jda.commands.dispatching.events.interactions; + + exports com.github.kaktushose.jda.commands.dispatching.middleware; + exports com.github.kaktushose.jda.commands.dispatching.middleware.impl; + + exports com.github.kaktushose.jda.commands.dispatching.reply; + + exports com.github.kaktushose.jda.commands.dispatching.validation; + exports com.github.kaktushose.jda.commands.dispatching.validation.impl; + + // embed + exports com.github.kaktushose.jda.commands.embeds; + exports com.github.kaktushose.jda.commands.embeds.error; + + // permissions api + exports com.github.kaktushose.jda.commands.permissions; + + // command scope api + exports com.github.kaktushose.jda.commands.scope; } \ No newline at end of file diff --git a/src/main/javadoc/doc-files/flowchart.png b/src/main/javadoc/doc-files/flowchart.png new file mode 100644 index 0000000000000000000000000000000000000000..52960e517fd4743eecd9b18adc1185868c56d151 GIT binary patch literal 129529 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 literal 0 HcmV?d00001 diff --git a/src/main/javadoc/overview.md b/src/main/javadoc/overview.md new file mode 100644 index 000000000..bb55cf4d6 --- /dev/null +++ b/src/main/javadoc/overview.md @@ -0,0 +1,65 @@ +# JDA-Commands + +### A declarative, annotation driven interaction framework for JDA. + +--- + +## Resources + +This is the official documentation for jda-commands. If you are new to jda-commands +(or [JDA](https://jda.wiki/introduction/jda/) in general) you might also find the following resources helpful: + +- [JDA-Commands Wiki](https://github.com/Kaktushose/jda-commands/wiki) +- [Release Notes](https://github.com/Kaktushose/jda-commands/releases) +- [JDA Wiki](https://jda.wiki/) + +Having trouble or found a bug? + +- Check out the [Examples](https://github.com/Kaktushose/jda-commands/tree/main/src/examples) +- Join the [Discord Server](https://discord.gg/JYWezvQ) +- Or open an [Issue](https://github.com/Kaktushose/jda-commands/issues) + +## Example usage +### 1. Create JDACommands instance and start framework +```java +public class Main { + + public static void main(String[] args) { + JDA jda = yourJDABuilding(); + JDACommands jdaCommands = JDACommands.start(jda, Main.class); + } + +} +``` + +### 2. Create interaction class + +```java +@Interaction +public class HelloWorld { + + @SlashCommand("greet") + public void onCommand(CommandEvent event) { + event.reply("Hello World!"); + } + +} +``` + + +## Runtime Concept + +One of the core concepts in jda-commands is the so-called `Runtime`. It is mentioned frequently in the docs. A `Runtime` delegates the jda events to their corresponding `EventHandlers` and manages the used virtual threads. + +A new `Runtime` is created each time a [`SlashCommandInteractionEvent`](https://javadoc.io/doc/net.dv8tion/JDA/latest/net/dv8tion/jda/api/events/interaction/command/SlashCommandInteractionEvent.html), +[`GenericContextInteractionEvent`](https://javadoc.io/doc/net.dv8tion/JDA/latest/net/dv8tion/jda/api/events/interaction/command/GenericContextInteractionEvent.html) +or [`CommandAutoCompleteInteractionEvent`](https://javadoc.io/doc/net.dv8tion/JDA/latest/net/dv8tion/jda/api/events/interaction/command/CommandAutoCompleteInteractionEvent.html) is provided by jda +or if an interaction is marked as [*independent*](jda.commands/com/github/kaktushose/jda/commands/dispatching/reply/Component.html#independent(java.lang.String...)). + +Runtimes are executed in parallel, but events are processed sequentially by each runtime. +Every `EventHandler` called by this `Runtime` is executed in its own virtual thread, isolated from the runtime one. + +See [`ExpirationStrategy`](jda.commands/com/github/kaktushose/jda/commands/dispatching/ExpirationStrategy.html) for +details when a `Runtime` will close. + +event/runtime flowchart diff --git a/src/test/java/adapting/CustomTypeAdapter.java b/src/test/java/adapting/CustomTypeAdapter.java index cabfc90f5..36318f70f 100644 --- a/src/test/java/adapting/CustomTypeAdapter.java +++ b/src/test/java/adapting/CustomTypeAdapter.java @@ -1,15 +1,16 @@ package adapting; -import com.github.kaktushose.jda.commands.dispatching.interactions.Context; import com.github.kaktushose.jda.commands.dispatching.adapter.TypeAdapter; +import net.dv8tion.jda.api.events.interaction.GenericInteractionCreateEvent; import org.jetbrains.annotations.NotNull; import java.util.Optional; public class CustomTypeAdapter implements TypeAdapter { + @Override - public Optional apply(@NotNull String raw, @NotNull Context context) { + public @NotNull Optional apply(@NotNull String raw, @NotNull GenericInteractionCreateEvent event) { return Optional.of(new CustomType()); } } diff --git a/src/test/java/adapting/TypeAdapterRegistryTest.java b/src/test/java/adapting/TypeAdapterRegistryTest.java index 9f23c24ef..783eea028 100644 --- a/src/test/java/adapting/TypeAdapterRegistryTest.java +++ b/src/test/java/adapting/TypeAdapterRegistryTest.java @@ -1,176 +1,171 @@ -package adapting; - -import adapting.mock.SlashCommandInteractionEventMock; -import adapting.mock.TypeAdapterRegistryTestController; -import com.github.kaktushose.jda.commands.annotations.interactions.Interaction; -import com.github.kaktushose.jda.commands.dependency.DefaultDependencyInjector; -import com.github.kaktushose.jda.commands.dispatching.adapter.TypeAdapterRegistry; -import com.github.kaktushose.jda.commands.dispatching.adapter.impl.IntegerAdapter; -import com.github.kaktushose.jda.commands.dispatching.interactions.commands.CommandEvent; -import com.github.kaktushose.jda.commands.dispatching.interactions.commands.SlashCommandContext; -import com.github.kaktushose.jda.commands.dispatching.middleware.MiddlewareRegistry; -import com.github.kaktushose.jda.commands.dispatching.validation.ValidatorRegistry; -import com.github.kaktushose.jda.commands.reflect.CooldownDefinition; -import com.github.kaktushose.jda.commands.reflect.ImplementationRegistry; -import com.github.kaktushose.jda.commands.reflect.InteractionRegistry; -import com.github.kaktushose.jda.commands.reflect.MethodBuildContext; -import com.github.kaktushose.jda.commands.reflect.interactions.commands.SlashCommandDefinition; -import net.dv8tion.jda.api.interactions.commands.localization.ResourceBundleLocalizationFunction; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import java.lang.reflect.Method; -import java.util.Set; - -import static org.junit.jupiter.api.Assertions.*; - -public class TypeAdapterRegistryTest { - - private static Class controller; - private static ValidatorRegistry validator; - private static TypeAdapterRegistry adapter; - private TypeAdapterRegistry registry; - - @BeforeAll - public static void setup() { - TypeAdapterRegistryTestController instance = new TypeAdapterRegistryTestController(); - controller = instance.getClass(); - validator = new ValidatorRegistry(); - adapter = new TypeAdapterRegistry(); - } - - @BeforeEach - public void cleanup() { - registry = new TypeAdapterRegistry(); - } - - @Test - public void register_withNewTypeAndNewAdapter_ShouldAdd() { - registry = new TypeAdapterRegistry(); - registry.register(CustomType.class, new CustomTypeAdapter()); - - assertTrue(registry.exists(CustomType.class)); - } - - @Test - public void register_withExistingTypeAndNewAdapter_ShouldOverride() { - registry = new TypeAdapterRegistry(); - CustomTypeAdapter adapter = new CustomTypeAdapter(); - - assertEquals(IntegerAdapter.class, registry.get(Integer.class).get().getClass()); - registry.register(Integer.class, adapter); - - assertEquals(adapter, registry.get(Integer.class).orElse(null)); - } - - @Test - public void unregister_withExistingType_ShouldRemove() { - registry = new TypeAdapterRegistry(); - - assertTrue(registry.exists(Integer.class)); - registry.unregister(Integer.class); - - assertFalse(registry.exists(Integer.class)); - } - - @Test - public void adapt_withStringArray_ShouldNotAdapt() throws NoSuchMethodException { - SlashCommandContext context = buildContext(buildCommand("stringArray", CommandEvent.class, String[].class), "a", "b", "c"); - - registry.adapt(context); - - assertArrayEquals(new String[]{"a", "b", "c"}, (String[]) context.getArguments().getFirst()); - } - - @Test - public void adapt_withLessInputThanParameters_ShouldThrow() throws NoSuchMethodException { - SlashCommandContext context = buildContext(buildCommand("inputLength", CommandEvent.class, int.class)); - - assertThrows(IllegalStateException.class, () -> registry.adapt(context)); - } - - @Test - public void adapt_withMoreInputThanParameters_ShouldNotCancel() throws NoSuchMethodException { - SlashCommandContext context = buildContext(buildCommand("inputLength", CommandEvent.class, int.class), "1", "2"); - - registry.adapt(context); - - assertFalse(context.isCancelled()); - } - - @Test - public void adapt_withOptionalWithDefaultNull_ShouldAddNull() throws NoSuchMethodException { - SlashCommandContext context = buildContext(buildCommand("optionalNull", CommandEvent.class, int.class)); - - registry.adapt(context); - - assertNull(context.getArguments().getFirst()); - } - - @Test - public void adapt_withOptionalWithDefault_ShouldAddDefault() throws NoSuchMethodException { - SlashCommandContext context = buildContext(buildCommand("optionalDefault", CommandEvent.class, String.class)); - - registry.adapt(context); - - assertEquals(TypeAdapterRegistryTestController.OPTIONAL_DEFAULT, context.getArguments().getFirst()); - } - - @Test - public void adapt_withMissingTypeAdapter_ShouldThrowIllegalArgumentException() throws NoSuchMethodException { - adapter.register(CustomType.class, new CustomTypeAdapter()); - SlashCommandContext context = buildContext(buildCommand("noAdapter", CommandEvent.class, CustomType.class), "string"); - adapter.unregister(CustomType.class); - - assertThrows(IllegalArgumentException.class, () -> registry.adapt(context)); - } - - @Test - public void adapt_withWrongArgument_ShouldCancel() throws NoSuchMethodException { - SlashCommandContext context = buildContext(buildCommand("wrongArgument", CommandEvent.class, int.class), "string"); - - registry.adapt(context); - assertTrue(context.isCancelled()); - } - - private SlashCommandDefinition buildCommand(String name, Class... parameterTypes) throws NoSuchMethodException { - Method method = controller.getMethod(name, parameterTypes); - SlashCommandDefinition command = SlashCommandDefinition.build(new MethodBuildContext( - validator, - ResourceBundleLocalizationFunction.empty().build(), - controller.getAnnotation(Interaction.class), - Set.of(), - CooldownDefinition.build(null), - method, - Set.of() - )).orElse(null); - assertNotNull(command); - return command; - } - - public static SlashCommandContext buildContext(SlashCommandDefinition command, String... input) { - SlashCommandContext context = new SlashCommandContext( - new SlashCommandInteractionEventMock(), - new InteractionRegistry( - new ValidatorRegistry(), - new DefaultDependencyInjector(), - ResourceBundleLocalizationFunction.empty().build() - ), - new ImplementationRegistry( - new DefaultDependencyInjector(), - new MiddlewareRegistry(), - new TypeAdapterRegistry(), - new ValidatorRegistry() - ) - ); - context.setInput(input); - context.setCommand(command); - return context; - } - - private T giveNull() { - return null; - } - -} +//package adapting; +// +//import adapting.mock.SlashCommandInteractionEventMock; +//import adapting.mock.TypeAdapterRegistryTestController; +//import com.github.kaktushose.jda.commands.annotations.interactions.Interaction; +//import com.github.kaktushose.jda.commands.dependency.DefaultDependencyInjector; +//import com.github.kaktushose.jda.commands.dispatching.adapter.TypeAdapterRegistry; +//import com.github.kaktushose.jda.commands.dispatching.adapter.impl.IntegerAdapter; +//import com.github.kaktushose.jda.commands.dispatching.events.interactions.CommandEvent; +//import com.github.kaktushose.jda.commands.dispatching.middleware.MiddlewareRegistry; +//import com.github.kaktushose.jda.commands.dispatching.validation.ValidatorRegistry; +//import com.github.kaktushose.jda.commands.reflect.CooldownDefinition; +//import com.github.kaktushose.jda.commands.reflect.ImplementationRegistry; +//import com.github.kaktushose.jda.commands.reflect.InteractionRegistry; +//import com.github.kaktushose.jda.commands.reflect.MethodBuildContext; +//import com.github.kaktushose.jda.commands.reflect.interactions.commands.SlashCommandDefinition; +//import net.dv8tion.jda.api.interactions.commands.localization.ResourceBundleLocalizationFunction; +//import org.junit.jupiter.api.BeforeAll; +//import org.junit.jupiter.api.BeforeEach; +//import org.junit.jupiter.api.Test; +// +//import java.lang.reflect.Method; +//import java.util.Set; +// +//import static org.junit.jupiter.api.Assertions.*; +// +//public class TypeAdapterRegistryTest { +// +// private static Class controller; +// private static ValidatorRegistry validator; +// private static TypeAdapterRegistry adapter; +// private TypeAdapterRegistry registry; +// +// @BeforeAll +// public static void setup() { +// TypeAdapterRegistryTestController instance = new TypeAdapterRegistryTestController(); +// controller = instance.getClass(); +// validator = new ValidatorRegistry(); +// adapter = new TypeAdapterRegistry(); +// } +// +// @BeforeEach +// public void cleanup() { +// registry = new TypeAdapterRegistry(); +// } +// +// @Test +// public void register_withNewTypeAndNewAdapter_ShouldAdd() { +// registry = new TypeAdapterRegistry(); +// registry.register(CustomType.class, new CustomTypeAdapter()); +// +// assertTrue(registry.exists(CustomType.class)); +// } +// +// @Test +// public void register_withExistingTypeAndNewAdapter_ShouldOverride() { +// registry = new TypeAdapterRegistry(); +// CustomTypeAdapter adapter = new CustomTypeAdapter(); +// +// assertEquals(IntegerAdapter.class, registry.get(Integer.class).get().getClass()); +// registry.register(Integer.class, adapter); +// +// assertEquals(adapter, registry.get(Integer.class).orElse(null)); +// } +// +// @Test +// public void unregister_withExistingType_ShouldRemove() { +// registry = new TypeAdapterRegistry(); +// +// assertTrue(registry.exists(Integer.class)); +// registry.unregister(Integer.class); +// +// assertFalse(registry.exists(Integer.class)); +// } +// +// @Test +// public void adapt_withStringArray_ShouldNotAdapt() throws NoSuchMethodException { +// SlashCommandContext context = buildContext(buildCommand("stringArray", CommandEvent.class, String[].class), "a", "b", "c"); +// +// registry.adapt(context); +// +// assertArrayEquals(new String[]{"a", "b", "c"}, (String[]) context.getArguments().getFirst()); +// } +// +// @Test +// public void adapt_withLessInputThanParameters_ShouldThrow() throws NoSuchMethodException { +// SlashCommandContext context = buildContext(buildCommand("inputLength", CommandEvent.class, int.class)); +// +// assertThrows(IllegalStateException.class, () -> registry.adapt(context)); +// } +// +// @Test +// public void adapt_withMoreInputThanParameters_ShouldNotCancel() throws NoSuchMethodException { +// SlashCommandContext context = buildContext(buildCommand("inputLength", CommandEvent.class, int.class), "1", "2"); +// +// registry.adapt(context); +// +// assertFalse(context.isCancelled()); +// } +// +// @Test +// public void adapt_withOptionalWithDefaultNull_ShouldAddNull() throws NoSuchMethodException { +// SlashCommandContext context = buildContext(buildCommand("optionalNull", CommandEvent.class, int.class)); +// +// registry.adapt(context); +// +// assertNull(context.getArguments().getFirst()); +// } +// +// @Test +// public void adapt_withOptionalWithDefault_ShouldAddDefault() throws NoSuchMethodException { +// SlashCommandContext context = buildContext(buildCommand("optionalDefault", CommandEvent.class, String.class)); +// +// registry.adapt(context); +// +// assertEquals(TypeAdapterRegistryTestController.OPTIONAL_DEFAULT, context.getArguments().getFirst()); +// } +// +// @Test +// public void adapt_withMissingTypeAdapter_ShouldThrowIllegalArgumentException() throws NoSuchMethodException { +// adapter.register(CustomType.class, new CustomTypeAdapter()); +// SlashCommandContext context = buildContext(buildCommand("noAdapter", CommandEvent.class, CustomType.class), "string"); +// adapter.unregister(CustomType.class); +// +// assertThrows(IllegalArgumentException.class, () -> registry.adapt(context)); +// } +// +// @Test +// public void adapt_withWrongArgument_ShouldCancel() throws NoSuchMethodException { +// SlashCommandContext context = buildContext(buildCommand("wrongArgument", CommandEvent.class, int.class), "string"); +// +// registry.(context); +// assertTrue(context.isCancelled()); +// } +// +// private SlashCommandDefinition buildCommand(String name, Class... parameterTypes) throws NoSuchMethodException { +// Method method = controller.getMethod(name, parameterTypes); +// SlashCommandDefinition command = SlashCommandDefinition.build(new MethodBuildContext( +// validator, +// ResourceBundleLocalizationFunction.empty().build(), +// controller.getAnnotation(Interaction.class), +// Set.of(), +// CooldownDefinition.build(null), +// method, +// Set.of() +// )).orElse(null); +// assertNotNull(command); +// return command; +// } +// +// public static SlashCommandContext buildContext(SlashCommandDefinition command, String... input) { +// SlashCommandContext context = new SlashCommandContext( +// new SlashCommandInteractionEventMock(), +// new InteractionRegistry( +// new ValidatorRegistry(), +// new DefaultDependencyInjector(), +// ResourceBundleLocalizationFunction.empty().build() +// ), +// new ImplementationRegistry( +// new DefaultDependencyInjector(), +// new MiddlewareRegistry(), +// new TypeAdapterRegistry(), +// new ValidatorRegistry() +// ) +// ); +// context.setInput(input); +// context.setCommand(command); +// return context; +// } +// +//} diff --git a/src/test/java/adapting/TypeAdapterTest.java b/src/test/java/adapting/TypeAdapterTest.java index ace397e3b..f597916de 100644 --- a/src/test/java/adapting/TypeAdapterTest.java +++ b/src/test/java/adapting/TypeAdapterTest.java @@ -2,9 +2,10 @@ import adapting.mock.GuildMock; import adapting.mock.JDAMock; -import com.github.kaktushose.jda.commands.Helpers; +import adapting.mock.SlashCommandInteractionEventMock; +import com.github.kaktushose.jda.commands.internal.Helpers; import com.github.kaktushose.jda.commands.dispatching.adapter.impl.*; -import com.github.kaktushose.jda.commands.dispatching.interactions.commands.SlashCommandContext; +import net.dv8tion.jda.api.events.interaction.GenericInteractionCreateEvent; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -12,291 +13,291 @@ public class TypeAdapterTest { - private static SlashCommandContext context; + private static GenericInteractionCreateEvent event; @BeforeAll public static void setup() { - context = TypeAdapterRegistryTest.buildContext(null, ""); + event = new SlashCommandInteractionEventMock(); } @Test public void booleanAdapter_withZero_ShouldReturnFalse() { BooleanAdapter adapter = new BooleanAdapter(); - assertFalse(adapter.apply("0", context).orElse(true)); + assertFalse(adapter.apply("0", event).orElse(true)); } @Test public void booleanAdapter_withFalse_ShouldReturnFalse() { BooleanAdapter adapter = new BooleanAdapter(); - assertFalse(adapter.apply("False", context).orElse(true)); - assertFalse(adapter.apply("false", context).orElse(true)); - assertFalse(adapter.apply("fAlSe", context).orElse(true)); + assertFalse(adapter.apply("False", event).orElse(true)); + assertFalse(adapter.apply("false", event).orElse(true)); + assertFalse(adapter.apply("fAlSe", event).orElse(true)); } @Test public void booleanAdapter_withOne_ShouldReturnTrue() { BooleanAdapter adapter = new BooleanAdapter(); - assertTrue(adapter.apply("1", context).orElse(false)); + assertTrue(adapter.apply("1", event).orElse(false)); } @Test public void booleanAdapter_withTrue_ShouldReturnTrue() { BooleanAdapter adapter = new BooleanAdapter(); - assertTrue(adapter.apply("True", context).orElse(false)); - assertTrue(adapter.apply("true", context).orElse(false)); - assertTrue(adapter.apply("tRuE", context).orElse(false)); + assertTrue(adapter.apply("True", event).orElse(false)); + assertTrue(adapter.apply("true", event).orElse(false)); + assertTrue(adapter.apply("tRuE", event).orElse(false)); } @Test public void booleanAdapter_withNonBoolean_ShouldBeEmpty() { BooleanAdapter adapter = new BooleanAdapter(); - assertFalse(adapter.apply("text", context).isPresent()); + assertFalse(adapter.apply("text", event).isPresent()); } @Test public void byteAdapter_withNumber_ShouldBePresent() { ByteAdapter adapter = new ByteAdapter(); - assertEquals(Byte.MIN_VALUE, adapter.apply(String.valueOf(Byte.MIN_VALUE), context).orElseThrow()); - assertEquals(Byte.MAX_VALUE, adapter.apply(String.valueOf(Byte.MAX_VALUE), context).orElseThrow()); + assertEquals(Byte.MIN_VALUE, adapter.apply(String.valueOf(Byte.MIN_VALUE), event).orElseThrow()); + assertEquals(Byte.MAX_VALUE, adapter.apply(String.valueOf(Byte.MAX_VALUE), event).orElseThrow()); } @Test public void byteAdapter_withNonNumeric_ShouldBeEmpty() { ByteAdapter adapter = new ByteAdapter(); - assertFalse(adapter.apply("text", context).isPresent()); + assertFalse(adapter.apply("text", event).isPresent()); } @Test public void characterAdapter_withOneChar_ShouldBePresent() { CharacterAdapter adapter = new CharacterAdapter(); - assertTrue(adapter.apply("c", context).isPresent()); + assertTrue(adapter.apply("c", event).isPresent()); } @Test public void characterAdapter_withMultipleChars_ShouldBeEmpty() { CharacterAdapter adapter = new CharacterAdapter(); - assertFalse(adapter.apply("chars", context).isPresent()); + assertFalse(adapter.apply("chars", event).isPresent()); } @Test public void doubleAdapter_withNumber_ShouldReturnDouble() { DoubleAdapter adapter = new DoubleAdapter(); - assertEquals(Double.MIN_VALUE, adapter.apply(String.valueOf(Double.MIN_VALUE), context).orElseThrow()); - assertEquals(Double.MIN_EXPONENT, adapter.apply(String.valueOf(Double.MIN_EXPONENT), context).orElseThrow()); - assertEquals(Double.MAX_VALUE, adapter.apply(String.valueOf(Double.MAX_VALUE), context).orElseThrow()); - assertEquals(Double.MAX_EXPONENT, adapter.apply(String.valueOf(Double.MAX_EXPONENT), context).orElseThrow()); + assertEquals(Double.MIN_VALUE, adapter.apply(String.valueOf(Double.MIN_VALUE), event).orElseThrow()); + assertEquals(Double.MIN_EXPONENT, adapter.apply(String.valueOf(Double.MIN_EXPONENT), event).orElseThrow()); + assertEquals(Double.MAX_VALUE, adapter.apply(String.valueOf(Double.MAX_VALUE), event).orElseThrow()); + assertEquals(Double.MAX_EXPONENT, adapter.apply(String.valueOf(Double.MAX_EXPONENT), event).orElseThrow()); } @Test public void doubleAdapter_withNonNumeric_ShouldBeEmpty() { DoubleAdapter adapter = new DoubleAdapter(); - assertFalse(adapter.apply("text", context).isPresent()); + assertFalse(adapter.apply("text", event).isPresent()); } @Test public void floatAdapter_withNumber_ShouldReturnDouble() { FloatAdapter adapter = new FloatAdapter(); - assertEquals(Float.MIN_VALUE, adapter.apply(String.valueOf(Float.MIN_VALUE), context).orElseThrow()); - assertEquals(Float.MIN_EXPONENT, adapter.apply(String.valueOf(Float.MIN_EXPONENT), context).orElseThrow()); - assertEquals(Float.MAX_VALUE, adapter.apply(String.valueOf(Float.MAX_VALUE), context).orElseThrow()); - assertEquals(Float.MAX_EXPONENT, adapter.apply(String.valueOf(Float.MAX_EXPONENT), context).orElseThrow()); + assertEquals(Float.MIN_VALUE, adapter.apply(String.valueOf(Float.MIN_VALUE), event).orElseThrow()); + assertEquals(Float.MIN_EXPONENT, adapter.apply(String.valueOf(Float.MIN_EXPONENT), event).orElseThrow()); + assertEquals(Float.MAX_VALUE, adapter.apply(String.valueOf(Float.MAX_VALUE), event).orElseThrow()); + assertEquals(Float.MAX_EXPONENT, adapter.apply(String.valueOf(Float.MAX_EXPONENT), event).orElseThrow()); } @Test public void floatAdapter_withNonNumeric_ShouldBeEmpty() { FloatAdapter adapter = new FloatAdapter(); - assertFalse(adapter.apply("text", context).isPresent()); + assertFalse(adapter.apply("text", event).isPresent()); } @Test public void integerAdapter_withNumber_ShouldReturnDouble() { IntegerAdapter adapter = new IntegerAdapter(); - assertEquals(Integer.MIN_VALUE, adapter.apply(String.valueOf(Integer.MIN_VALUE), context).orElseThrow()); - assertEquals(Integer.MAX_VALUE, adapter.apply(String.valueOf(Integer.MAX_VALUE), context).orElseThrow()); + assertEquals(Integer.MIN_VALUE, adapter.apply(String.valueOf(Integer.MIN_VALUE), event).orElseThrow()); + assertEquals(Integer.MAX_VALUE, adapter.apply(String.valueOf(Integer.MAX_VALUE), event).orElseThrow()); } @Test public void integerAdapter_withNonNumeric_ShouldBeEmpty() { IntegerAdapter adapter = new IntegerAdapter(); - assertFalse(adapter.apply("text", context).isPresent()); + assertFalse(adapter.apply("text", event).isPresent()); } @Test public void longAdapter_withNumber_ShouldReturnDouble() { LongAdapter adapter = new LongAdapter(); - assertEquals(Long.MIN_VALUE, adapter.apply(String.valueOf(Long.MIN_VALUE), context).orElseThrow()); - assertEquals(Long.MAX_VALUE, adapter.apply(String.valueOf(Long.MAX_VALUE), context).orElseThrow()); + assertEquals(Long.MIN_VALUE, adapter.apply(String.valueOf(Long.MIN_VALUE), event).orElseThrow()); + assertEquals(Long.MAX_VALUE, adapter.apply(String.valueOf(Long.MAX_VALUE), event).orElseThrow()); } @Test public void longAdapter_withNonNumeric_ShouldBeEmpty() { LongAdapter adapter = new LongAdapter(); - assertFalse(adapter.apply("text", context).isPresent()); + assertFalse(adapter.apply("text", event).isPresent()); } @Test public void shortAdapter_withNumber_ShouldReturnDouble() { ShortAdapter adapter = new ShortAdapter(); - assertEquals(Short.MIN_VALUE, adapter.apply(String.valueOf(Short.MIN_VALUE), context).orElseThrow()); - assertEquals(Short.MAX_VALUE, adapter.apply(String.valueOf(Short.MAX_VALUE), context).orElseThrow()); + assertEquals(Short.MIN_VALUE, adapter.apply(String.valueOf(Short.MIN_VALUE), event).orElseThrow()); + assertEquals(Short.MAX_VALUE, adapter.apply(String.valueOf(Short.MAX_VALUE), event).orElseThrow()); } @Test public void shortAdapter_withNonNumeric_ShouldBeEmpty() { ShortAdapter adapter = new ShortAdapter(); - assertFalse(adapter.apply("text", context).isPresent()); + assertFalse(adapter.apply("text", event).isPresent()); } @Test public void adapter_withExistingId_ShouldReturnUser() { UserAdapter adapter = new UserAdapter(); - assertEquals(JDAMock.USER, adapter.apply(String.valueOf(JDAMock.USER.getIdLong()), context).orElseThrow()); + assertEquals(JDAMock.USER, adapter.apply(String.valueOf(JDAMock.USER.getIdLong()), event).orElseThrow()); } @Test public void adapter_withNonExistingId_ShouldBeEmpty() { UserAdapter adapter = new UserAdapter(); - assertFalse(adapter.apply("1234567890", context).isPresent()); + assertFalse(adapter.apply("1234567890", event).isPresent()); } @Test public void adapter_withExistingName_ShouldReturnUser() { UserAdapter adapter = new UserAdapter(); - assertEquals(JDAMock.USER, adapter.apply(JDAMock.USER.getName(), context).orElseThrow()); + assertEquals(JDAMock.USER, adapter.apply(JDAMock.USER.getName(), event).orElseThrow()); } @Test public void adapter_withNonExistingName_ShouldBeEmpty() { UserAdapter adapter = new UserAdapter(); - assertFalse(adapter.apply("thispersondoesnotexist.com", context).isPresent()); + assertFalse(adapter.apply("thispersondoesnotexist.com", event).isPresent()); } @Test public void memberAdapter_withExistingId_ShouldReturnMember() { MemberAdapter adapter = new MemberAdapter(); - assertEquals(GuildMock.MEMBER, adapter.apply(String.valueOf(GuildMock.MEMBER.getIdLong()), context).orElseThrow()); + assertEquals(GuildMock.MEMBER, adapter.apply(String.valueOf(GuildMock.MEMBER.getIdLong()), event).orElseThrow()); } @Test public void memberAdapter_withNonExistingId_ShouldBeEmpty() { MemberAdapter adapter = new MemberAdapter(); - assertFalse(adapter.apply("1234567890", context).isPresent()); + assertFalse(adapter.apply("1234567890", event).isPresent()); } @Test public void memberAdapter_withExistingName_ShouldReturnMember() { MemberAdapter adapter = new MemberAdapter(); - assertEquals(GuildMock.MEMBER, adapter.apply(GuildMock.MEMBER.getNickname(), context).orElseThrow()); + assertEquals(GuildMock.MEMBER, adapter.apply(GuildMock.MEMBER.getNickname(), event).orElseThrow()); } @Test public void memberAdapter_withNonExistingName_ShouldBeEmpty() { MemberAdapter adapter = new MemberAdapter(); - assertFalse(adapter.apply("thispersondoesnotexist.com", context).isPresent()); + assertFalse(adapter.apply("thispersondoesnotexist.com", event).isPresent()); } @Test public void memberAdapter_inNotGuildContext_ShouldBeEmpty() { MemberAdapter adapter = new MemberAdapter(); - assertFalse(adapter.apply("name", context).isPresent()); + assertFalse(adapter.apply("name", event).isPresent()); } @Test public void roleAdapter_withExistingId_ShouldReturnMember() { RoleAdapter adapter = new RoleAdapter(); - assertEquals(GuildMock.ROLE, adapter.apply(String.valueOf(GuildMock.ROLE.getIdLong()), context).orElseThrow()); + assertEquals(GuildMock.ROLE, adapter.apply(String.valueOf(GuildMock.ROLE.getIdLong()), event).orElseThrow()); } @Test public void roleAdapter_withNonExistingId_ShouldBeEmpty() { RoleAdapter adapter = new RoleAdapter(); - assertFalse(adapter.apply("1234567890", context).isPresent()); + assertFalse(adapter.apply("1234567890", event).isPresent()); } @Test public void roleAdapter_withExistingName_ShouldReturnMember() { RoleAdapter adapter = new RoleAdapter(); - assertEquals(GuildMock.ROLE, adapter.apply(GuildMock.ROLE.getName(), context).orElseThrow()); + assertEquals(GuildMock.ROLE, adapter.apply(GuildMock.ROLE.getName(), event).orElseThrow()); } @Test public void roleAdapter_withNonExistingName_ShouldBeEmpty() { RoleAdapter adapter = new RoleAdapter(); - assertFalse(adapter.apply("thispersondoesnotexist.com", context).isPresent()); + assertFalse(adapter.apply("thispersondoesnotexist.com", event).isPresent()); } @Test public void roleAdapter_inNotGuildContext_ShouldBeEmpty() { RoleAdapter adapter = new RoleAdapter(); - assertFalse(adapter.apply("name", context).isPresent()); + assertFalse(adapter.apply("name", event).isPresent()); } @Test public void textChannelAdapter_withExistingId_ShouldReturnMember() { TextChannelAdapter adapter = new TextChannelAdapter(); - assertEquals(GuildMock.TEXT_CHANNEL, adapter.apply(String.valueOf(GuildMock.TEXT_CHANNEL.getIdLong()), context).orElseThrow()); + assertEquals(GuildMock.TEXT_CHANNEL, adapter.apply(String.valueOf(GuildMock.TEXT_CHANNEL.getIdLong()), event).orElseThrow()); } @Test public void textChannelAdapter_withNonExistingId_ShouldBeEmpty() { TextChannelAdapter adapter = new TextChannelAdapter(); - assertFalse(adapter.apply("1234567890", context).isPresent()); + assertFalse(adapter.apply("1234567890", event).isPresent()); } @Test public void textChannelAdapter_withExistingName_ShouldReturnMember() { TextChannelAdapter adapter = new TextChannelAdapter(); - assertEquals(GuildMock.TEXT_CHANNEL, adapter.apply(GuildMock.TEXT_CHANNEL.getName(), context).orElseThrow()); + assertEquals(GuildMock.TEXT_CHANNEL, adapter.apply(GuildMock.TEXT_CHANNEL.getName(), event).orElseThrow()); } @Test public void textChannelAdapter_withNonExistingName_ShouldBeEmpty() { TextChannelAdapter adapter = new TextChannelAdapter(); - assertFalse(adapter.apply("thispersondoesnotexist.com", context).isPresent()); + assertFalse(adapter.apply("thispersondoesnotexist.com", event).isPresent()); } @Test public void textChannelAdapter_inNotGuildContext_ShouldBeEmpty() { TextChannelAdapter adapter = new TextChannelAdapter(); - assertFalse(adapter.apply("name", context).isPresent()); + assertFalse(adapter.apply("name", event).isPresent()); } @Test diff --git a/src/test/java/adapting/mock/JDACommandsMock.java b/src/test/java/adapting/mock/JDACommandsMock.java index 052cd48c8..e5374c9fc 100644 --- a/src/test/java/adapting/mock/JDACommandsMock.java +++ b/src/test/java/adapting/mock/JDACommandsMock.java @@ -1,10 +1,9 @@ package adapting.mock; -import com.github.kaktushose.jda.commands.JDAContext; -import com.github.kaktushose.jda.commands.SlashCommandUpdater; +import com.github.kaktushose.jda.commands.internal.JDAContext; +import com.github.kaktushose.jda.commands.internal.register.SlashCommandUpdater; import com.github.kaktushose.jda.commands.dependency.DependencyInjector; -import com.github.kaktushose.jda.commands.dispatching.DispatcherSupervisor; -import com.github.kaktushose.jda.commands.dispatching.RuntimeSupervisor; +import com.github.kaktushose.jda.commands.dispatching.internal.JDAEventListener; import com.github.kaktushose.jda.commands.dispatching.adapter.TypeAdapterRegistry; import com.github.kaktushose.jda.commands.dispatching.middleware.MiddlewareRegistry; import com.github.kaktushose.jda.commands.dispatching.validation.ValidatorRegistry; @@ -13,13 +12,11 @@ public record JDACommandsMock( JDAContext jdaContext, - DispatcherSupervisor dispatcherSupervisor, + JDAEventListener JDAEventListener, MiddlewareRegistry middlewareRegistry, TypeAdapterRegistry adapterRegistry, ValidatorRegistry validatorRegistry, DependencyInjector dependencyInjector, InteractionRegistry interactionRegistry, - SlashCommandUpdater updater, - RuntimeSupervisor runtimeSupervisor -) { + SlashCommandUpdater updater) { } diff --git a/src/test/java/adapting/mock/TypeAdapterRegistryTestController.java b/src/test/java/adapting/mock/TypeAdapterRegistryTestController.java index e237d74a7..175832c6b 100644 --- a/src/test/java/adapting/mock/TypeAdapterRegistryTestController.java +++ b/src/test/java/adapting/mock/TypeAdapterRegistryTestController.java @@ -4,7 +4,7 @@ import com.github.kaktushose.jda.commands.annotations.interactions.Interaction; import com.github.kaktushose.jda.commands.annotations.interactions.SlashCommand; import com.github.kaktushose.jda.commands.annotations.interactions.Optional; -import com.github.kaktushose.jda.commands.dispatching.interactions.commands.CommandEvent; +import com.github.kaktushose.jda.commands.dispatching.events.interactions.CommandEvent; @Interaction public class TypeAdapterRegistryTestController { diff --git a/src/test/java/commands/CommandDefinitionTestController.java b/src/test/java/commands/CommandDefinitionTestController.java index ed1ac08c6..443a10c49 100644 --- a/src/test/java/commands/CommandDefinitionTestController.java +++ b/src/test/java/commands/CommandDefinitionTestController.java @@ -1,7 +1,7 @@ package commands; import com.github.kaktushose.jda.commands.annotations.interactions.*; -import com.github.kaktushose.jda.commands.dispatching.interactions.commands.CommandEvent; +import com.github.kaktushose.jda.commands.dispatching.events.interactions.CommandEvent; import java.util.concurrent.TimeUnit; diff --git a/src/test/java/commands/SlashCommandDefinitionTest.java b/src/test/java/commands/SlashCommandDefinitionTest.java index eab854146..f976a8f0c 100644 --- a/src/test/java/commands/SlashCommandDefinitionTest.java +++ b/src/test/java/commands/SlashCommandDefinitionTest.java @@ -2,7 +2,7 @@ import com.github.kaktushose.jda.commands.annotations.interactions.Interaction; import com.github.kaktushose.jda.commands.dispatching.adapter.TypeAdapterRegistry; -import com.github.kaktushose.jda.commands.dispatching.interactions.commands.CommandEvent; +import com.github.kaktushose.jda.commands.dispatching.events.interactions.CommandEvent; import com.github.kaktushose.jda.commands.dispatching.validation.ValidatorRegistry; import com.github.kaktushose.jda.commands.reflect.CooldownDefinition; import com.github.kaktushose.jda.commands.reflect.MethodBuildContext; diff --git a/src/test/java/controller/ControllerDefinitionTestController.java b/src/test/java/controller/ControllerDefinitionTestController.java index d098e90fe..472ce9fc7 100644 --- a/src/test/java/controller/ControllerDefinitionTestController.java +++ b/src/test/java/controller/ControllerDefinitionTestController.java @@ -4,7 +4,7 @@ import com.github.kaktushose.jda.commands.annotations.interactions.SlashCommand; import com.github.kaktushose.jda.commands.annotations.interactions.Cooldown; import com.github.kaktushose.jda.commands.annotations.interactions.Permissions; -import com.github.kaktushose.jda.commands.dispatching.interactions.commands.CommandEvent; +import com.github.kaktushose.jda.commands.dispatching.events.interactions.CommandEvent; import java.util.concurrent.TimeUnit; diff --git a/src/test/java/controller/InteractionControllerDefinitionTest.java b/src/test/java/controller/InteractionControllerDefinitionTest.java index 98346f961..09c6f4738 100644 --- a/src/test/java/controller/InteractionControllerDefinitionTest.java +++ b/src/test/java/controller/InteractionControllerDefinitionTest.java @@ -2,7 +2,7 @@ import com.github.kaktushose.jda.commands.dependency.DefaultDependencyInjector; import com.github.kaktushose.jda.commands.dispatching.adapter.TypeAdapterRegistry; -import com.github.kaktushose.jda.commands.dispatching.interactions.commands.CommandEvent; +import com.github.kaktushose.jda.commands.dispatching.events.interactions.CommandEvent; import com.github.kaktushose.jda.commands.dispatching.validation.ValidatorRegistry; import com.github.kaktushose.jda.commands.reflect.InteractionControllerDefinition; import com.github.kaktushose.jda.commands.reflect.interactions.commands.SlashCommandDefinition; diff --git a/src/test/java/reply/CustomController.java b/src/test/java/reply/CustomController.java new file mode 100644 index 000000000..d3ed6aa70 --- /dev/null +++ b/src/test/java/reply/CustomController.java @@ -0,0 +1,29 @@ +package reply; + +import com.github.kaktushose.jda.commands.annotations.interactions.Button; +import com.github.kaktushose.jda.commands.annotations.interactions.Interaction; +import com.github.kaktushose.jda.commands.annotations.interactions.ReplyConfig; +import com.github.kaktushose.jda.commands.dispatching.events.interactions.ComponentEvent; + +@Interaction +@ReplyConfig(ephemeral = true, editReply = false, keepComponents = false) +public class CustomController { + + @Button + public void defaultValues(ComponentEvent event) { + + } + + @Button + @ReplyConfig(ephemeral = true, editReply = false, keepComponents = false) + public void sameValues(ComponentEvent event) { + + } + + @Button + @ReplyConfig + public void customValues(ComponentEvent event) { + + } + +} \ No newline at end of file diff --git a/src/test/java/reply/DefaultController.java b/src/test/java/reply/DefaultController.java new file mode 100644 index 000000000..d6b561a78 --- /dev/null +++ b/src/test/java/reply/DefaultController.java @@ -0,0 +1,21 @@ +package reply; + +import com.github.kaktushose.jda.commands.annotations.interactions.Button; +import com.github.kaktushose.jda.commands.annotations.interactions.Interaction; +import com.github.kaktushose.jda.commands.annotations.interactions.ReplyConfig; +import com.github.kaktushose.jda.commands.dispatching.events.interactions.ComponentEvent; + +@Interaction +public class DefaultController { + + @Button + public void defaultValues(ComponentEvent event) { + + } + + @Button + @ReplyConfig(ephemeral = true, editReply = false, keepComponents = false) + public void customValues(ComponentEvent event) { + + } +} diff --git a/src/test/java/reply/ReplyConfigTest.java b/src/test/java/reply/ReplyConfigTest.java new file mode 100644 index 000000000..5ea237dc5 --- /dev/null +++ b/src/test/java/reply/ReplyConfigTest.java @@ -0,0 +1,68 @@ +package reply; + +import com.github.kaktushose.jda.commands.internal.Helpers; +import com.github.kaktushose.jda.commands.dispatching.events.interactions.ComponentEvent; +import com.github.kaktushose.jda.commands.dispatching.reply.GlobalReplyConfig; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class ReplyConfigTest { + + private static Class defaultController; + private static Class customController; + + @BeforeAll + public static void setup() { + defaultController = DefaultController.class; + customController = CustomController.class; + } + + @Test + public void defaultController_withDefaultMethod_ShouldUseGlobalValues() throws NoSuchMethodException { + var config = Helpers.replyConfig(defaultController.getDeclaredMethod("defaultValues", ComponentEvent.class)); + + assertEquals(config.ephemeral(), GlobalReplyConfig.ephemeral); + assertEquals(config.editReply(), GlobalReplyConfig.editReply); + assertEquals(config.keepComponents(), GlobalReplyConfig.keepComponents); + } + + @Test + public void defaultController_withCustomMethod_ShouldUseMethodValues() throws NoSuchMethodException { + var config = Helpers.replyConfig(defaultController.getDeclaredMethod("customValues", ComponentEvent.class)); + + assertTrue(config.ephemeral()); + assertFalse(config.editReply()); + assertFalse(config.keepComponents()); + } + + @Test + public void customController_withDefaultMethod_ShouldUseControllerValues() throws NoSuchMethodException { + var config = Helpers.replyConfig(customController.getDeclaredMethod("defaultValues", ComponentEvent.class)); + + assertTrue(config.ephemeral()); + assertFalse(config.editReply()); + assertFalse(config.keepComponents()); + } + + @Test + public void customController_withSameCustomMethod_ShouldBeEquals() throws NoSuchMethodException { + var first = Helpers.replyConfig(customController.getDeclaredMethod("defaultValues", ComponentEvent.class)); + var second = Helpers.replyConfig(customController.getDeclaredMethod("sameValues", ComponentEvent.class)); + + assertEquals(first.ephemeral(), second.ephemeral()); + assertEquals(first.editReply(), second.editReply()); + assertEquals(first.keepComponents(), second.keepComponents()); + } + + @Test + public void customController_withDifferentCustomMethod_ShouldUseMethodValues() throws NoSuchMethodException { + var config = Helpers.replyConfig(customController.getDeclaredMethod("customValues", ComponentEvent.class)); + + assertFalse(config.ephemeral()); + assertTrue(config.editReply()); + assertTrue(config.keepComponents()); + } + +} From 2fba5411cd7b658a170ab9b0b5cb04c02e11d9c4 Mon Sep 17 00:00:00 2001 From: Finn | Sachsenspielt Date: Thu, 26 Dec 2024 13:08:29 +0100 Subject: [PATCH 14/38] Update coverage.yml to use java 23 (#151) --- .github/workflows/coverage.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 77d8a4f41..587533b07 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -10,10 +10,11 @@ jobs: steps: - uses: actions/checkout@v2 - - name: Set up JDK 11 - uses: actions/setup-java@v1 + - name: Set up JDK 23 + uses: actions/setup-java@v4 with: - java-version: 11 + distribution: 'temurin' + java-version: '23' - name: Run Tests run: mvn -B test - name: Run codacy-coverage-reporter From 5da6f49e7fb67a92de765938c0a96c6978a66900 Mon Sep 17 00:00:00 2001 From: Nick Hensel <47005420+Goldmensch@users.noreply.github.com> Date: Sun, 5 Jan 2025 14:50:03 +0100 Subject: [PATCH 15/38] Refactor definition building (#149) * first pass on new definition api * make interaction interfaces records and first pass on new registry * update records * fix invocation context * add missing parameter methods * rename * add reflection wrapper * add reflective descriptor * update definitions * update definitions * fix shit ton of compile errors * cleanup * formatting * remove since tag * update documentation * fix unit tests * extract reflections out of InteractionRegistry * Use Iterable instead of List and introduce CustomId#independent * Restructure Definitions package and separate Context and SlashCommand * remove Permissions feature interface * Forbid Definition#invoke by user and fix bug in CustomID * javadocs * Yeah.. * docs * more javadoc * formatting * fix java tests * fix java tests * fix wrong annotation * fix codacy * small changes --------- Co-authored-by: Kaktushose <42280757+Kaktushose@users.noreply.github.com> Co-authored-by: Kaktushose --- pom.xml | 17 ++ .../kaktushose/jda/commands/JDACommands.java | 57 ++-- .../commands/annotations/Implementation.java | 52 ++-- .../jda/commands/annotations/Inject.java | 13 +- .../jda/commands/annotations/Produces.java | 51 ++-- .../annotations/constraints/Constraint.java | 19 +- .../commands/annotations/constraints/Max.java | 25 +- .../commands/annotations/constraints/Min.java | 25 +- .../annotations/constraints/NotPerm.java | 25 +- .../annotations/constraints/NotRole.java | 31 +- .../annotations/constraints/NotUser.java | 32 +-- .../annotations/constraints/Perm.java | 25 +- .../annotations/constraints/Role.java | 31 +- .../annotations/constraints/User.java | 32 +-- .../annotations/constraints/package-info.java | 4 +- .../interactions/AutoComplete.java | 10 +- .../annotations/interactions/Button.java | 33 +-- .../annotations/interactions/Choices.java | 17 +- .../interactions/CommandScope.java | 10 + .../interactions/ContextCommand.java | 64 ++--- .../annotations/interactions/Cooldown.java | 25 +- .../interactions/EntitySelectMenu.java | 87 +++--- .../annotations/interactions/Interaction.java | 10 +- .../annotations/interactions/Modal.java | 10 +- .../annotations/interactions/Optional.java | 35 +-- .../annotations/interactions/Param.java | 27 +- .../annotations/interactions/Permissions.java | 45 +-- .../annotations/interactions/ReplyConfig.java | 1 - .../interactions/SelectOption.java | 51 ++-- .../interactions/SlashCommand.java | 73 ++--- .../interactions/StringSelectMenu.java | 39 ++- .../annotations/interactions/TextInput.java | 76 ++--- .../internal/SelectOptionContainer.java | 11 +- .../annotations/internal/package-info.java | 4 +- .../commands/annotations/package-info.java | 4 +- .../jda/commands/definitions/Definition.java | 27 ++ .../description/ClassDescription.java | 19 ++ .../definitions/description/Description.java | 31 ++ .../definitions/description/Descriptor.java | 8 + .../definitions/description/Invoker.java | 20 ++ .../description/MethodDescription.java | 25 ++ .../description/ParameterDescription.java | 17 ++ .../definitions/description/package-info.java | 6 + .../reflective/ReflectiveClassFinder.java | 28 ++ .../reflective/ReflectiveDescriptor.java | 72 +++++ .../description/reflective/package-info.java | 3 + .../features/CustomIdJDAEntity.java | 27 ++ .../definitions/features/JDAEntity.java | 35 +++ .../features/internal/Invokable.java | 51 ++++ .../definitions/features/package-info.java | 2 + .../interactions/AutoCompleteDefinition.java | 51 ++++ .../definitions/interactions/CustomId.java | 105 +++++++ .../interactions/InteractionDefinition.java | 93 ++++++ .../interactions/InteractionRegistry.java | 203 +++++++++++++ .../interactions/MethodBuildContext.java | 27 ++ .../interactions/ModalDefinition.java | 168 +++++++++++ .../command/CommandDefinition.java | 40 +++ .../command/ContextCommandDefinition.java | 108 +++++++ .../command}/ParameterDefinition.java | 204 +++++++------ .../command/SlashCommandDefinition.java | 192 +++++++++++++ .../interactions/command/package-info.java | 2 + .../component/ButtonDefinition.java | 100 +++++++ .../component/ComponentDefinition.java | 27 ++ .../menu/EntitySelectMenuDefinition.java | 115 ++++++++ .../component/menu/SelectMenuDefinition.java | 24 ++ .../menu/StringSelectMenuDefinition.java | 148 ++++++++++ .../component/menu/package-info.java | 2 + .../interactions/component/package-info.java | 2 + .../interactions/package-info.java | 2 + .../dependency/DefaultDependencyInjector.java | 15 +- .../dependency/DependencyInjector.java | 53 ++-- .../jda/commands/dependency/package-info.java | 4 +- .../ImplementationRegistry.java | 102 +++---- .../{internal => }/JDAEventListener.java | 14 +- .../dispatching/{internal => }/Runtime.java | 10 +- .../dispatching/adapter/TypeAdapter.java | 23 +- .../adapter/TypeAdapterRegistry.java | 69 ++--- .../adapter/impl/AudioChannelAdapter.java | 20 +- .../adapter/impl/BooleanAdapter.java | 20 +- .../dispatching/adapter/impl/ByteAdapter.java | 18 +- .../adapter/impl/CharacterAdapter.java | 18 +- .../adapter/impl/DoubleAdapter.java | 18 +- .../adapter/impl/FloatAdapter.java | 18 +- .../adapter/impl/GuildChannelAdapter.java | 20 +- .../impl/GuildMessageChannelAdapter.java | 20 +- .../adapter/impl/IntegerAdapter.java | 18 +- .../dispatching/adapter/impl/LongAdapter.java | 18 +- .../adapter/impl/MemberAdapter.java | 20 +- .../adapter/impl/NewsChannelAdapter.java | 20 +- .../dispatching/adapter/impl/RoleAdapter.java | 20 +- .../adapter/impl/ShortAdapter.java | 18 +- .../adapter/impl/StageChannelAdapter.java | 20 +- .../adapter/impl/TextChannelAdapter.java | 20 +- .../adapter/impl/ThreadChannelAdapter.java | 20 +- .../dispatching/adapter/impl/UserAdapter.java | 20 +- .../adapter/impl/VoiceChannelAdapter.java | 20 +- .../adapter/impl/package-info.java | 4 +- .../dispatching/adapter/package-info.java | 4 +- .../context/InvocationContext.java | 14 +- .../dispatching/context/KeyValueStore.java | 60 ++-- .../commands/dispatching/events/Event.java | 73 +---- .../events/ModalReplyableEvent.java | 34 +-- .../dispatching/events/ReplyableEvent.java | 56 +++- .../interactions/AutoCompleteEvent.java | 61 ++-- .../events/interactions/CommandEvent.java | 21 +- .../events/interactions/ComponentEvent.java | 23 +- .../events/interactions/ModalEvent.java | 21 +- .../events/interactions/package-info.java | 2 - .../dispatching/events/package-info.java | 2 +- .../{ => expiration}/ExpirationStrategy.java | 3 +- .../handling/AutoCompleteHandler.java | 10 +- .../handling/ComponentHandler.java | 12 +- .../handling/DispatchingContext.java | 8 +- .../dispatching/handling/EventHandler.java | 25 +- .../dispatching/handling/ModalHandler.java | 12 +- .../command/ContextCommandHandler.java | 13 +- .../handling/command/SlashCommandHandler.java | 22 +- .../dispatching/middleware/Middleware.java | 26 +- .../middleware/MiddlewareRegistry.java | 74 ++--- .../dispatching/middleware/Priority.java | 25 +- .../middleware/impl/ConstraintMiddleware.java | 32 +-- .../middleware/impl/CooldownMiddleware.java | 59 ++-- .../impl/PermissionsMiddleware.java | 33 +-- .../commands/dispatching/package-info.java | 4 +- .../commands/dispatching/reply/Component.java | 1 - .../dispatching/reply/ComponentReply.java | 2 - .../dispatching/reply/ConfigurableReply.java | 46 +-- .../dispatching/reply/MessageReply.java | 23 +- .../jda/commands/dispatching/reply/Reply.java | 28 +- .../dispatching/reply/package-info.java | 4 +- .../dispatching/validation/Validator.java | 25 +- .../validation/ValidatorRegistry.java | 66 ++--- .../validation/impl/MaximumValidator.java | 23 +- .../validation/impl/MinimumValidator.java | 25 +- .../impl/NotPermissionValidator.java | 27 +- .../validation/impl/NotRoleValidator.java | 23 +- .../validation/impl/NotUserValidator.java | 23 +- .../validation/impl/PermissionValidator.java | 26 +- .../validation/impl/RoleValidator.java | 25 +- .../validation/impl/UserValidator.java | 23 +- .../validation/impl/package-info.java | 4 +- .../dispatching/validation/package-info.java | 4 +- .../jda/commands/embeds/EmbedCache.java | 93 ++---- .../jda/commands/embeds/EmbedDTO.java | 1 - .../error/DefaultErrorMessageFactory.java | 29 +- .../embeds/error/ErrorMessageFactory.java | 17 +- .../embeds/error/JsonErrorMessageFactory.java | 31 +- .../commands/embeds/error/package-info.java | 4 +- .../jda/commands/internal/Helpers.java | 70 +++-- .../jda/commands/internal/JDAContext.java | 50 +--- .../internal/register/CommandTree.java | 67 ++--- .../register/SlashCommandUpdater.java | 129 +++------ .../commands/internal/register/TreeNode.java | 126 ++++---- .../kaktushose/jda/commands/package-info.java | 4 +- .../DefaultPermissionsProvider.java | 3 +- .../permissions/PermissionsProvider.java | 3 +- .../commands/permissions/package-info.java | 4 +- .../reflect/ConstraintDefinition.java | 13 - .../commands/reflect/CooldownDefinition.java | 32 --- .../InteractionControllerDefinition.java | 152 ---------- .../commands/reflect/InteractionRegistry.java | 184 ------------ .../commands/reflect/MethodBuildContext.java | 26 -- .../commands/reflect/TextInputDefinition.java | 81 ------ .../interactions/AutoCompleteDefinition.java | 71 ----- .../reflect/interactions/CustomId.java | 90 ------ .../EphemeralInteractionDefinition.java | 32 --- .../GenericInteractionDefinition.java | 89 ------ .../reflect/interactions/ModalDefinition.java | 143 --------- .../reflect/interactions/ReplyConfig.java | 24 -- .../commands/ContextCommandDefinition.java | 111 ------- .../commands/GenericCommandDefinition.java | 146 ---------- .../commands/SlashCommandDefinition.java | 271 ------------------ .../components/ButtonDefinition.java | 157 ---------- .../GenericComponentDefinition.java | 35 --- .../menus/EntitySelectMenuDefinition.java | 154 ---------- .../menus/GenericSelectMenuDefinition.java | 79 ----- .../menus/SelectOptionDefinition.java | 45 --- .../menus/StringSelectMenuDefinition.java | 113 -------- .../jda/commands/reflect/package-info.java | 4 - .../scope/DefaultGuildScopeProvider.java | 9 +- .../commands/scope/GuildScopeProvider.java | 21 +- .../jda/commands/scope/package-info.java | 4 +- src/main/java/module-info.java | 19 +- .../java/adapting/mock/JDACommandsMock.java | 8 +- .../commands/SlashCommandDefinitionTest.java | 135 ++++----- .../ControllerDefinitionTestController.java | 38 --- .../InteractionControllerDefinitionTest.java | 101 ------- .../parameters/ParameterDefinitionTest.java | 49 ++-- .../parameters/ParameterTestController.java | 4 - 189 files changed, 3405 insertions(+), 4508 deletions(-) create mode 100644 src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/CommandScope.java create mode 100644 src/main/java/com/github/kaktushose/jda/commands/definitions/Definition.java create mode 100644 src/main/java/com/github/kaktushose/jda/commands/definitions/description/ClassDescription.java create mode 100644 src/main/java/com/github/kaktushose/jda/commands/definitions/description/Description.java create mode 100644 src/main/java/com/github/kaktushose/jda/commands/definitions/description/Descriptor.java create mode 100644 src/main/java/com/github/kaktushose/jda/commands/definitions/description/Invoker.java create mode 100644 src/main/java/com/github/kaktushose/jda/commands/definitions/description/MethodDescription.java create mode 100644 src/main/java/com/github/kaktushose/jda/commands/definitions/description/ParameterDescription.java create mode 100644 src/main/java/com/github/kaktushose/jda/commands/definitions/description/package-info.java create mode 100644 src/main/java/com/github/kaktushose/jda/commands/definitions/description/reflective/ReflectiveClassFinder.java create mode 100644 src/main/java/com/github/kaktushose/jda/commands/definitions/description/reflective/ReflectiveDescriptor.java create mode 100644 src/main/java/com/github/kaktushose/jda/commands/definitions/description/reflective/package-info.java create mode 100644 src/main/java/com/github/kaktushose/jda/commands/definitions/features/CustomIdJDAEntity.java create mode 100644 src/main/java/com/github/kaktushose/jda/commands/definitions/features/JDAEntity.java create mode 100644 src/main/java/com/github/kaktushose/jda/commands/definitions/features/internal/Invokable.java create mode 100644 src/main/java/com/github/kaktushose/jda/commands/definitions/features/package-info.java create mode 100644 src/main/java/com/github/kaktushose/jda/commands/definitions/interactions/AutoCompleteDefinition.java create mode 100644 src/main/java/com/github/kaktushose/jda/commands/definitions/interactions/CustomId.java create mode 100644 src/main/java/com/github/kaktushose/jda/commands/definitions/interactions/InteractionDefinition.java create mode 100644 src/main/java/com/github/kaktushose/jda/commands/definitions/interactions/InteractionRegistry.java create mode 100644 src/main/java/com/github/kaktushose/jda/commands/definitions/interactions/MethodBuildContext.java create mode 100644 src/main/java/com/github/kaktushose/jda/commands/definitions/interactions/ModalDefinition.java create mode 100644 src/main/java/com/github/kaktushose/jda/commands/definitions/interactions/command/CommandDefinition.java create mode 100644 src/main/java/com/github/kaktushose/jda/commands/definitions/interactions/command/ContextCommandDefinition.java rename src/main/java/com/github/kaktushose/jda/commands/{reflect => definitions/interactions/command}/ParameterDefinition.java (54%) create mode 100644 src/main/java/com/github/kaktushose/jda/commands/definitions/interactions/command/SlashCommandDefinition.java create mode 100644 src/main/java/com/github/kaktushose/jda/commands/definitions/interactions/command/package-info.java create mode 100644 src/main/java/com/github/kaktushose/jda/commands/definitions/interactions/component/ButtonDefinition.java create mode 100644 src/main/java/com/github/kaktushose/jda/commands/definitions/interactions/component/ComponentDefinition.java create mode 100644 src/main/java/com/github/kaktushose/jda/commands/definitions/interactions/component/menu/EntitySelectMenuDefinition.java create mode 100644 src/main/java/com/github/kaktushose/jda/commands/definitions/interactions/component/menu/SelectMenuDefinition.java create mode 100644 src/main/java/com/github/kaktushose/jda/commands/definitions/interactions/component/menu/StringSelectMenuDefinition.java create mode 100644 src/main/java/com/github/kaktushose/jda/commands/definitions/interactions/component/menu/package-info.java create mode 100644 src/main/java/com/github/kaktushose/jda/commands/definitions/interactions/component/package-info.java create mode 100644 src/main/java/com/github/kaktushose/jda/commands/definitions/interactions/package-info.java rename src/main/java/com/github/kaktushose/jda/commands/{reflect => dispatching}/ImplementationRegistry.java (83%) rename src/main/java/com/github/kaktushose/jda/commands/dispatching/{internal => }/JDAEventListener.java (86%) rename src/main/java/com/github/kaktushose/jda/commands/dispatching/{internal => }/Runtime.java (94%) rename src/main/java/com/github/kaktushose/jda/commands/dispatching/{ => expiration}/ExpirationStrategy.java (95%) delete mode 100644 src/main/java/com/github/kaktushose/jda/commands/reflect/ConstraintDefinition.java delete mode 100644 src/main/java/com/github/kaktushose/jda/commands/reflect/CooldownDefinition.java delete mode 100644 src/main/java/com/github/kaktushose/jda/commands/reflect/InteractionControllerDefinition.java delete mode 100644 src/main/java/com/github/kaktushose/jda/commands/reflect/InteractionRegistry.java delete mode 100644 src/main/java/com/github/kaktushose/jda/commands/reflect/MethodBuildContext.java delete mode 100644 src/main/java/com/github/kaktushose/jda/commands/reflect/TextInputDefinition.java delete mode 100644 src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/AutoCompleteDefinition.java delete mode 100644 src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/CustomId.java delete mode 100644 src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/EphemeralInteractionDefinition.java delete mode 100644 src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/GenericInteractionDefinition.java delete mode 100644 src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/ModalDefinition.java delete mode 100644 src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/ReplyConfig.java delete mode 100644 src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/commands/ContextCommandDefinition.java delete mode 100644 src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/commands/GenericCommandDefinition.java delete mode 100644 src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/commands/SlashCommandDefinition.java delete mode 100644 src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/components/ButtonDefinition.java delete mode 100644 src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/components/GenericComponentDefinition.java delete mode 100644 src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/components/menus/EntitySelectMenuDefinition.java delete mode 100644 src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/components/menus/GenericSelectMenuDefinition.java delete mode 100644 src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/components/menus/SelectOptionDefinition.java delete mode 100644 src/main/java/com/github/kaktushose/jda/commands/reflect/interactions/components/menus/StringSelectMenuDefinition.java delete mode 100644 src/main/java/com/github/kaktushose/jda/commands/reflect/package-info.java delete mode 100644 src/test/java/controller/ControllerDefinitionTestController.java delete mode 100644 src/test/java/controller/InteractionControllerDefinitionTest.java diff --git a/pom.xml b/pom.xml index f45bedc44..d8298bc0d 100644 --- a/pom.xml +++ b/pom.xml @@ -43,6 +43,23 @@ https://javadoc.io/doc/net.dv8tion/JDA/5.0.2 true + + + apiNote + a + API Note: + + + implSpec + a + Implementation Requirements: + + + implNote + a + Implementation Note: + + diff --git a/src/main/java/com/github/kaktushose/jda/commands/JDACommands.java b/src/main/java/com/github/kaktushose/jda/commands/JDACommands.java index 2e1346a1d..cbeb02375 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/JDACommands.java +++ b/src/main/java/com/github/kaktushose/jda/commands/JDACommands.java @@ -1,13 +1,20 @@ package com.github.kaktushose.jda.commands; +import com.github.kaktushose.jda.commands.annotations.interactions.CommandScope; import com.github.kaktushose.jda.commands.annotations.interactions.EntitySelectMenu; import com.github.kaktushose.jda.commands.annotations.interactions.StringSelectMenu; +import com.github.kaktushose.jda.commands.definitions.description.reflective.ReflectiveClassFinder; +import com.github.kaktushose.jda.commands.definitions.description.reflective.ReflectiveDescriptor; +import com.github.kaktushose.jda.commands.definitions.interactions.CustomId; +import com.github.kaktushose.jda.commands.definitions.interactions.InteractionRegistry; +import com.github.kaktushose.jda.commands.definitions.interactions.component.ButtonDefinition; +import com.github.kaktushose.jda.commands.definitions.interactions.component.menu.SelectMenuDefinition; import com.github.kaktushose.jda.commands.dependency.DefaultDependencyInjector; import com.github.kaktushose.jda.commands.dependency.DependencyInjector; -import com.github.kaktushose.jda.commands.dispatching.ExpirationStrategy; -import com.github.kaktushose.jda.commands.dispatching.internal.JDAEventListener; -import com.github.kaktushose.jda.commands.dispatching.internal.Runtime; +import com.github.kaktushose.jda.commands.dispatching.ImplementationRegistry; +import com.github.kaktushose.jda.commands.dispatching.JDAEventListener; import com.github.kaktushose.jda.commands.dispatching.adapter.TypeAdapterRegistry; +import com.github.kaktushose.jda.commands.dispatching.expiration.ExpirationStrategy; import com.github.kaktushose.jda.commands.dispatching.handling.DispatchingContext; import com.github.kaktushose.jda.commands.dispatching.middleware.MiddlewareRegistry; import com.github.kaktushose.jda.commands.dispatching.middleware.Priority; @@ -17,10 +24,6 @@ import com.github.kaktushose.jda.commands.dispatching.validation.ValidatorRegistry; import com.github.kaktushose.jda.commands.internal.JDAContext; import com.github.kaktushose.jda.commands.internal.register.SlashCommandUpdater; -import com.github.kaktushose.jda.commands.reflect.ImplementationRegistry; -import com.github.kaktushose.jda.commands.reflect.InteractionRegistry; -import com.github.kaktushose.jda.commands.reflect.interactions.components.ButtonDefinition; -import com.github.kaktushose.jda.commands.reflect.interactions.components.menus.GenericSelectMenuDefinition; import net.dv8tion.jda.api.JDA; import net.dv8tion.jda.api.interactions.commands.localization.LocalizationFunction; import net.dv8tion.jda.api.interactions.commands.localization.ResourceBundleLocalizationFunction; @@ -53,7 +56,7 @@ private static JDACommands startInternal(Object jda, Class clazz, Localizatio var adapterRegistry = new TypeAdapterRegistry(); var validatorRegistry = new ValidatorRegistry(); var implementationRegistry = new ImplementationRegistry(dependencyInjector, middlewareRegistry, adapterRegistry, validatorRegistry); - var interactionRegistry = new InteractionRegistry(validatorRegistry, dependencyInjector, function); + var interactionRegistry = new InteractionRegistry(dependencyInjector, validatorRegistry, function, new ReflectiveDescriptor()); middlewareRegistry.register(Priority.PERMISSIONS, new PermissionsMiddleware(implementationRegistry)); middlewareRegistry.register(Priority.NORMAL, new ConstraintMiddleware(implementationRegistry), new CooldownMiddleware(implementationRegistry)); @@ -62,7 +65,7 @@ private static JDACommands startInternal(Object jda, Class clazz, Localizatio implementationRegistry.index(clazz, packages); - interactionRegistry.index(clazz, packages); + interactionRegistry.index(ReflectiveClassFinder.find(clazz, packages)); var updater = new SlashCommandUpdater(jdaContext, implementationRegistry.getGuildScopeProvider(), interactionRegistry); updater.updateAllCommands(); @@ -171,14 +174,14 @@ public void shutdown() { /** * Updates all slash commands that are registered with - * {@link com.github.kaktushose.jda.commands.annotations.interactions.SlashCommand.CommandScope#GUILD + * {@link CommandScope#GUILD * CommandScope#Guild} */ public void updateGuildCommands() { updater.updateGuildCommands(); } - /// Gets a [`Button`][com.github.kaktushose.jda.commands.annotations.interactions.Button] based on the method name + /// Gets a [`Button`][com.github.kaktushose.jda.commands.annotations.interactions.Button] based on the definition id /// and transforms it into a JDA [Button]. /// /// The button will be [`Runtime`]({@docRoot}/index.html#runtime-concept-heading) independent. This may be useful if you want to send a message without @@ -188,38 +191,22 @@ public void updateGuildCommands() { /// @return the JDA [Button] @NotNull public Button getButton(@NotNull String button) { - if (!button.matches("[a-zA-Z]+\\.[a-zA-Z]+")) { - throw new IllegalArgumentException("Unknown Button"); - } - - String sanitizedId = button.replaceAll("\\.", ""); - ButtonDefinition buttonDefinition = interactionRegistry.getButtons().stream() - .filter(it -> it.getDefinitionId().equals(sanitizedId)) - .findFirst().orElseThrow(() -> new IllegalArgumentException("Unknown Button")); - - return buttonDefinition.toButton().withId(buttonDefinition.independentCustomId()); + var id = String.valueOf(button.replaceAll("\\.", "").hashCode()); + var definition = interactionRegistry.find(ButtonDefinition.class, false, it -> it.definitionId().equals(id)); + return definition.toJDAEntity(CustomId.independent(definition.definitionId())); } - /// Gets a [StringSelectMenu] or [EntitySelectMenu] based on the method name and transforms it into a JDA [SelectMenu]. + /// Gets a [StringSelectMenu] or [EntitySelectMenu] based on the definition id and transforms it into a JDA [SelectMenu]. /// /// The select menu will be [`Runtime`]({@docRoot}/index.html#runtime-concept-heading) independent. This may be useful if you want to send a component /// without using the framework. /// - /// @param the type of [SelectMenu] /// @param menu the name of the select menu /// @return the JDA [SelectMenu] - @SuppressWarnings("unchecked") @NotNull - public S getSelectMenu(@NotNull String menu) { - if (!menu.matches("[a-zA-Z]+\\.[a-zA-Z]+")) { - throw new IllegalArgumentException("Unknown Select Menu"); - } - - String sanitizedId = menu.replaceAll("\\.", ""); - GenericSelectMenuDefinition selectMenuDefinition = interactionRegistry.getSelectMenus().stream() - .filter(it -> it.getDefinitionId().equals(sanitizedId)) - .findFirst().orElseThrow(() -> new IllegalArgumentException("Unknown Select Menu")); - - return (S) selectMenuDefinition.toSelectMenu(selectMenuDefinition.independentCustomId(), true); + public SelectMenu getSelectMenu(@NotNull String menu) { + var id = String.valueOf(menu.replaceAll("\\.", "").hashCode()); + var definition = interactionRegistry.find(SelectMenuDefinition.class, false, it -> it.definitionId().equals(id)); + return (SelectMenu) definition.toJDAEntity(CustomId.independent(definition.definitionId())); } } diff --git a/src/main/java/com/github/kaktushose/jda/commands/annotations/Implementation.java b/src/main/java/com/github/kaktushose/jda/commands/annotations/Implementation.java index 99aedd334..6df27a324 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/annotations/Implementation.java +++ b/src/main/java/com/github/kaktushose/jda/commands/annotations/Implementation.java @@ -1,46 +1,40 @@ package com.github.kaktushose.jda.commands.annotations; import com.github.kaktushose.jda.commands.annotations.constraints.Constraint; +import com.github.kaktushose.jda.commands.dispatching.ImplementationRegistry; +import com.github.kaktushose.jda.commands.dispatching.adapter.TypeAdapter; +import com.github.kaktushose.jda.commands.dispatching.middleware.Middleware; import com.github.kaktushose.jda.commands.dispatching.middleware.Priority; +import com.github.kaktushose.jda.commands.dispatching.validation.Validator; import com.github.kaktushose.jda.commands.embeds.error.ErrorMessageFactory; +import com.github.kaktushose.jda.commands.permissions.PermissionsProvider; +import com.github.kaktushose.jda.commands.scope.GuildScopeProvider; import java.lang.annotation.*; -/** - * Indicates that the annotated class is a custom implementation that should replace the default implementation. - * - * @see com.github.kaktushose.jda.commands.reflect.ImplementationRegistry ImplementationRegistry - * @see com.github.kaktushose.jda.commands.dispatching.middleware.Middleware Middleware - * @see com.github.kaktushose.jda.commands.dispatching.validation.Validator Validator - * @see com.github.kaktushose.jda.commands.dispatching.adapter.TypeAdapter TypeAdapter - * @see com.github.kaktushose.jda.commands.permissions.PermissionsProvider PermissionsProvider - * @see com.github.kaktushose.jda.commands.scope.GuildScopeProvider GuildScopeProvider - * @see ErrorMessageFactory ErrorMessageFactory - * @since 2.0.0 - */ +/// Indicates that the annotated class is a custom implementation that should replace the default implementation. +/// +/// @see ImplementationRegistry +/// @see Middleware +/// @see Validator +/// @see TypeAdapter +/// @see PermissionsProvider +/// @see GuildScopeProvider +/// @see ErrorMessageFactory @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Implementation { - /** - * Gets the {@link Priority} to register the - * {@link com.github.kaktushose.jda.commands.dispatching.middleware.Middleware Middleware} with. If this - * implementation is not a subtype - * of {@link com.github.kaktushose.jda.commands.dispatching.middleware.Middleware Middleware}, this field can be - * ignored. - * - * @return the {@link Priority} - */ + /// Gets the [Priority] to register the [Middleware] with. If this implementation is not a subtype of [Middleware], + /// this field can be ignored. + /// + /// @return the [Priority] Priority priority() default Priority.NORMAL; - /** - * Gets the annotation the {@link com.github.kaktushose.jda.commands.dispatching.validation.Validator Validator} - * should be mapped to. If this class is not a subtype of - * {@link com.github.kaktushose.jda.commands.dispatching.validation.Validator Validator}, this field can be ignored. - * - * @return the annotation the {@link com.github.kaktushose.jda.commands.dispatching.validation.Validator Validator} - * should be mapped to - */ + /// Gets the annotation the [Validator] should be mapped to. If this class is not a subtype of [Validator], + /// this field can be ignored. + /// + /// @return the annotation the [Validator] should be mapped to Class annotation() default Constraint.class; } diff --git a/src/main/java/com/github/kaktushose/jda/commands/annotations/Inject.java b/src/main/java/com/github/kaktushose/jda/commands/annotations/Inject.java index 55ccbc257..81371c243 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/annotations/Inject.java +++ b/src/main/java/com/github/kaktushose/jda/commands/annotations/Inject.java @@ -5,14 +5,11 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -/** - * Fields annotated with Inject will be assigned a value that is provided by a {@link Produces} method. - * If no Producer for the field type is available then the field will be assigned {@code null}. Please note, that each - * field type can only have one producer. - * - * @see Produces - * @since 1.0.0 - */ +/// Fields annotated with Inject will be assigned a value that is provided by a [Produces] method. +/// If no Producer for the field type is available then the field will be assigned `null`. Please note, that each +/// field type can only have one producer. +/// +/// @see Produces @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface Inject { diff --git a/src/main/java/com/github/kaktushose/jda/commands/annotations/Produces.java b/src/main/java/com/github/kaktushose/jda/commands/annotations/Produces.java index 63383d2fc..63aa6c485 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/annotations/Produces.java +++ b/src/main/java/com/github/kaktushose/jda/commands/annotations/Produces.java @@ -1,42 +1,37 @@ package com.github.kaktushose.jda.commands.annotations; +import com.github.kaktushose.jda.commands.annotations.interactions.Interaction; +import com.github.kaktushose.jda.commands.dependency.DependencyInjector; + import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -/** - * Methods annotated with Produces will be used to get the instance of a dependency. - * - *

    The instances provided by producer methods are used to inject values to the fields inside a - * {@link com.github.kaktushose.jda.commands.annotations.interactions.Interaction Interaction} - * that are annotated with {@link Inject}. The access modifier of a producer method must be - * public. - * - *

    Classes containing producer methods will be found automatically on startup. They can also be registered via - * {@link com.github.kaktushose.jda.commands.dependency.DependencyInjector#registerProvider(Object) - * DependencyInjector.registerProvider(Object)} - * - *

    Please note that this is only a very basic implementation of dependency injection and can only be used inside - * interaction controller classes or custom {@link Implementation implementations}. Furthermore, each type can only - * have one producer. In other words you cannot register different instances of the same dependency. - * - * @see Inject - * @see com.github.kaktushose.jda.commands.dependency.DependencyInjector DependencyInjector - * @since 1.0.0 - */ +/// Methods annotated with Produces will be used to get the instance of a dependency. +/// +/// The instances provided by producer methods are used to inject values to the fields inside a [Interaction] +/// that are annotated with [Inject]. The access modifier of a producer method must be +/// public. +/// +/// Classes containing producer methods will be found automatically on startup. They can also be registered via +/// [DependencyInjector#registerProvider(Object)] +/// +/// Please note that this is only a very basic implementation of dependency injection and can only be used inside +/// interaction controller classes or custom [Implementation]s. Furthermore, each type can only +/// have one producer. In other words you cannot register different instances of the same dependency. +/// +/// @see Inject +/// @see com.github.kaktushose.jda.commands.dependency.DependencyInjector DependencyInjector @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Produces { - /** - * Whether jda-commands should ignore this method at indexing during startup. Useful if you wish to register your - * dependency providers manually by calling - * {@link com.github.kaktushose.jda.commands.dependency.DependencyInjector#registerProvider(Object) - * DependencyInjector#registerProvider(Object)} - * - * @return Whether jda-commands should ignore this method, default {@code true} - */ + /// Whether jda-commands should ignore this method at indexing during startup. Useful if you wish to register your + /// dependency providers manually by calling + /// [DependencyInjector#registerProvider(Object)] + /// + /// @return Whether jda-commands should ignore this method, default `true` boolean skipIndexing() default false; } diff --git a/src/main/java/com/github/kaktushose/jda/commands/annotations/constraints/Constraint.java b/src/main/java/com/github/kaktushose/jda/commands/annotations/constraints/Constraint.java index 3ed0aef29..e27e1eb5f 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/annotations/constraints/Constraint.java +++ b/src/main/java/com/github/kaktushose/jda/commands/annotations/constraints/Constraint.java @@ -5,21 +5,16 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -/** - * Indicates that an annotation type can be used for parameter validation. When implementing custom validators, the - * annotation type must be annotated with this annotation. - * - * @see com.github.kaktushose.jda.commands.dispatching.validation.ValidatorRegistry ValidatorRegistry - * @since 2.0.0 - */ +/// Indicates that an annotation type can be used for parameter validation. When implementing custom validators, the +/// annotation type must be annotated with this annotation. +/// +/// @see com.github.kaktushose.jda.commands.dispatching.validation.ValidatorRegistry ValidatorRegistry @Target(ElementType.ANNOTATION_TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Constraint { - /** - * Returns an array of all types this annotation can be used for. - * - * @return an array of all types this annotation can be used for - */ + /// Returns an array of all types this annotation can be used for. + /// + /// @return an array of all types this annotation can be used for Class[] value(); } diff --git a/src/main/java/com/github/kaktushose/jda/commands/annotations/constraints/Max.java b/src/main/java/com/github/kaktushose/jda/commands/annotations/constraints/Max.java index 222a92f30..76ba440a8 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/annotations/constraints/Max.java +++ b/src/main/java/com/github/kaktushose/jda/commands/annotations/constraints/Max.java @@ -5,28 +5,21 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -/** - * The annotated element must be a number whose value must be lower or equal to the specified maximum. - * - * @see Constraint - * @since 2.0.0 - */ +/// The annotated element must be a number whose value must be less or equal to the specified maximum. +/// +/// @see Constraint @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) @Constraint({Byte.class, Short.class, Integer.class, Long.class, Float.class, Double.class}) public @interface Max { - /** - * Returns the value the element must be lower or equal to. - * - * @return Returns the value the element must be lower or equal to - */ + /// Returns the value the element must be less or equal to. + /// + /// @return Returns the value the element must be less or equal to long value(); - /** - * Returns the error message that will be displayed if the constraint fails. - * - * @return the error message - */ + /// Returns the error message that will be displayed if the constraint fails. + /// + /// @return the error message String message() default "Parameter exceeds maximum value"; } diff --git a/src/main/java/com/github/kaktushose/jda/commands/annotations/constraints/Min.java b/src/main/java/com/github/kaktushose/jda/commands/annotations/constraints/Min.java index 84af82013..7706fa846 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/annotations/constraints/Min.java +++ b/src/main/java/com/github/kaktushose/jda/commands/annotations/constraints/Min.java @@ -5,28 +5,21 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -/** - * The annotated element must be a number whose value must be greater or equal to the specified minimum. - * - * @see Constraint - * @since 2.0.0 - */ +/// The annotated element must be a number whose value must be greater or equal to the specified minimum. +/// +/// @see Constraint @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) @Constraint({Byte.class, Short.class, Integer.class, Long.class, Float.class, Double.class}) public @interface Min { - /** - * Returns the value the element must be greater or equal to. - * - * @return Returns the value the element must be greater or equal to - */ + /// Returns the value the element must be greater or equal to. + /// + /// @return Returns the value the element must be greater or equal to long value(); - /** - * Returns the error message that will be displayed if the constraint fails. - * - * @return the error message - */ + /// Returns the error message that will be displayed if the constraint fails. + /// + /// @return the error message String message() default "Parameter falls below minimum value"; } diff --git a/src/main/java/com/github/kaktushose/jda/commands/annotations/constraints/NotPerm.java b/src/main/java/com/github/kaktushose/jda/commands/annotations/constraints/NotPerm.java index 68e6fa8a7..8268572f2 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/annotations/constraints/NotPerm.java +++ b/src/main/java/com/github/kaktushose/jda/commands/annotations/constraints/NotPerm.java @@ -8,28 +8,21 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -/** - * The annotated element must be a user or member that doesn't have the specified discord permission. - * - * @see Constraint - * @since 2.0.0 - */ +/// The annotated element must be a user or member that **doesn't** have the specified discord permission. +/// +/// @see Constraint @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) @Constraint({Member.class, User.class}) public @interface NotPerm { - /** - * Returns the discord permission(s) the element must not have. - * - * @return the discord permission(s) the element must not have. - */ + /// Returns the discord permission(s) the element must not have. + /// + /// @return the discord permission(s) the element must not have. String[] value(); - /** - * Returns the error message that will be displayed if the constraint fails. - * - * @return the error message - */ + /// Returns the error message that will be displayed if the constraint fails. + /// + /// @return the error message String message() default "Member or User has at least one permission that isn't allowed"; } diff --git a/src/main/java/com/github/kaktushose/jda/commands/annotations/constraints/NotRole.java b/src/main/java/com/github/kaktushose/jda/commands/annotations/constraints/NotRole.java index c2b8c06f0..32adef342 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/annotations/constraints/NotRole.java +++ b/src/main/java/com/github/kaktushose/jda/commands/annotations/constraints/NotRole.java @@ -1,5 +1,6 @@ package com.github.kaktushose.jda.commands.annotations.constraints; +import com.github.kaktushose.jda.commands.dispatching.adapter.TypeAdapterRegistry; import net.dv8tion.jda.api.entities.Member; import java.lang.annotation.ElementType; @@ -7,31 +8,23 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -/** - * The annotated element must be member that doesn't have the specified guild role. This constraint will use the - * {@link com.github.kaktushose.jda.commands.dispatching.adapter.TypeAdapterRegistry TypeAdapterRegistry} to determine - * the role. - * - * @see Constraint - * @see com.github.kaktushose.jda.commands.dispatching.adapter.impl.RoleAdapter RoleAdapter - * @since 2.0.0 - */ +/// The annotated element must be member that **doesn't** have the specified guild role. This constraint will use the +/// [TypeAdapterRegistry] to determine the role. +/// +/// @see Constraint +/// @see com.github.kaktushose.jda.commands.dispatching.adapter.impl.RoleAdapter RoleAdapter @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) @Constraint({Member.class}) public @interface NotRole { - /** - * Returns the guild role the element must not have. - * - * @return the guild role the element must not have. - */ + /// Returns the guild role the element must not have. + /// + /// @return the guild role the element must not have. String value(); - /** - * Returns the error message that will be displayed if the constraint fails. - * - * @return the error message - */ + /// Returns the error message that will be displayed if the constraint fails. + /// + /// @return the error message String message() default "Member has at least one role that isn't allowed"; } diff --git a/src/main/java/com/github/kaktushose/jda/commands/annotations/constraints/NotUser.java b/src/main/java/com/github/kaktushose/jda/commands/annotations/constraints/NotUser.java index 1f55b3698..809e22045 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/annotations/constraints/NotUser.java +++ b/src/main/java/com/github/kaktushose/jda/commands/annotations/constraints/NotUser.java @@ -1,5 +1,7 @@ package com.github.kaktushose.jda.commands.annotations.constraints; +import com.github.kaktushose.jda.commands.dispatching.adapter.TypeAdapterRegistry; +import com.github.kaktushose.jda.commands.dispatching.adapter.impl.RoleAdapter; import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.User; @@ -8,31 +10,23 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -/** - * The annotated element must not be the specified user or member. This constraint will use the - * {@link com.github.kaktushose.jda.commands.dispatching.adapter.TypeAdapterRegistry TypeAdapterRegistry} to - * determine the user or member. - * - * @see Constraint - * @see com.github.kaktushose.jda.commands.dispatching.adapter.impl.RoleAdapter RoleAdapter - * @since 2.0.0 - */ +/// The annotated element must **not** be the specified user or member. This constraint will use the +/// [TypeAdapterRegistry] to determine the user or member. +/// +/// @see Constraint +/// @see RoleAdapter @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) @Constraint({Member.class, User.class}) public @interface NotUser { - /** - * Returns the user or member the element must not be equals to. - * - * @return the user or member the element must not be equals to - */ + /// Returns the user or member the element must not be equals to. + /// + /// @return the user or member the element must not be equals to String value(); - /** - * Returns the error message that will be displayed if the constraint fails. - * - * @return the error message - */ + /// Returns the error message that will be displayed if the constraint fails. + /// + /// @return the error message String message() default "The given Member or User is invalid as a parameter"; } diff --git a/src/main/java/com/github/kaktushose/jda/commands/annotations/constraints/Perm.java b/src/main/java/com/github/kaktushose/jda/commands/annotations/constraints/Perm.java index 59dc22409..bdf42ea4e 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/annotations/constraints/Perm.java +++ b/src/main/java/com/github/kaktushose/jda/commands/annotations/constraints/Perm.java @@ -8,28 +8,21 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -/** - * The annotated element must be a user or member that have the specified discord permission. - * - * @see Constraint - * @since 2.0.0 - */ +/// The annotated element must be a user or member that have the specified discord permission. +/// +/// @see Constraint @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) @Constraint({Member.class, User.class}) public @interface Perm { - /** - * Returns the discord permission(s) the element must have. - * - * @return the discord permission(s) the element must have. - */ + /// Returns the discord permission(s) the element must have. + /// + /// @return the discord permission(s) the element must have. String[] value(); - /** - * Returns the error message that will be displayed if the constraint fails. - * - * @return the error message - */ + /// Returns the error message that will be displayed if the constraint fails. + /// + /// @return the error message String message() default "Member or User is missing at least one permission that is required"; } diff --git a/src/main/java/com/github/kaktushose/jda/commands/annotations/constraints/Role.java b/src/main/java/com/github/kaktushose/jda/commands/annotations/constraints/Role.java index 0cfc01ef3..58a1a8108 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/annotations/constraints/Role.java +++ b/src/main/java/com/github/kaktushose/jda/commands/annotations/constraints/Role.java @@ -1,5 +1,7 @@ package com.github.kaktushose.jda.commands.annotations.constraints; +import com.github.kaktushose.jda.commands.dispatching.adapter.TypeAdapterRegistry; +import com.github.kaktushose.jda.commands.dispatching.adapter.impl.RoleAdapter; import net.dv8tion.jda.api.entities.Member; import java.lang.annotation.ElementType; @@ -7,30 +9,23 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -/** - * The annotated element must be member that have the specified guild role. This constraint will use the - * {@link com.github.kaktushose.jda.commands.dispatching.adapter.TypeAdapterRegistry} to determine the role. - * - * @see Constraint - * @see com.github.kaktushose.jda.commands.dispatching.adapter.impl.RoleAdapter RoleAdapter - * @since 2.0.0 - */ +/// The annotated element must be member that have the specified guild role. This constraint will use the +/// [TypeAdapterRegistry] to determine the role. +/// +/// @see Constraint +/// @see RoleAdapter @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) @Constraint({Member.class}) public @interface Role { - /** - * Returns the guild role the element must have. - * - * @return the guild role the element must have. - */ + /// Returns the guild role the element must have. + /// + /// @return the guild role the element must have. String value(); - /** - * Returns the error message that will be displayed if the constraint fails. - * - * @return the error message - */ + /// Returns the error message that will be displayed if the constraint fails. + /// + /// @return the error message String message() default "Member or User is missing at least one role that is required"; } diff --git a/src/main/java/com/github/kaktushose/jda/commands/annotations/constraints/User.java b/src/main/java/com/github/kaktushose/jda/commands/annotations/constraints/User.java index 96c3e5f13..048cad8b1 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/annotations/constraints/User.java +++ b/src/main/java/com/github/kaktushose/jda/commands/annotations/constraints/User.java @@ -1,5 +1,7 @@ package com.github.kaktushose.jda.commands.annotations.constraints; +import com.github.kaktushose.jda.commands.dispatching.adapter.TypeAdapterRegistry; +import com.github.kaktushose.jda.commands.dispatching.adapter.impl.RoleAdapter; import net.dv8tion.jda.api.entities.Member; import java.lang.annotation.ElementType; @@ -7,31 +9,23 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -/** - * The annotated element must be the specified user or member. This constraint will use the - * {@link com.github.kaktushose.jda.commands.dispatching.adapter.TypeAdapterRegistry TypeAdapterRegistry} to determine - * the user or member. - * - * @see Constraint - * @see com.github.kaktushose.jda.commands.dispatching.adapter.impl.RoleAdapter RoleAdapter - * @since 2.0.0 - */ +/// The annotated element must be the specified user or member. This constraint will use the +/// [TypeAdapterRegistry] to determine the user or member. +/// +/// @see Constraint +/// @see RoleAdapter @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) @Constraint({Member.class, net.dv8tion.jda.api.entities.User.class}) public @interface User { - /** - * Returns the user or member the element must be equals to. - * - * @return the user or member the element must be equals to - */ + /// Returns the user or member the element must be equals to. + /// + /// @return the user or member the element must be equals to String value(); - /** - * Returns the error message that will be displayed if the constraint fails. - * - * @return the error message - */ + /// Returns the error message that will be displayed if the constraint fails. + /// + /// @return the error message String message() default "The given Member or User is invalid as a parameter"; } diff --git a/src/main/java/com/github/kaktushose/jda/commands/annotations/constraints/package-info.java b/src/main/java/com/github/kaktushose/jda/commands/annotations/constraints/package-info.java index 44c9ff02b..88abd9075 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/annotations/constraints/package-info.java +++ b/src/main/java/com/github/kaktushose/jda/commands/annotations/constraints/package-info.java @@ -1,4 +1,2 @@ -/** - * All annotations needed for parameter validation. - */ +/// All annotations needed for parameter validation. package com.github.kaktushose.jda.commands.annotations.constraints; diff --git a/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/AutoComplete.java b/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/AutoComplete.java index 5af595b39..28fa08b8f 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/AutoComplete.java +++ b/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/AutoComplete.java @@ -23,18 +23,14 @@ /// event.replyChoices(...); /// } /// ``` -/// /// @see SlashCommand -/// @since 4.0.0 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface AutoComplete { - /** - * Returns the name of the slash commands this autocomplete should handle. - * - * @return the slash commands - */ + /// Returns the name of the slash commands this autocomplete should handle. + /// + /// @return the slash commands String[] value(); } diff --git a/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/Button.java b/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/Button.java index 8d51a4073..61573d7d0 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/Button.java +++ b/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/Button.java @@ -28,37 +28,28 @@ /// ``` /// /// @see Interaction -/// @since 2.3.0 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Button { - /** - * Gets the label of the button. - * - * @return the label of the button - */ + /// Gets the label of the button. + /// + /// @return the label of the button String value() default ""; - /** - * Gets the {@link ButtonStyle}. - * - * @return the {@link ButtonStyle} - */ + /// Gets the [ButtonStyle]. + /// + /// @return the [ButtonStyle] ButtonStyle style() default ButtonStyle.PRIMARY; - /** - * Gets the {@link Emoji} of the button. - * - * @return the {@link Emoji} - */ + /// Gets the [Emoji] of the button. + /// + /// @return the [Emoji] String emoji() default ""; - /** - * Gets the link of the button. - * - * @return the link of the button - */ + /// Gets the link of the button. + /// + /// @return the link of the button String link() default ""; } diff --git a/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/Choices.java b/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/Choices.java index bc04a52c3..52f4ac2e4 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/Choices.java +++ b/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/Choices.java @@ -5,21 +5,16 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -/** - * Annotation used to add choices to parameters. - * - * @see SlashCommand - * @since 2.3.0 - */ +/// Annotation used to add choices to parameters. +/// +/// @see SlashCommand @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) public @interface Choices { - /** - * Returns the choices of a parameter. This value will only be used for slash commands. - * - * @return the choices of the parameter - */ + /// Returns the choices of a parameter. This value will only be used for slash commands. + /// + /// @return the choices of the parameter String[] value(); } diff --git a/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/CommandScope.java b/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/CommandScope.java new file mode 100644 index 000000000..e12319159 --- /dev/null +++ b/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/CommandScope.java @@ -0,0 +1,10 @@ +package com.github.kaktushose.jda.commands.annotations.interactions; + +/// Enum describing the two possible scopes a command can be registered for. +/// +/// @see SlashCommand#scope() +/// @see ContextCommand#scope() +public enum CommandScope { + GUILD, + GLOBAL +} diff --git a/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/ContextCommand.java b/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/ContextCommand.java index 602081004..56d498795 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/ContextCommand.java +++ b/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/ContextCommand.java @@ -28,59 +28,45 @@ /// @ContextCommand(value = "user context command", type = Command.Type.USER) /// public void onCommand(CommandEvent event, User target) { ... } /// ``` -/// /// @see Interaction -/// @since 4.0.0 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface ContextCommand { - /** - * Returns the name of the command. - * - * @return the name of the command - */ + /// Returns the name of the command. + /// + /// @return the name of the command String value() default ""; - /** - * Returns whether this command is only usable in a guild. - * This only has an effect if this command is registered globally. - * - * @return {@code true} if this command is only usable in a guild - */ + /// Returns whether this command is only usable in a guild. + /// This only has an effect if this command is registered globally. + /// + /// @return `true` if this command is only usable in a guild boolean isGuildOnly() default false; - /** - * Returns whether this command can only be executed in NSFW channels. - * - * @return {@code true} if this command can only be executed in NSFW channels - */ + /// Returns whether this command can only be executed in NSFW channels. + /// + /// @return `true` if this command can only be executed in NSFW channels boolean isNSFW() default false; - /** - * Returns an array of {@link net.dv8tion.jda.api.Permission Permissions} this command - * should be enabled for by default. Note that guild admins can modify this at any time. - * - * @return an array of permissions this command will be enabled for by default - * @see Permissions Permission - * @see net.dv8tion.jda.api.interactions.commands.DefaultMemberPermissions DefaultMemberPermissions.ENABLED - * @see net.dv8tion.jda.api.interactions.commands.DefaultMemberPermissions DefaultMemberPermissions.DISABLED - */ + /// Returns an array of [Permission] this command + /// should be enabled for by default. Note that guild admins can modify this at any time. + /// + /// @return an array of permissions this command will be enabled for by default + /// @see Permissions Permission + /// @see net.dv8tion.jda.api.interactions.commands.DefaultMemberPermissions DefaultMemberPermissions.ENABLED + /// @see net.dv8tion.jda.api.interactions.commands.DefaultMemberPermissions DefaultMemberPermissions.DISABLED Permission[] enabledFor() default Permission.UNKNOWN; - /** - * Returns whether this command should be registered as a global or as a guild command. - * - * @return whether this command should be registered as a global or as a guild command - * @see SlashCommand.CommandScope - */ - SlashCommand.CommandScope scope() default SlashCommand.CommandScope.GLOBAL; + /// Returns whether this command should be registered as a global or as a guild command. + /// + /// @return whether this command should be registered as a global or as a guild command + /// @see CommandScope + CommandScope scope() default CommandScope.GLOBAL; - /** - * Gets the type of this command. - * - * @return the type of the command - */ + /// Gets the type of this command. + /// + /// @return the type of the command Command.Type type(); } diff --git a/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/Cooldown.java b/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/Cooldown.java index c7c7f1dc4..6ffeac8dd 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/Cooldown.java +++ b/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/Cooldown.java @@ -6,28 +6,21 @@ import java.lang.annotation.Target; import java.util.concurrent.TimeUnit; -/** - * Commands annotated with Cooldown will have a per-user cooldown. - * - * @see com.github.kaktushose.jda.commands.dispatching.middleware.impl.CooldownMiddleware CooldownMiddleware - * @since 1.0.0 - */ +/// Commands annotated with Cooldown will have a per-user cooldown. +/// +/// @see com.github.kaktushose.jda.commands.dispatching.middleware.impl.CooldownMiddleware CooldownMiddleware @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface Cooldown { - /** - * Returns the delay of the cooldown. - * - * @return the delay of the cooldown - */ + /// Returns the delay of the cooldown. + /// + /// @return the delay of the cooldown long value(); - /** - * Returns the {@link TimeUnit} of the specified delay. - * - * @return the {@link TimeUnit} of the specified delay - */ + /// Returns the [TimeUnit] of the specified delay. + /// + /// @return the [TimeUnit] of the specified delay TimeUnit timeUnit(); } diff --git a/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/EntitySelectMenu.java b/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/EntitySelectMenu.java index 5a68978fc..45b253854 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/EntitySelectMenu.java +++ b/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/EntitySelectMenu.java @@ -29,75 +29,60 @@ /// public void onMenu(ComponentEvent event, Mentions mentions) { ... } /// ``` /// @see Interaction -/// @since 2.3.0 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface EntitySelectMenu { - /** - * Supported {@link SelectTarget SelectTargets}. - * - * @return an array of supported {@link SelectTarget SelectTargets} - */ + /// Supported [SelectTarget]s. + /// + /// @return an array of supported [SelectTarget]s SelectTarget[] value(); - /** - * The {@link net.dv8tion.jda.api.interactions.components.selections.EntitySelectMenu.DefaultValue default values} - * for roles that will be shown to the user. - * - * @return the {@link net.dv8tion.jda.api.interactions.components.selections.EntitySelectMenu.DefaultValue default values} - */ + /// The [default values][net.dv8tion.jda.api.interactions.components.selections.EntitySelectMenu.DefaultValue] + /// for roles that will be shown to the user. + /// + /// @return the [default values][net.dv8tion.jda.api.interactions.components.selections.EntitySelectMenu.DefaultValue] long[] defaultRoles() default -1; - /** - * The {@link net.dv8tion.jda.api.interactions.components.selections.EntitySelectMenu.DefaultValue default values} - * for channels that will be shown to the user. - * - * @return the {@link net.dv8tion.jda.api.interactions.components.selections.EntitySelectMenu.DefaultValue default values} - */ + /// The [default values][net.dv8tion.jda.api.interactions.components.selections.EntitySelectMenu.DefaultValue] + /// for channels that will be shown to the user. + /// + /// @return the [default values][net.dv8tion.jda.api.interactions.components.selections.EntitySelectMenu.DefaultValue] long[] defaultChannels() default -1; - /** - * The {@link net.dv8tion.jda.api.interactions.components.selections.EntitySelectMenu.DefaultValue default values} - * for users that will be shown to the user. - * - * @return the {@link net.dv8tion.jda.api.interactions.components.selections.EntitySelectMenu.DefaultValue default values} - */ + /// The [default values][net.dv8tion.jda.api.interactions.components.selections.EntitySelectMenu.DefaultValue] + /// for users that will be shown to the user. + /// + /// @return the [default values][net.dv8tion.jda.api.interactions.components.selections.EntitySelectMenu.DefaultValue] long[] defaultUsers() default -1; - /** - * The {@link ChannelType ChannelTypes} that should be supported by this menu. - * This is only relevant for menus that allow CHANNEL targets. - * - * @return the {@link ChannelType ChannelTypes} that should be supported by this menu - */ + /// The [ChannelType]s that should be supported by this menu. + /// This is only relevant for menus that allow CHANNEL targets. + /// + /// @return the [ChannelType]s that should be supported by this menu ChannelType[] channelTypes() default ChannelType.UNKNOWN; - /** - * Configure the placeholder which is displayed when no selections have been made yet. - * - * @return the placeholder which is displayed when no selections have been made yet - */ + /// Configure the placeholder which is displayed when no selections have been made yet. + /// + /// @return the placeholder which is displayed when no selections have been made yet String placeholder() default ""; - /** - * The minimum amount of values a user has to select. - *
    Default: {@code 1} - * - *

    The minimum must not exceed the amount of available options. - * - * @return the minimum amount of values a user has to select - */ + /// The minimum amount of values a user has to select. + /// + /// Default: `1` + /// + /// The minimum must not exceed the amount of available options. + /// + /// @return the minimum amount of values a user has to select int minValue() default 1; - /** - * The maximum amount of values a user can select. - *
    Default: {@code 1} - * - *

    The maximum must not exceed the amount of available options. - * - * @return the maximum amount of values a user can select - */ + /// The maximum amount of values a user can select. + /// + /// Default: `1` + /// + /// The maximum must not exceed the amount of available options. + /// + /// @return the maximum amount of values a user can select int maxValue() default 1; } diff --git a/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/Interaction.java b/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/Interaction.java index 32c010799..4af52daf6 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/Interaction.java +++ b/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/Interaction.java @@ -22,17 +22,13 @@ /// } /// } /// ``` -/// -/// @since 4.0.0 @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Interaction { - /** - * Returns the base name for slash commands. - * - * @return the base name for slash commands - */ + /// Returns the base name for slash commands. + /// + /// @return the base name for slash commands String value() default ""; } diff --git a/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/Modal.java b/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/Modal.java index a9d86b105..ca56dbfbe 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/Modal.java +++ b/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/Modal.java @@ -19,19 +19,15 @@ /// @Modal("My Modal") /// public void onModal(ModalEvent event, @TextInput("Type here") String input) { ... } /// ``` -/// /// @see Interaction /// @see TextInput -/// @since 4.0.0 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Modal { - /** - * Gets the title of this modal. - * - * @return the title of the modal - */ + /// Gets the title of this modal. + /// + /// @return the title of the modal String value(); } diff --git a/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/Optional.java b/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/Optional.java index 0e5fc05b6..ecf9c1860 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/Optional.java +++ b/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/Optional.java @@ -1,33 +1,28 @@ package com.github.kaktushose.jda.commands.annotations.interactions; +import com.github.kaktushose.jda.commands.dispatching.adapter.TypeAdapterRegistry; + import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -/** - * Parameters annotated with Optional are as the name says optional. - * - *

    More formally if a command has an optional parameter the argument doesn't need to be present to execute the - * command. - * - *

    It is also possible to pass a default value which will be used instead if the argument isn't present. - * The default value will be handled as a normal input and thus the - * {@link com.github.kaktushose.jda.commands.dispatching.adapter.TypeAdapterRegistry TypeAdapterRegistry} - * will try to parse it. If the parsing fails the command will still be executed but with empty or - * possible {@code null} values. - * - * @see SlashCommand - * @since 1.0.0 - */ +/// Parameters annotated with Optional are as the name says optional. +/// +/// More formally if a command has an optional parameter the argument doesn't need to be present to execute the +/// command. +/// +/// It is also possible to pass a default value which will be used instead if the argument isn't present. +/// The default value will be handled as a normal input and thus the [TypeAdapterRegistry] will try to parse it. +/// If the parsing fails the command will still be executed but with empty or possible `null` values. +/// +/// @see SlashCommand @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) public @interface Optional { - /** - * Returns the default value. - * - * @return the default value - */ + /// Returns the default value. + /// + /// @return the default value String value() default ""; } diff --git a/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/Param.java b/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/Param.java index 43077100f..97fdff79d 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/Param.java +++ b/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/Param.java @@ -5,29 +5,22 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -/** - * Annotation used to add a name and description to slash command parameters. - * - * @see SlashCommand - * @since 2.3.0 - */ +/// Annotation used to add a name and description to slash command parameters. +/// +/// @see SlashCommand @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) public @interface Param { - /** - * Returns the description of the parameter. This value will only be used for slash commands. - * - * @return the description of the parameter - */ + /// Returns the description of the parameter. This value will only be used for slash commands. + /// + /// @return the description of the parameter String value() default ""; - /** - * Returns the name of the parameter. Use the compiler flag {@code -parameters} to have the parameter name resolved - * at runtime making this field redundant. - * - * @return the name of the parameter - */ + /// Returns the name of the parameter. Use the compiler flag `-parameters` to have the parameter name resolved + /// at runtime making this field redundant. + /// + /// @return the name of the parameter String name() default ""; } diff --git a/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/Permissions.java b/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/Permissions.java index 792a564a8..99940d58d 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/Permissions.java +++ b/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/Permissions.java @@ -1,33 +1,38 @@ package com.github.kaktushose.jda.commands.annotations.interactions; +import com.github.kaktushose.jda.commands.dispatching.middleware.impl.PermissionsMiddleware; +import com.github.kaktushose.jda.commands.permissions.PermissionsProvider; +import net.dv8tion.jda.api.Permission; + import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -/** - * {@link com.github.kaktushose.jda.commands.annotations.interactions.Interaction Interaction} classes or - * interaction methods (commands, components or modals) annotated with Permission will - * require the user to have the given permissions in order to execute the command. - * - *

    The default implementation of this framework can only handle discord permissions. - * However, the {@link com.github.kaktushose.jda.commands.permissions.PermissionsProvider PermissionsProvider} interface - * allows custom implementations. - * - *

    If a class is annotated with Permission all methods or respectively all interactions will require the given - * permission level. - * - * @see com.github.kaktushose.jda.commands.permissions.PermissionsProvider PermissionsProvider - * @since 1.0.0 - */ +/// [Interaction] classes or +/// interaction methods (commands, components or modals) annotated with Permission will +/// require the user to have the given permissions in order to execute the command. +/// +/// @apiNote This annotation should not be confused with [SlashCommand#enabledFor()] or [ContextCommand#enabledFor()]. +/// The `enabledFor` permissions will be client-side checked by Discord directly, while the [Permissions] annotation +/// will be used for the own permission system of jda-commands. +/// @implNote The [PermissionsMiddleware] will validate the permissions during the middleware phase of the execution +/// chain. The [PermissionsProvider] will be called to check the respective user. The default implementation of the +/// [PermissionsProvider] can only handle Discord permissions([Permission]). +/// +/// ## Example: +/// ``` +/// @Permissions("BAN_MEMBERS") +/// public class BanCommand { ... } +/// ``` +/// +/// @see PermissionsProvider @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface Permissions { - /** - * Returns a String array of all required permissions. - * - * @return a String array of all required permissions. - */ + /// Returns a String array of all required permissions. + /// + /// @return a String array of all required permissions. String[] value(); } diff --git a/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/ReplyConfig.java b/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/ReplyConfig.java index d624a0b74..6189759b6 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/ReplyConfig.java +++ b/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/ReplyConfig.java @@ -26,7 +26,6 @@ /// 4. [GlobalReplyConfig] /// /// @see ReplyableEvent -/// @since 4.0.0 @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface ReplyConfig { diff --git a/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/SelectOption.java b/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/SelectOption.java index de03b476e..e1b7f26c3 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/SelectOption.java +++ b/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/SelectOption.java @@ -5,51 +5,38 @@ import java.lang.annotation.*; -/** - * Used to define {@link net.dv8tion.jda.api.interactions.components.selections.SelectOption SelectOptions} - * for {@link StringSelectMenu StringSelectMenus}. - * - * @see StringSelectMenu - * @since 4.0.0 - */ +/// Used to define [`SelectOptions`][net.dv8tion.jda.api.interactions.components.selections.SelectOption] +/// for [StringSelectMenu]s. +/// +/// @see StringSelectMenu @Repeatable(SelectOptionContainer.class) @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface SelectOption { - /** - * Gets the label of an option. - * - * @return the label - */ + /// Gets the label of an option. + /// + /// @return the label String label(); - /** - * Gets the value of an option. - * - * @return the value - */ + /// Gets the value of an option. + /// + /// @return the value String value(); - /** - * Gets the description of an option. - * - * @return the description - */ + /// Gets the description of an option. + /// + /// @return the description String description() default ""; - /** - * Gets the {@link Emoji} of an option. - * - * @return the {@link Emoji} - */ + /// Gets the [Emoji] of an option. + /// + /// @return the [Emoji] String emoji() default ""; - /** - * Gets whether this option is a default option. - * - * @return {@code true} if this SelectOption is a default option - */ + /// Gets whether this option is a default option. + /// + /// @return `true` if this SelectOption is a default option boolean isDefault() default false; } diff --git a/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/SlashCommand.java b/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/SlashCommand.java index 177f3786d..de40f4898 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/SlashCommand.java +++ b/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/SlashCommand.java @@ -37,72 +37,47 @@ /// public void onCommand(CommandEvent event, String[] arguments) {} /// } /// ``` -/// /// @see Interaction /// @see com.github.kaktushose.jda.commands.annotations.interactions.Interaction Interaction /// @see com.github.kaktushose.jda.commands.annotations.constraints.Constraint Constraint -/// @since 1.0.0 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface SlashCommand { - /** - * Returns the name of the command. - * - * @return the name of the command - */ + /// Returns the name of the command. + /// + /// @return the name of the command String value() default ""; - /** - * Returns whether this command is only usable in a guild. - * This only has an effect if this command is registered globally. - * - * @return {@code true} if this command is only usable in a guild - */ + /// Returns whether this command is only usable in a guild. + /// This only has an effect if this command is registered globally. + /// + /// @return `true` if this command is only usable in a guild boolean isGuildOnly() default false; - /** - * Returns the description of the command. - * - * @return the description of the command - */ + /// Returns the description of the command. + /// + /// @return the description of the command String desc() default "N/A"; - /** - * Returns whether this command can only be executed in NSFW channels. - * - * @return {@code true} if this command can only be executed in NSFW channels - */ + /// Returns whether this command can only be executed in NSFW channels. + /// + /// @return `true` if this command can only be executed in NSFW channels boolean isNSFW() default false; - /** - * Returns an array of {@link net.dv8tion.jda.api.Permission Permissions} this command - * should be enabled for by default. Note that guild admins can modify this at any time. - * - * @return an array of permissions this command will be enabled for by default - * @see Permissions Permission - * @see net.dv8tion.jda.api.interactions.commands.DefaultMemberPermissions DefaultMemberPermissions.ENABLED - * @see net.dv8tion.jda.api.interactions.commands.DefaultMemberPermissions DefaultMemberPermissions.DISABLED - */ + /// Returns an array of [Permission]s this command should be enabled for by default. Note that guild admins can + /// modify this at any time. + /// + /// @return an array of permissions this command will be enabled for by default + /// @see Permissions Permission + /// @see net.dv8tion.jda.api.interactions.commands.DefaultMemberPermissions DefaultMemberPermissions.ENABLED + /// @see net.dv8tion.jda.api.interactions.commands.DefaultMemberPermissions DefaultMemberPermissions.DISABLED Permission[] enabledFor() default Permission.UNKNOWN; - /** - * Returns whether this command should be registered as a global or as a guild command. - * - * @return whether this command should be registered as a global or as a guild command - * @see CommandScope - */ + /// Returns whether this command should be registered as a global or as a guild command. + /// + /// @return whether this command should be registered as a global or as a guild command + /// @see CommandScope CommandScope scope() default CommandScope.GLOBAL; - /** - * Enum describing the two possible scopes a command can be registered for. - * - * @see SlashCommand#scope() - * @since 4.0.0 - */ - enum CommandScope { - GUILD, - GLOBAL - } - } diff --git a/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/StringSelectMenu.java b/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/StringSelectMenu.java index 688f622a6..73ce8ce78 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/StringSelectMenu.java +++ b/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/StringSelectMenu.java @@ -31,36 +31,31 @@ /// ``` /// @see Interaction /// @see SelectOption -/// @since 4.0.0 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface StringSelectMenu { - /** - * Configure the placeholder which is displayed when no selections have been made yet. - * - * @return the placeholder which is displayed when no selections have been made yet - */ + /// Configure the placeholder which is displayed when no selections have been made yet. + /// + /// @return the placeholder which is displayed when no selections have been made yet String value(); - /** - * The minimum amount of values a user has to select. - *
    Default: {@code 1} - * - *

    The minimum must not exceed the amount of available options. - * - * @return the minimum amount of values a user has to select - */ + /// The minimum amount of values a user has to select. + /// + /// Default: `1` + /// + /// The minimum must not exceed the amount of available options. + /// + /// @return the minimum amount of values a user has to select int minValue() default 1; - /** - * The maximum amount of values a user can select. - *
    Default: {@code 1} - * - *

    The maximum must not exceed the amount of available options. - * - * @return the maximum amount of values a user can select - */ + /// The maximum amount of values a user can select. + /// + /// Default: `1` + /// + /// The maximum must not exceed the amount of available options. + /// + /// @return the maximum amount of values a user can select int maxValue() default 1; } diff --git a/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/TextInput.java b/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/TextInput.java index c1de71bc1..156b597b1 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/TextInput.java +++ b/src/main/java/com/github/kaktushose/jda/commands/annotations/interactions/TextInput.java @@ -7,67 +7,53 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -/** - * Annotation used to add TextInputs to {@link Modal Modals}. - * - * @see Modal - * @since 4.0.0 - */ +/// Annotation used to add TextInputs to [Modal]s. +/// +/// @see Modal @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) public @interface TextInput { - /** - * The placeholder of this TextInput - *
    This is the short hint that describes the expected value of the TextInput field. - * - * @return Placeholder - */ + /// The placeholder of this TextInput + /// + /// This is the short hint that describes the expected value of the TextInput field. + /// + /// @return Placeholder String value(); - /** - * The label shown above this text input box - * - * @return Label for the input - */ + /// The label shown above this text input box + /// + /// @return Label for the input String label() default ""; - /** - * The default value of this TextInput. - *
    This sets a pre-populated text for this TextInput field - * - * @return default value - */ + /// The default value of this TextInput. + /// + /// This sets a pre-populated text for this TextInput field + /// + /// @return default value String defaultValue() default ""; - /** - * The minimum length. This is -1 if none has been set. - * - * @return Minimum length or -1 - */ + /// The minimum length. This is -1 if none has been set. + /// + /// @return Minimum length or -1 int minValue() default -1; - /** - * The maximum length. This is -1 if none has been set. - * - * @return Maximum length or -1 - */ + /// The maximum length. This is -1 if none has been set. + /// + /// @return Maximum length or -1 int maxValue() default -1; - /** - * The {@link TextInputStyle TextInputStyle}. The default value is {@link TextInputStyle#PARAGRAPH}. - * - * @return The TextInputStyle - */ + /// The [TextInputStyle]. The default value is [TextInputStyle#PARAGRAPH]. + /// + /// @return The TextInputStyle TextInputStyle style() default TextInputStyle.PARAGRAPH; - /** - * Whether this TextInput is required. - *
    If this is True, the user must populate this TextInput field before they can submit the Modal. - * - * @return True if this TextInput is required - * @see net.dv8tion.jda.api.interactions.components.text.TextInput#isRequired() - */ + /// Whether this TextInput is required. + /// + /// If this is True, the user must populate this TextInput field before they can submit the Modal. + /// + /// @return True if this TextInput is required + /// @see net.dv8tion.jda.api.interactions.components.text.TextInput#isRequired() boolean required() default true; } diff --git a/src/main/java/com/github/kaktushose/jda/commands/annotations/internal/SelectOptionContainer.java b/src/main/java/com/github/kaktushose/jda/commands/annotations/internal/SelectOptionContainer.java index beab34365..83866dd03 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/annotations/internal/SelectOptionContainer.java +++ b/src/main/java/com/github/kaktushose/jda/commands/annotations/internal/SelectOptionContainer.java @@ -1,20 +1,19 @@ package com.github.kaktushose.jda.commands.annotations.internal; import com.github.kaktushose.jda.commands.annotations.interactions.SelectOption; +import org.jetbrains.annotations.ApiStatus; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -/** - * Internal wrapper for repeating {@link SelectOption SelectOptions}. - * - * @see SelectOption - * @since 4.0.0 - */ +/// Internal wrapper for repeating [SelectOption]s. +/// +/// @see SelectOption @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) +@ApiStatus.Internal public @interface SelectOptionContainer { SelectOption[] value(); diff --git a/src/main/java/com/github/kaktushose/jda/commands/annotations/internal/package-info.java b/src/main/java/com/github/kaktushose/jda/commands/annotations/internal/package-info.java index 91bae5373..7c13bc512 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/annotations/internal/package-info.java +++ b/src/main/java/com/github/kaktushose/jda/commands/annotations/internal/package-info.java @@ -1,4 +1,2 @@ -/** - * Annotations that are only used for internal purposes and aren't intended to use in production. - */ +/// Annotations that are only used for internal purposes and aren't intended to use in production. package com.github.kaktushose.jda.commands.annotations.internal; diff --git a/src/main/java/com/github/kaktushose/jda/commands/annotations/package-info.java b/src/main/java/com/github/kaktushose/jda/commands/annotations/package-info.java index 9b50e4c46..9485e3173 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/annotations/package-info.java +++ b/src/main/java/com/github/kaktushose/jda/commands/annotations/package-info.java @@ -1,4 +1,2 @@ -/** - * All annotations that are needed to use this framework. - */ +/// All annotations that are needed to use this framework. package com.github.kaktushose.jda.commands.annotations; diff --git a/src/main/java/com/github/kaktushose/jda/commands/definitions/Definition.java b/src/main/java/com/github/kaktushose/jda/commands/definitions/Definition.java new file mode 100644 index 000000000..a1ec7cca8 --- /dev/null +++ b/src/main/java/com/github/kaktushose/jda/commands/definitions/Definition.java @@ -0,0 +1,27 @@ +package com.github.kaktushose.jda.commands.definitions; + +import com.github.kaktushose.jda.commands.definitions.features.CustomIdJDAEntity; +import com.github.kaktushose.jda.commands.definitions.features.JDAEntity; +import com.github.kaktushose.jda.commands.definitions.features.internal.Invokable; +import com.github.kaktushose.jda.commands.definitions.interactions.InteractionDefinition; +import com.github.kaktushose.jda.commands.definitions.interactions.ModalDefinition; +import com.github.kaktushose.jda.commands.definitions.interactions.command.ParameterDefinition; +import com.github.kaktushose.jda.commands.definitions.interactions.command.SlashCommandDefinition; +import com.github.kaktushose.jda.commands.definitions.interactions.component.menu.StringSelectMenuDefinition; +import org.jetbrains.annotations.NotNull; + +/// The common interface for all interaction definitions and their sub parts, such as parameters or text inputs, etc. +public sealed interface Definition permits CustomIdJDAEntity, Invokable, JDAEntity, InteractionDefinition, + ModalDefinition.TextInputDefinition, ParameterDefinition, ParameterDefinition.ConstraintDefinition, + SlashCommandDefinition.CooldownDefinition, StringSelectMenuDefinition.SelectOptionDefinition { + + /// The id for this definition. Per default this is the hash code of the [#toString()] method. + @NotNull + default String definitionId() { + return String.valueOf(toString().hashCode()); + } + + /// The human-readable name of this definition. + @NotNull + String displayName(); +} diff --git a/src/main/java/com/github/kaktushose/jda/commands/definitions/description/ClassDescription.java b/src/main/java/com/github/kaktushose/jda/commands/definitions/description/ClassDescription.java new file mode 100644 index 000000000..e64468bc6 --- /dev/null +++ b/src/main/java/com/github/kaktushose/jda/commands/definitions/description/ClassDescription.java @@ -0,0 +1,19 @@ +package com.github.kaktushose.jda.commands.definitions.description; + +import org.jetbrains.annotations.NotNull; + +import java.lang.annotation.Annotation; +import java.util.Collection; + +/// A [Description] that describes a class. +/// +/// @param clazz the [Class] this [Description] describes. +/// @param name the name of the class +/// @param annotations a [Collection] of all [Annotation]s this class is annotated with +/// @param methods a [Collection] of all the public [`methods`][MethodDescription] of this class +public record ClassDescription( + @NotNull Class clazz, + @NotNull String name, + @NotNull Collection annotations, + @NotNull Collection methods +) implements Description {} diff --git a/src/main/java/com/github/kaktushose/jda/commands/definitions/description/Description.java b/src/main/java/com/github/kaktushose/jda/commands/definitions/description/Description.java new file mode 100644 index 000000000..a12c469d7 --- /dev/null +++ b/src/main/java/com/github/kaktushose/jda/commands/definitions/description/Description.java @@ -0,0 +1,31 @@ +package com.github.kaktushose.jda.commands.definitions.description; + +import org.jetbrains.annotations.NotNull; + +import java.lang.annotation.Annotation; +import java.util.Collection; +import java.util.Optional; + +/// The common interface of all [Description] types. +/// +/// A [Description] is, as the name says, a description of a class, method or parameter, similar to how +/// `java.lang.reflect` works. +/// +/// @see ClassDescription +/// @see MethodDescription +/// @see ParameterDescription +public sealed interface Description permits ClassDescription, MethodDescription, ParameterDescription { + + /// a possibly-empty [Collection] of all [Annotation]s this element is annotated with. + @NotNull + Collection annotations(); + + /// Gets this element's [Annotation] for the specified type if such an annotation is present + /// + /// @param type the type of the annotation to get + /// @return an [Optional] holding the [Annotation] if present at this element or else an empty [Optional] + @NotNull + default Optional annotation(@NotNull Class type) { + return annotations().stream().filter(type::isInstance).map(type::cast).findFirst(); + } +} diff --git a/src/main/java/com/github/kaktushose/jda/commands/definitions/description/Descriptor.java b/src/main/java/com/github/kaktushose/jda/commands/definitions/description/Descriptor.java new file mode 100644 index 000000000..34fdcd01b --- /dev/null +++ b/src/main/java/com/github/kaktushose/jda/commands/definitions/description/Descriptor.java @@ -0,0 +1,8 @@ +package com.github.kaktushose.jda.commands.definitions.description; + +import java.util.function.Function; + +/// A [Descriptor] takes a [Class] as input and transforms it into a [ClassDescription]. +@FunctionalInterface +public interface Descriptor extends Function, ClassDescription> { +} diff --git a/src/main/java/com/github/kaktushose/jda/commands/definitions/description/Invoker.java b/src/main/java/com/github/kaktushose/jda/commands/definitions/description/Invoker.java new file mode 100644 index 000000000..0d956aa54 --- /dev/null +++ b/src/main/java/com/github/kaktushose/jda/commands/definitions/description/Invoker.java @@ -0,0 +1,20 @@ +package com.github.kaktushose.jda.commands.definitions.description; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.lang.reflect.InvocationTargetException; +import java.util.SequencedCollection; + +/// A functional interface, that allows the invocation of a [MethodDescription]. +@FunctionalInterface +public interface Invoker { + + /// @param instance an instance of the declaring class of the method + /// @param arguments a [SequencedCollection] of the arguments the method should be invoked with + /// @return the result of the method invocation + /// @throws IllegalAccessException if this Method object is enforcing Java language access control and the + /// underlying method is inaccessible + /// @throws InvocationTargetException if an exception was thrown by the invoked method or constructor + @Nullable Object invoke(@NotNull Object instance, @NotNull SequencedCollection arguments) throws IllegalAccessException, InvocationTargetException; +} diff --git a/src/main/java/com/github/kaktushose/jda/commands/definitions/description/MethodDescription.java b/src/main/java/com/github/kaktushose/jda/commands/definitions/description/MethodDescription.java new file mode 100644 index 000000000..acc28bcc2 --- /dev/null +++ b/src/main/java/com/github/kaktushose/jda/commands/definitions/description/MethodDescription.java @@ -0,0 +1,25 @@ +package com.github.kaktushose.jda.commands.definitions.description; + +import org.jetbrains.annotations.NotNull; + +import java.lang.annotation.Annotation; +import java.util.Collection; +import java.util.SequencedCollection; + +/// A [Description] that describes a method. +/// +/// @param declaringClass the declaring [Class] of this method +/// @param returnType the [Class] this method returns +/// @param name the name of the method +/// @param parameters a [SequencedCollection] of the [ParameterDescription]s of this method +/// @param annotations a [Collection] of all [Annotation]s this method is annotated with +/// @param invoker the corresponding [Invoker], used to invoke this method +public record MethodDescription( + @NotNull Class declaringClass, + @NotNull Class returnType, + @NotNull String name, + @NotNull SequencedCollection parameters, + @NotNull Collection annotations, + @NotNull Invoker invoker +) implements Description { +} diff --git a/src/main/java/com/github/kaktushose/jda/commands/definitions/description/ParameterDescription.java b/src/main/java/com/github/kaktushose/jda/commands/definitions/description/ParameterDescription.java new file mode 100644 index 000000000..3816c2273 --- /dev/null +++ b/src/main/java/com/github/kaktushose/jda/commands/definitions/description/ParameterDescription.java @@ -0,0 +1,17 @@ +package com.github.kaktushose.jda.commands.definitions.description; + +import org.jetbrains.annotations.NotNull; + +import java.lang.annotation.Annotation; +import java.util.Collection; + +/// A [Description] that describes a parameter. +/// +/// @param type the [Class] representing the type of this parameter +/// @param name the name of the parameter +/// @param annotations a [Collection] of all [Annotation]s this parameter is annotated with +public record ParameterDescription( + @NotNull Class type, + @NotNull String name, + @NotNull Collection annotations +) implements Description {} diff --git a/src/main/java/com/github/kaktushose/jda/commands/definitions/description/package-info.java b/src/main/java/com/github/kaktushose/jda/commands/definitions/description/package-info.java new file mode 100644 index 000000000..da7dd094b --- /dev/null +++ b/src/main/java/com/github/kaktushose/jda/commands/definitions/description/package-info.java @@ -0,0 +1,6 @@ +/// This packages includes different types of [`Descriptions`][com.github.kaktushose.jda.commands.definitions.description.Description] +/// that contain meta information about classes, methods und parameters, similar to reflections. +/// +/// Descriptions are created by an implementation of [`Descriptions`][com.github.kaktushose.jda.commands.definitions.description.Description] +/// and are used in many places of this framework, resulting in the possible avoidance of java reflections. +package com.github.kaktushose.jda.commands.definitions.description; diff --git a/src/main/java/com/github/kaktushose/jda/commands/definitions/description/reflective/ReflectiveClassFinder.java b/src/main/java/com/github/kaktushose/jda/commands/definitions/description/reflective/ReflectiveClassFinder.java new file mode 100644 index 000000000..3da9ea859 --- /dev/null +++ b/src/main/java/com/github/kaktushose/jda/commands/definitions/description/reflective/ReflectiveClassFinder.java @@ -0,0 +1,28 @@ +package com.github.kaktushose.jda.commands.definitions.description.reflective; + +import com.github.kaktushose.jda.commands.annotations.interactions.Interaction; +import org.jetbrains.annotations.ApiStatus; +import org.reflections.Reflections; +import org.reflections.scanners.Scanners; +import org.reflections.util.ClasspathHelper; +import org.reflections.util.ConfigurationBuilder; +import org.reflections.util.FilterBuilder; + +// temp class +@ApiStatus.Internal +public class ReflectiveClassFinder { + + public static Iterable> find(Class clazz, String[] packages) { + var filter = new FilterBuilder(); + for (String pkg : packages) { + filter.includePackage(pkg); + } + + var config = new ConfigurationBuilder() + .setScanners(Scanners.SubTypes, Scanners.TypesAnnotated) + .setUrls(ClasspathHelper.forClass(clazz)) + .filterInputsBy(filter); + var reflections = new Reflections(config); + return reflections.getTypesAnnotatedWith(Interaction.class); + } +} diff --git a/src/main/java/com/github/kaktushose/jda/commands/definitions/description/reflective/ReflectiveDescriptor.java b/src/main/java/com/github/kaktushose/jda/commands/definitions/description/reflective/ReflectiveDescriptor.java new file mode 100644 index 000000000..f289f3d9d --- /dev/null +++ b/src/main/java/com/github/kaktushose/jda/commands/definitions/description/reflective/ReflectiveDescriptor.java @@ -0,0 +1,72 @@ +package com.github.kaktushose.jda.commands.definitions.description.reflective; + +import com.github.kaktushose.jda.commands.definitions.description.ClassDescription; +import com.github.kaktushose.jda.commands.definitions.description.Descriptor; +import com.github.kaktushose.jda.commands.definitions.description.MethodDescription; +import com.github.kaktushose.jda.commands.definitions.description.ParameterDescription; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.Parameter; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Objects; + +/// An [Descriptor] implementation that uses `java.lang.reflect` to create the [ClassDescription]. +public class ReflectiveDescriptor implements Descriptor { + + /// Transforms the given [Class] into a [ClassDescription]. + /// + /// @param clazz the [Class] to transform + /// @return the [ClassDescription] built from the given [Class] + @NotNull + @Override + public ClassDescription apply(@NotNull Class clazz) { + List methods = Arrays.stream(clazz.getMethods()) + .map(this::method) + .filter(Objects::nonNull) + .toList(); + + return new ClassDescription( + clazz, + clazz.getName(), + toList(clazz.getAnnotations()), + methods + ); + } + + @Nullable + private MethodDescription method(@NotNull Method method) { + if (!Modifier.isPublic(method.getModifiers())) return null; + List parameters = Arrays.stream(method.getParameters()) + .map(this::parameter) + .toList(); + + + return new MethodDescription( + method.getDeclaringClass(), + method.getReturnType(), + method.getName(), + parameters, + toList(method.getAnnotations()), + (instance, arguments) -> method.invoke(instance, arguments.toArray()) + ); + } + + @NotNull + private ParameterDescription parameter(@NotNull Parameter parameter) { + return new ParameterDescription( + parameter.getType(), + parameter.getName(), + toList(parameter.getAnnotations()) + ); + } + + @NotNull + private Collection toList(@NotNull T[] array) { + return Arrays.stream(array).toList(); + } +} diff --git a/src/main/java/com/github/kaktushose/jda/commands/definitions/description/reflective/package-info.java b/src/main/java/com/github/kaktushose/jda/commands/definitions/description/reflective/package-info.java new file mode 100644 index 000000000..1a7e340de --- /dev/null +++ b/src/main/java/com/github/kaktushose/jda/commands/definitions/description/reflective/package-info.java @@ -0,0 +1,3 @@ +/// Contains [`Descriptors`][com.github.kaktushose.jda.commands.definitions.description.Descriptor] that use +/// `java.lang.reflect` to build [`Descriptions][com.github.kaktushose.jda.commands.definitions.description.Description]. +package com.github.kaktushose.jda.commands.definitions.description.reflective; diff --git a/src/main/java/com/github/kaktushose/jda/commands/definitions/features/CustomIdJDAEntity.java b/src/main/java/com/github/kaktushose/jda/commands/definitions/features/CustomIdJDAEntity.java new file mode 100644 index 000000000..b4d76995a --- /dev/null +++ b/src/main/java/com/github/kaktushose/jda/commands/definitions/features/CustomIdJDAEntity.java @@ -0,0 +1,27 @@ +package com.github.kaktushose.jda.commands.definitions.features; + +import com.github.kaktushose.jda.commands.definitions.Definition; +import com.github.kaktushose.jda.commands.definitions.interactions.CustomId; +import com.github.kaktushose.jda.commands.definitions.interactions.ModalDefinition; +import com.github.kaktushose.jda.commands.definitions.interactions.component.ButtonDefinition; +import com.github.kaktushose.jda.commands.definitions.interactions.component.ComponentDefinition; +import com.github.kaktushose.jda.commands.definitions.interactions.component.menu.EntitySelectMenuDefinition; +import com.github.kaktushose.jda.commands.definitions.interactions.component.menu.StringSelectMenuDefinition; +import org.jetbrains.annotations.NotNull; + +/// Indicates that the implementing [Definition] can be transformed into a JDA entity that has a [CustomId]. +/// +/// @see ButtonDefinition +/// @see EntitySelectMenuDefinition +/// @see StringSelectMenuDefinition +/// @see ModalDefinition +public sealed interface CustomIdJDAEntity extends Definition permits ComponentDefinition, ModalDefinition { + + /// Transforms this [Definition] into a JDA entity of the given type [T]. + /// + /// @param customId the [CustomId] to use to build this JDA entity + /// @return a JDA entity of type [T] + @NotNull + T toJDAEntity(@NotNull CustomId customId); + +} diff --git a/src/main/java/com/github/kaktushose/jda/commands/definitions/features/JDAEntity.java b/src/main/java/com/github/kaktushose/jda/commands/definitions/features/JDAEntity.java new file mode 100644 index 000000000..dd2fd6202 --- /dev/null +++ b/src/main/java/com/github/kaktushose/jda/commands/definitions/features/JDAEntity.java @@ -0,0 +1,35 @@ +package com.github.kaktushose.jda.commands.definitions.features; + +import com.github.kaktushose.jda.commands.definitions.Definition; +import com.github.kaktushose.jda.commands.definitions.interactions.ModalDefinition.TextInputDefinition; +import com.github.kaktushose.jda.commands.definitions.interactions.command.CommandDefinition; +import com.github.kaktushose.jda.commands.definitions.interactions.command.ContextCommandDefinition; +import com.github.kaktushose.jda.commands.definitions.interactions.command.ParameterDefinition; +import com.github.kaktushose.jda.commands.definitions.interactions.command.SlashCommandDefinition; +import com.github.kaktushose.jda.commands.definitions.interactions.component.ButtonDefinition; +import com.github.kaktushose.jda.commands.definitions.interactions.component.ComponentDefinition; +import com.github.kaktushose.jda.commands.definitions.interactions.component.menu.EntitySelectMenuDefinition; +import com.github.kaktushose.jda.commands.definitions.interactions.component.menu.StringSelectMenuDefinition; +import com.github.kaktushose.jda.commands.definitions.interactions.component.menu.StringSelectMenuDefinition.SelectOptionDefinition; +import org.jetbrains.annotations.NotNull; + +/// Indicates that the implementing [Definition] can be transformed into a JDA entity. +/// +/// @see ButtonDefinition +/// @see EntitySelectMenuDefinition +/// @see StringSelectMenuDefinition +/// @see SlashCommandDefinition +/// @see ContextCommandDefinition +/// @see ParameterDefinition +/// @see SelectOptionDefinition +/// @see TextInputDefinition +public sealed interface JDAEntity extends Definition + permits ComponentDefinition, TextInputDefinition, CommandDefinition, ParameterDefinition, SelectOptionDefinition { + + /// Transforms this [Definition] into a JDA entity of the given type [T]. + /// + /// @return a JDA entity of type [T] + @NotNull + T toJDAEntity(); + +} diff --git a/src/main/java/com/github/kaktushose/jda/commands/definitions/features/internal/Invokable.java b/src/main/java/com/github/kaktushose/jda/commands/definitions/features/internal/Invokable.java new file mode 100644 index 000000000..0ee833dae --- /dev/null +++ b/src/main/java/com/github/kaktushose/jda/commands/definitions/features/internal/Invokable.java @@ -0,0 +1,51 @@ +package com.github.kaktushose.jda.commands.definitions.features.internal; + +import com.github.kaktushose.jda.commands.definitions.Definition; +import com.github.kaktushose.jda.commands.definitions.description.ClassDescription; +import com.github.kaktushose.jda.commands.definitions.description.MethodDescription; +import com.github.kaktushose.jda.commands.definitions.interactions.InteractionDefinition; +import com.github.kaktushose.jda.commands.dispatching.context.InvocationContext; +import com.github.kaktushose.jda.commands.dispatching.handling.EventHandler; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.lang.reflect.InvocationTargetException; +import java.util.SequencedCollection; + +/// Indicates that the implementing [Definition] is bound to a method that can be invoked. +@ApiStatus.Internal +public sealed interface Invokable extends Definition permits InteractionDefinition { + + Logger log = LoggerFactory.getLogger(Invokable.class); + + /// Invokes the method that this [Definition] is bound to. + /// + /// @param instance an instance of the declaring class of the method + /// @param invocation the corresponding [InvocationContext] + /// @return the result of the method invocation + /// @throws IllegalStateException if the definition must not be invoked at the moment this method gets called + /// @throws IllegalAccessException if the method object is enforcing Java language access control and the + /// underlying method is inaccessible + /// @throws InvocationTargetException if an exception was thrown by the invoked method or constructor + @Nullable + default Object invoke(@NotNull Object instance, @NotNull InvocationContext invocation) throws IllegalAccessException, InvocationTargetException { + if (!EventHandler.INVOCATION_PERMITTED.get()) { + throw new IllegalStateException("The Definition must not be invoked at this point."); + } + SequencedCollection arguments = invocation.arguments(); + + EventHandler.INVOCATION_PERMITTED.set(false); + return methodDescription().invoker().invoke(instance, arguments); + } + + /// The [ClassDescription] of the declaring class of the [#methodDescription()]. + @NotNull + ClassDescription clazzDescription(); + + /// The [MethodDescription] of the method this [Definition] is bound to. + @NotNull + MethodDescription methodDescription(); +} diff --git a/src/main/java/com/github/kaktushose/jda/commands/definitions/features/package-info.java b/src/main/java/com/github/kaktushose/jda/commands/definitions/features/package-info.java new file mode 100644 index 000000000..d4202ea90 --- /dev/null +++ b/src/main/java/com/github/kaktushose/jda/commands/definitions/features/package-info.java @@ -0,0 +1,2 @@ +/// This package contains interfaces that implement some form of functionality for a definition, that not all definitions might share. +package com.github.kaktushose.jda.commands.definitions.features; \ No newline at end of file diff --git a/src/main/java/com/github/kaktushose/jda/commands/definitions/interactions/AutoCompleteDefinition.java b/src/main/java/com/github/kaktushose/jda/commands/definitions/interactions/AutoCompleteDefinition.java new file mode 100644 index 000000000..bb60afb20 --- /dev/null +++ b/src/main/java/com/github/kaktushose/jda/commands/definitions/interactions/AutoCompleteDefinition.java @@ -0,0 +1,51 @@ +package com.github.kaktushose.jda.commands.definitions.interactions; + +import com.github.kaktushose.jda.commands.annotations.interactions.AutoComplete; +import com.github.kaktushose.jda.commands.definitions.description.ClassDescription; +import com.github.kaktushose.jda.commands.definitions.description.MethodDescription; +import com.github.kaktushose.jda.commands.dispatching.events.interactions.AutoCompleteEvent; +import com.github.kaktushose.jda.commands.internal.Helpers; +import org.jetbrains.annotations.NotNull; + +import java.util.*; +import java.util.stream.Collectors; + +/// Representation of an auto complete handler. +/// +/// @param clazzDescription the [ClassDescription] of the declaring class of the [#methodDescription()] +/// @param methodDescription the [MethodDescription] of the method this definition is bound to +/// @param commands the commands this autocomplete handler can handle +public record AutoCompleteDefinition(@NotNull ClassDescription clazzDescription, + @NotNull MethodDescription methodDescription, + @NotNull Set commands) + implements InteractionDefinition { + + /// Builds a new [AutoCompleteDefinition] from the given class and method description. + /// + /// @param clazz the corresponding [ClassDescription] + /// @param method the corresponding [MethodDescription] + /// @return an [Optional] holding the [AutoCompleteDefinition] + @NotNull + public static Optional build(@NotNull ClassDescription clazz, @NotNull MethodDescription method) { + if (Helpers.checkSignature(method, List.of(AutoCompleteEvent.class))) { + return Optional.empty(); + } + return method.annotation(AutoComplete.class).map(complete -> + new AutoCompleteDefinition(clazz, method, Arrays.stream(complete.value()).collect(Collectors.toSet())) + ); + + } + + @NotNull + @Override + public String displayName() { + return "%s.%s".formatted(methodDescription.declaringClass().getName(), methodDescription.name()); + } + + @NotNull + @Override + public Collection permissions() { + return Collections.emptyList(); + } + +} diff --git a/src/main/java/com/github/kaktushose/jda/commands/definitions/interactions/CustomId.java b/src/main/java/com/github/kaktushose/jda/commands/definitions/interactions/CustomId.java new file mode 100644 index 000000000..979f83798 --- /dev/null +++ b/src/main/java/com/github/kaktushose/jda/commands/definitions/interactions/CustomId.java @@ -0,0 +1,105 @@ +package com.github.kaktushose.jda.commands.definitions.interactions; + +import com.github.kaktushose.jda.commands.definitions.Definition; +import net.dv8tion.jda.api.events.interaction.GenericInteractionCreateEvent; +import net.dv8tion.jda.api.events.interaction.ModalInteractionEvent; +import net.dv8tion.jda.api.events.interaction.component.GenericComponentInteractionCreateEvent; +import org.jetbrains.annotations.NotNull; + +/// Representation of a custom id used in modals, buttons or select menus. +/// +/// @param runtimeId the id of the [`Runtime`]({@docRoot}/index.html#runtime-concept-heading) this custom id is bound to +/// or the literal `independent`. +/// @param definitionId the [Definition#definitionId()] +/// @implNote the custom id has the following format: `jdac.runtimeId.definitionId` +public record CustomId(@NotNull String runtimeId, @NotNull String definitionId) { + private static final String PREFIX = "jdac"; + private static final String INDEPENDENT_ID = "independent"; + public static final String BOUND_CUSTOM_ID_REGEX = "^jdac\\.[0-9a-fA-F-]{36}\\.-?\\d+$"; + public static final String INDEPENDENT_CUSTOM_ID_REGEX = "^jdac\\.%s\\.-?\\d+$".formatted(INDEPENDENT_ID); + + public CustomId { + if (!runtimeId.matches("[0-9a-fA-F-]{36}") && !runtimeId.equals(INDEPENDENT_ID)) { + throw new IllegalArgumentException("Invalid runtime id! Must either be a UUID or \"%s\"".formatted(INDEPENDENT_ID)); + } + } + + /// Constructs a new [CustomId] from the given [GenericInteractionCreateEvent]. + /// + /// @param event the [GenericInteractionCreateEvent] + /// @return the [CustomId] + @NotNull + public static CustomId fromEvent(@NotNull GenericComponentInteractionCreateEvent event) { + return fromEvent(event.getComponentId()); + } + + /// Constructs a new [CustomId] from the given [ModalInteractionEvent]. + /// + /// @param event the [ModalInteractionEvent] + /// @return the [CustomId] + @NotNull + public static CustomId fromEvent(@NotNull ModalInteractionEvent event) { + return fromEvent(event.getModalId()); + } + + /// Constructs a new [CustomId] from the given String. + /// + /// @param customId the custom id String + /// @return the [CustomId] + @NotNull + private static CustomId fromEvent(@NotNull String customId) { + if (isInvalid(customId)) { + throw new IllegalArgumentException("Provided custom id is invalid!"); + } + var split = customId.split("\\."); + return new CustomId(split[1], split[2]); + } + + /// Constructs a new runtime-independent [CustomId] from the given definition id. + /// + /// @param definitionId the definition id to construct the [CustomId] from + /// @return a new runtime-independent [CustomId] + @NotNull + public static CustomId independent(@NotNull String definitionId) { + return new CustomId(INDEPENDENT_ID, definitionId); + } + + /// Checks if the passed custom id *doesn't* conform to the defined format of jda-commands. + /// + /// @return `true` if the passed custom id *doesn't* conform to the jda-commands format + public static boolean isInvalid(@NotNull String customId) { + return !(customId.matches(BOUND_CUSTOM_ID_REGEX) || customId.matches(INDEPENDENT_CUSTOM_ID_REGEX)); + } + + /// The String representation of this custom id. + @NotNull + public String id() { + return "%s.%s.%s".formatted(PREFIX, runtimeId, definitionId); + } + + /// Gets the runtime id of this custom id. + /// + /// @return the runtime id + /// @throws IllegalStateException if this custom id is runtime-independent + @NotNull + public String runtimeId() { + if (isIndependent()) { + throw new IllegalStateException("Provided custom id is runtime-independent!"); + } + return runtimeId; + } + + /// Checks if the passed custom id is runtime-independent. + /// + /// @return `true` if the custom id is runtime-independent + public boolean isIndependent() { + return runtimeId.equals(INDEPENDENT_ID); + } + + /// Checks if the passed custom id is runtime-bound. + /// + /// @return `true` if the custom id is runtime-bound + public boolean isBound() { + return !isIndependent(); + } +} diff --git a/src/main/java/com/github/kaktushose/jda/commands/definitions/interactions/InteractionDefinition.java b/src/main/java/com/github/kaktushose/jda/commands/definitions/interactions/InteractionDefinition.java new file mode 100644 index 000000000..4ac5c32a8 --- /dev/null +++ b/src/main/java/com/github/kaktushose/jda/commands/definitions/interactions/InteractionDefinition.java @@ -0,0 +1,93 @@ +package com.github.kaktushose.jda.commands.definitions.interactions; + +import com.github.kaktushose.jda.commands.annotations.interactions.Interaction; +import com.github.kaktushose.jda.commands.definitions.Definition; +import com.github.kaktushose.jda.commands.definitions.features.internal.Invokable; +import com.github.kaktushose.jda.commands.definitions.interactions.command.CommandDefinition; +import com.github.kaktushose.jda.commands.definitions.interactions.command.ContextCommandDefinition; +import com.github.kaktushose.jda.commands.definitions.interactions.command.SlashCommandDefinition; +import com.github.kaktushose.jda.commands.definitions.interactions.component.ButtonDefinition; +import com.github.kaktushose.jda.commands.definitions.interactions.component.ComponentDefinition; +import com.github.kaktushose.jda.commands.definitions.interactions.component.menu.EntitySelectMenuDefinition; +import com.github.kaktushose.jda.commands.definitions.interactions.component.menu.StringSelectMenuDefinition; +import com.github.kaktushose.jda.commands.dispatching.middleware.impl.PermissionsMiddleware; +import com.github.kaktushose.jda.commands.dispatching.reply.GlobalReplyConfig; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +import java.lang.reflect.InvocationTargetException; +import java.util.Collection; + +/// Common interface for all definitions that represent an interaction. +/// +/// @see AutoCompleteDefinition +/// @see ModalDefinition +/// @see ContextCommandDefinition +/// @see SlashCommandDefinition +/// @see ButtonDefinition +/// @see EntitySelectMenuDefinition +/// @see StringSelectMenuDefinition +public sealed interface InteractionDefinition extends Definition, Invokable + permits AutoCompleteDefinition, ModalDefinition, CommandDefinition, ComponentDefinition { + + /// The id for this definition. For interaction definition this is the hash code of the full class name and method + /// name combined. + @NotNull + @Override + default String definitionId() { + return String.valueOf((clazzDescription().clazz().getName() + methodDescription().name()).hashCode()); + } + + /// Creates a new instance of the [Interaction] class. + /// + /// @return a new instance of the declaring class of the method this definition is bound to. + /// @throws InvocationTargetException if the object creation fails + /// @throws InstantiationException if the object creation fails + /// @throws IllegalAccessException if the object creation fails + @ApiStatus.Internal + default Object newInstance() throws InvocationTargetException, InstantiationException, IllegalAccessException { + return clazzDescription().clazz().getConstructors()[0].newInstance(); + } + + /// A possibly-empty [Collection] of permissions for this interaction. + /// + /// @apiNote The [PermissionsMiddleware] will validate the provided permissions. + @NotNull + Collection permissions(); + + /// The [ReplyConfig] that should be used when sending replies. + /// + /// @implNote This will first attempt to use the [`ReplyConfig`][com.github.kaktushose.jda.commands.annotations.interactions.ReplyConfig] + /// annotation of the method and then of the class. If neither is present will fall back to the [GlobalReplyConfig]. + @NotNull + default ReplyConfig replyConfig() { + var global = clazzDescription().annotation(com.github.kaktushose.jda.commands.annotations.interactions.ReplyConfig.class); + var local = methodDescription().annotation(com.github.kaktushose.jda.commands.annotations.interactions.ReplyConfig.class); + + if (global.isEmpty() && local.isEmpty()) { + return new ReplyConfig(); + } + + return local.map(ReplyConfig::new).orElseGet(() -> new ReplyConfig(global.get())); + + } + + /// Stores the configuration values for sending replies. This acts as a representation of + /// [`ReplyConfig`][com.github.kaktushose.jda.commands.annotations.interactions.ReplyConfig]. + /// + /// @see com.github.kaktushose.jda.commands.annotations.interactions.ReplyConfig ReplyConfig + record ReplyConfig(boolean ephemeral, boolean keepComponents, boolean editReply) { + + /// Constructs a new ReplyConfig using the default values specified by [GlobalReplyConfig]. + public ReplyConfig() { + this(GlobalReplyConfig.ephemeral, GlobalReplyConfig.keepComponents, GlobalReplyConfig.editReply); + } + + /// Constructs a new ReplyConfig. + /// + /// @param replyConfig the [`ReplyConfig`][com.github.kaktushose.jda.commands.annotations.interactions.ReplyConfig] to represent + public ReplyConfig(@NotNull com.github.kaktushose.jda.commands.annotations.interactions.ReplyConfig replyConfig) { + this(replyConfig.ephemeral(), replyConfig.keepComponents(), replyConfig.editReply()); + } + } +} diff --git a/src/main/java/com/github/kaktushose/jda/commands/definitions/interactions/InteractionRegistry.java b/src/main/java/com/github/kaktushose/jda/commands/definitions/interactions/InteractionRegistry.java new file mode 100644 index 000000000..67bce5390 --- /dev/null +++ b/src/main/java/com/github/kaktushose/jda/commands/definitions/interactions/InteractionRegistry.java @@ -0,0 +1,203 @@ +package com.github.kaktushose.jda.commands.definitions.interactions; + +import com.github.kaktushose.jda.commands.annotations.Inject; +import com.github.kaktushose.jda.commands.annotations.interactions.*; +import com.github.kaktushose.jda.commands.definitions.Definition; +import com.github.kaktushose.jda.commands.definitions.description.ClassDescription; +import com.github.kaktushose.jda.commands.definitions.description.Descriptor; +import com.github.kaktushose.jda.commands.definitions.description.MethodDescription; +import com.github.kaktushose.jda.commands.definitions.interactions.command.ContextCommandDefinition; +import com.github.kaktushose.jda.commands.definitions.interactions.command.SlashCommandDefinition; +import com.github.kaktushose.jda.commands.definitions.interactions.component.ButtonDefinition; +import com.github.kaktushose.jda.commands.definitions.interactions.component.menu.EntitySelectMenuDefinition; +import com.github.kaktushose.jda.commands.definitions.interactions.component.menu.StringSelectMenuDefinition; +import com.github.kaktushose.jda.commands.dependency.DependencyInjector; +import com.github.kaktushose.jda.commands.dispatching.validation.ValidatorRegistry; +import net.dv8tion.jda.api.interactions.commands.localization.LocalizationFunction; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Optional; +import java.util.*; +import java.util.function.Predicate; + +import static com.github.kaktushose.jda.commands.definitions.interactions.command.SlashCommandDefinition.CooldownDefinition; + +/// Central registry for all [InteractionDefinition]s. +public record InteractionRegistry(DependencyInjector dependencyInjector, + ValidatorRegistry validatorRegistry, + LocalizationFunction localizationFunction, + Descriptor descriptor, + Set definitions +) { + + private static final Logger log = LoggerFactory.getLogger(InteractionRegistry.class); + + /// Constructs a new [InteractionRegistry] + /// + /// @param injector the [DependencyInjector] to use + /// @param registry the corresponding [ValidatorRegistry] + /// @param function the [LocalizationFunction] to use + /// @param descriptor the [Descriptor] to use + public InteractionRegistry(DependencyInjector injector, ValidatorRegistry registry, LocalizationFunction function, Descriptor descriptor) { + this(injector, registry, function, descriptor, new HashSet<>()); + } + + /// Scans all given classes and registers the interactions defined in them. + /// + /// @param classes the [Class]es to build the interactions from + public void index(Iterable> classes) { + int oldSize = definitions.size(); + + int count = 0; + for (Class clazz : classes) { + log.debug("Found interaction controller {}", clazz.getName()); + definitions.addAll(indexInteractionClass(descriptor.apply(clazz))); + count++; + } + + log.debug("Successfully registered {} interaction controller(s) with a total of {} interaction(s)!", + count, + definitions.size() - oldSize); + } + + private Collection indexInteractionClass(ClassDescription clazz) { + var interaction = clazz.annotation(Interaction.class).orElseThrow(); + + var fields = Arrays.stream(clazz.clazz().getDeclaredFields()) + .filter(field -> field.isAnnotationPresent(Inject.class)) + .toList(); + dependencyInjector.registerDependencies(clazz.clazz(), fields); + + final Set permissions = clazz.annotation(Permissions.class).map(value -> Set.of(value.value())).orElseGet(Set::of); + // get controller level cooldown and use it if no command level cooldown is present + CooldownDefinition cooldown = clazz.annotation(Cooldown.class).map(CooldownDefinition::build).orElse(null); + + var autoCompletes = autoCompleteDefinitions(clazz); + + // index interactions + var interactionDefinitions = interactionDefinitions( + clazz, + validatorRegistry, + localizationFunction, + interaction, + permissions, + cooldown, + autoCompletes + ); + + // validate auto completes + var commandDefinitions = interactionDefinitions.stream() + .filter(SlashCommandDefinition.class::isInstance) + .map(SlashCommandDefinition.class::cast) + .toList(); + + autoCompletes.stream() + .map(AutoCompleteDefinition::commands) + .flatMap(Collection::stream) + .filter(name -> commandDefinitions.stream().noneMatch(command -> command.name().startsWith(name))) + .forEach(s -> log.warn("No Command found for auto complete {}", s)); + + return interactionDefinitions; + } + + private Collection autoCompleteDefinitions(ClassDescription clazz) { + return clazz.methods().stream() + .filter(it -> it.annotation(AutoComplete.class).isPresent()) + .map(method -> AutoCompleteDefinition.build(clazz, method)) + .filter(Optional::isPresent) + .map(Optional::get) + .toList(); + } + + private Set interactionDefinitions(ClassDescription clazz, + ValidatorRegistry validatorRegistry, + LocalizationFunction localizationFunction, + Interaction interaction, + Set permissions, + CooldownDefinition cooldown, + Collection autocompletes) { + Set definitions = new HashSet<>(autocompletes); + for (MethodDescription method : clazz.methods()) { + final MethodBuildContext context = new MethodBuildContext( + validatorRegistry, + localizationFunction, + interaction, + permissions, + cooldown, + clazz, + method, + autocompletes + ); + + Optional definition = Optional.empty(); + // index commands + if (method.annotation(SlashCommand.class).isPresent()) { + definition = SlashCommandDefinition.build(context); + } + if (method.annotation(ContextCommand.class).isPresent()) { + definition = ContextCommandDefinition.build(context); + } + + // index components + if (method.annotation(Button.class).isPresent()) { + definition = ButtonDefinition.build(context); + } + if (method.annotation(EntitySelectMenu.class).isPresent()) { + definition = EntitySelectMenuDefinition.build(context); + } + if (method.annotation(StringSelectMenu.class).isPresent()) { + definition = StringSelectMenuDefinition.build(context); + } + + //index modals + if (method.annotation(Modal.class).isPresent()) { + definition = ModalDefinition.build(context); + } + + definition.ifPresent(definitions::add); + } + return definitions; + } + + /// Attempts to find a [Definition] of type [T] based on the given [Predicate]. + /// + /// @param type the type of the [Definition] to find + /// @param internalError `true` if the [Definition] must be found and not finding it + /// indicates a framework bug + /// @param predicate the [Predicate] used to find the [Definition] + /// @param a subtype of [Definition] + /// @return [T] + /// @throws IllegalStateException if no [Definition] was found, although this mandatory should have been the case. + /// This is a rare occasion and can be considered a framework bug + /// @throws IllegalArgumentException if no [Definition] was found, because the [Predicate] didn't include any elements + public T find(Class type, boolean internalError, Predicate predicate) { + return definitions.stream() + .filter(type::isInstance) + .map(type::cast) + .filter(predicate) + .findFirst() + .orElseThrow(() -> internalError + ? new IllegalStateException("No interaction found! Please report this error the the devs of jda-commands.") + : new IllegalArgumentException("No interaction found! Please check that the referenced interaction method exists.") + ); + } + + /// Attempts to find all [Definition]s of type [T] based on the given [Predicate]. + /// + /// @param type the type of the [Definition] to find + /// indicates a framework bug + /// @param predicate the [Predicate] used to find the [Definition]s + /// @param a subtype of [Definition] + /// @return a possibly-empty [Collection] of all [Definition]s that match the given [Predicate] + /// @throws IllegalStateException if no [Definition] was found, although this mandatory should have been the case. + /// This is a rare occasion and can be considered a framework bug + /// @throws IllegalArgumentException if no [Definition] was found, because the [Predicate] didn't include any elements + public Collection find(Class type, Predicate predicate) { + return definitions.stream() + .filter(type::isInstance) + .map(type::cast) + .filter(predicate) + .toList(); + } +} diff --git a/src/main/java/com/github/kaktushose/jda/commands/definitions/interactions/MethodBuildContext.java b/src/main/java/com/github/kaktushose/jda/commands/definitions/interactions/MethodBuildContext.java new file mode 100644 index 000000000..5f7c0717f --- /dev/null +++ b/src/main/java/com/github/kaktushose/jda/commands/definitions/interactions/MethodBuildContext.java @@ -0,0 +1,27 @@ +package com.github.kaktushose.jda.commands.definitions.interactions; + +import com.github.kaktushose.jda.commands.annotations.interactions.Interaction; +import com.github.kaktushose.jda.commands.definitions.description.ClassDescription; +import com.github.kaktushose.jda.commands.definitions.description.MethodDescription; +import com.github.kaktushose.jda.commands.definitions.interactions.command.SlashCommandDefinition.CooldownDefinition; +import com.github.kaktushose.jda.commands.dispatching.validation.ValidatorRegistry; +import net.dv8tion.jda.api.interactions.commands.localization.LocalizationFunction; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; +import java.util.Set; + +/// Holds all objects needed to create an [InteractionDefinition]. +@ApiStatus.Internal +public record MethodBuildContext( + @NotNull ValidatorRegistry validatorRegistry, + @NotNull LocalizationFunction localizationFunction, + Interaction interaction, + @NotNull Set permissions, + @Nullable CooldownDefinition cooldownDefinition, + @NotNull ClassDescription clazz, + @NotNull MethodDescription method, + @NotNull Collection autoCompleteDefinitions +) {} diff --git a/src/main/java/com/github/kaktushose/jda/commands/definitions/interactions/ModalDefinition.java b/src/main/java/com/github/kaktushose/jda/commands/definitions/interactions/ModalDefinition.java new file mode 100644 index 000000000..dc95bc45c --- /dev/null +++ b/src/main/java/com/github/kaktushose/jda/commands/definitions/interactions/ModalDefinition.java @@ -0,0 +1,168 @@ +package com.github.kaktushose.jda.commands.definitions.interactions; + +import com.github.kaktushose.jda.commands.definitions.Definition; +import com.github.kaktushose.jda.commands.definitions.description.ClassDescription; +import com.github.kaktushose.jda.commands.definitions.description.MethodDescription; +import com.github.kaktushose.jda.commands.definitions.description.ParameterDescription; +import com.github.kaktushose.jda.commands.definitions.features.CustomIdJDAEntity; +import com.github.kaktushose.jda.commands.definitions.features.JDAEntity; +import com.github.kaktushose.jda.commands.definitions.interactions.command.ParameterDefinition; +import com.github.kaktushose.jda.commands.dispatching.events.interactions.ModalEvent; +import com.github.kaktushose.jda.commands.internal.Helpers; +import net.dv8tion.jda.api.interactions.components.text.TextInput; +import net.dv8tion.jda.api.interactions.components.text.TextInputStyle; +import net.dv8tion.jda.api.interactions.modals.Modal; +import org.jetbrains.annotations.NotNull; + +import java.util.*; + +/// Representation of a modal. +/// +/// @param clazzDescription the [ClassDescription] of the declaring class of the [#methodDescription()] +/// @param methodDescription the [MethodDescription] of the method this definition is bound to +/// @param permissions a [Collection] of permissions for this modal +/// @param title the title of the modal +/// @param textInputs the [`TextInputs`][TextInputDefinition] of this modal +public record ModalDefinition( + @NotNull ClassDescription clazzDescription, + @NotNull MethodDescription methodDescription, + @NotNull Collection permissions, + @NotNull String title, + @NotNull SequencedCollection textInputs +) implements InteractionDefinition, CustomIdJDAEntity { + + /// Builds a new [ModalDefinition] from the given [MethodBuildContext]. + /// + /// @return an [Optional] holding the [ModalDefinition] + public static Optional build(MethodBuildContext context) { + var method = context.method(); + var modal = method.annotation(com.github.kaktushose.jda.commands.annotations.interactions.Modal.class).orElseThrow(); + + // Modals support up to 5 TextInputs + if (method.parameters().isEmpty() || method.parameters().size() > 6) { + log.error("An error has occurred! Skipping Modal {}.{}:", + method.declaringClass().getName(), + method.name(), + new IllegalArgumentException("Invalid amount of parameters! Modals need between 1 and 5 TextInputs")); + return Optional.empty(); + } + + if (Helpers.isIncorrectParameterType(method, 0, ModalEvent.class)) { + return Optional.empty(); + } + + List textInputs = new ArrayList<>(); + for (int i = 1; i < method.parameters().size(); i++) { + var parameter = List.copyOf(method.parameters()).get(i); + TextInputDefinition.build(parameter).ifPresent(textInputs::add); + } + + if (textInputs.isEmpty()) { + log.error("An error has occurred! Skipping Modal {}.{}:", + method.declaringClass().getName(), + method.name(), + new IllegalArgumentException("Modals need at least one valid TextInput")); + return Optional.empty(); + } + + List> signature = new ArrayList<>(); + signature.add(ModalEvent.class); + textInputs.forEach(_ -> signature.add(String.class)); + if (Helpers.checkSignature(method, signature)) { + return Optional.empty(); + } + + + return Optional.of(new ModalDefinition(context.clazz(), method, Helpers.permissions(context), modal.value(), textInputs)); + } + + /// Transforms this definition to an [Modal] with the given custom id. + /// + /// @return the [Modal] + /// @see CustomId#independent(String) + @NotNull + @Override + public Modal toJDAEntity(@NotNull CustomId customId) { + var modal = Modal.create(customId.id(), title); + + textInputs.forEach(textInput -> modal.addActionRow(textInput.toJDAEntity())); + + return modal.build(); + } + + @NotNull + @Override + public String displayName() { + return title; + } + + /// Representation of a modal text input defined by + /// [`TextInput`][com.github.kaktushose.jda.commands.annotations.interactions.TextInput] + public record TextInputDefinition( + @NotNull String label, + @NotNull String placeholder, + @NotNull String defaultValue, + int minValue, + int maxValue, + @NotNull TextInputStyle style, + boolean required + ) implements JDAEntity, Definition { + + /// Builds a new [TextInputDefinition] from the given [ParameterDefinition] + /// + /// @param parameter the [ParameterDefinition] to build the [TextInputDefinition] from + /// @return the new [TextInputDefinition] + public static Optional build(ParameterDescription parameter) { + var optional = parameter.annotation(com.github.kaktushose.jda.commands.annotations.interactions.TextInput.class); + + if (optional.isEmpty()) { + return Optional.empty(); + } + + // TODO for now we only allow Strings, maybe add type adapting in the future + if (!String.class.isAssignableFrom(parameter.type())) { + return Optional.empty(); + } + var textInput = optional.get(); + + return Optional.of(new TextInputDefinition( + textInput.label().isEmpty() ? parameter.name() : textInput.label(), + textInput.value(), + textInput.defaultValue(), + textInput.minValue(), + textInput.maxValue(), + textInput.style(), + textInput.required() + )); + } + + @Override + public @NotNull String displayName() { + return label; + } + + /// Transforms this definition into a [TextInput]. + /// + /// @return the [TextInput] + @NotNull + @Override + public TextInput toJDAEntity() { + var textInput = TextInput.create(label, label, style).setRequired(required); + + if (minValue != -1) { + textInput.setMinLength(minValue); + } + if (maxValue != -1) { + textInput.setMaxLength(maxValue); + } + if (!placeholder.isBlank()) { + textInput.setPlaceholder(placeholder); + } + if (!defaultValue.isBlank()) { + textInput.setValue(defaultValue); + } + + return textInput.build(); + } + } +} diff --git a/src/main/java/com/github/kaktushose/jda/commands/definitions/interactions/command/CommandDefinition.java b/src/main/java/com/github/kaktushose/jda/commands/definitions/interactions/command/CommandDefinition.java new file mode 100644 index 000000000..b92a64da9 --- /dev/null +++ b/src/main/java/com/github/kaktushose/jda/commands/definitions/interactions/command/CommandDefinition.java @@ -0,0 +1,40 @@ +package com.github.kaktushose.jda.commands.definitions.interactions.command; + +import com.github.kaktushose.jda.commands.annotations.interactions.CommandScope; +import com.github.kaktushose.jda.commands.definitions.features.JDAEntity; +import com.github.kaktushose.jda.commands.definitions.interactions.InteractionDefinition; +import net.dv8tion.jda.api.Permission; +import net.dv8tion.jda.api.interactions.commands.Command; +import net.dv8tion.jda.api.interactions.commands.build.CommandData; +import net.dv8tion.jda.api.interactions.commands.localization.LocalizationFunction; +import org.jetbrains.annotations.NotNull; + +import java.util.Set; + +/// Common interface for command interaction definitions. +/// +/// @see SlashCommandDefinition +/// @see ContextCommandDefinition +public sealed interface CommandDefinition extends InteractionDefinition, JDAEntity permits ContextCommandDefinition, SlashCommandDefinition { + + /// The name of the command. + @NotNull String name(); + + /// Whether this command can only be executed in guilds. + boolean guildOnly(); + + /// Whether this command is nsfw. + boolean nsfw(); + + /// The [Command.Type] of this command. + @NotNull Command.Type commandType(); + + /// A possibly-empty [Set] of [Permission]s this command will be enabled for. + @NotNull Set enabledPermissions(); + + /// The [CommandScope] of this command. + @NotNull CommandScope scope(); + + /// The [LocalizationFunction] to use for this command. + @NotNull LocalizationFunction localizationFunction(); +} diff --git a/src/main/java/com/github/kaktushose/jda/commands/definitions/interactions/command/ContextCommandDefinition.java b/src/main/java/com/github/kaktushose/jda/commands/definitions/interactions/command/ContextCommandDefinition.java new file mode 100644 index 000000000..ffb63a009 --- /dev/null +++ b/src/main/java/com/github/kaktushose/jda/commands/definitions/interactions/command/ContextCommandDefinition.java @@ -0,0 +1,108 @@ +package com.github.kaktushose.jda.commands.definitions.interactions.command; + +import com.github.kaktushose.jda.commands.annotations.interactions.ContextCommand; +import com.github.kaktushose.jda.commands.definitions.description.ClassDescription; +import com.github.kaktushose.jda.commands.definitions.description.MethodDescription; +import com.github.kaktushose.jda.commands.definitions.interactions.MethodBuildContext; +import com.github.kaktushose.jda.commands.dispatching.events.interactions.CommandEvent; +import com.github.kaktushose.jda.commands.internal.Helpers; +import net.dv8tion.jda.api.Permission; +import net.dv8tion.jda.api.entities.Message; +import net.dv8tion.jda.api.entities.User; +import net.dv8tion.jda.api.interactions.commands.Command; +import net.dv8tion.jda.api.interactions.commands.DefaultMemberPermissions; +import net.dv8tion.jda.api.interactions.commands.build.CommandData; +import net.dv8tion.jda.api.interactions.commands.build.Commands; +import net.dv8tion.jda.api.interactions.commands.localization.LocalizationFunction; +import org.jetbrains.annotations.NotNull; + +import java.util.*; +import java.util.stream.Collectors; + +import com.github.kaktushose.jda.commands.annotations.interactions.CommandScope; + +/// Representation of a context command. +/// +/// @param clazzDescription the [ClassDescription] of the declaring class of the [#methodDescription()] +/// @param methodDescription the [MethodDescription] of the method this definition is bound to +/// @param permissions a [Collection] of permissions for this command +/// @param name the name of the command +/// @param commandType the [Command.Type] of this command +/// @param scope the [CommandScope] of this command +/// @param guildOnly whether this command can only be executed in guilds +/// @param nsfw whether this command is nsfw +/// @param enabledPermissions a possibly-empty [Set] of [Permission]s this command will be enabled for +/// @param localizationFunction the [LocalizationFunction] to use for this command +public record ContextCommandDefinition( + @NotNull ClassDescription clazzDescription, + @NotNull MethodDescription methodDescription, + @NotNull Collection permissions, + @NotNull String name, + @NotNull Command.Type commandType, + @NotNull CommandScope scope, + boolean guildOnly, + boolean nsfw, + @NotNull Set enabledPermissions, + @NotNull LocalizationFunction localizationFunction +) implements CommandDefinition { + + /// Builds a new [ContextCommandDefinition] from the given [MethodBuildContext]. + /// + /// @return an [Optional] holding the [ContextCommandDefinition] + @NotNull + public static Optional build(MethodBuildContext context) { + var method = context.method(); + ContextCommand command = method.annotation(ContextCommand.class).orElseThrow(); + + var type = switch (command.type()) { + case USER -> User.class; + case MESSAGE -> Message.class; + default -> null; + }; + if (type == null) { + log.error("Invalid command type for context command! Must either be USER or MESSAGE"); + return Optional.empty(); + } + if (Helpers.checkSignature(method, List.of(CommandEvent.class, type))) { + return Optional.empty(); + } + + Set enabledFor = Arrays.stream(command.enabledFor()).collect(Collectors.toSet()); + if (enabledFor.size() == 1 && enabledFor.contains(Permission.UNKNOWN)) { + enabledFor.clear(); + } + + return Optional.of(new ContextCommandDefinition( + context.clazz(), + method, + Helpers.permissions(context), + command.value(), + command.type(), + command.scope(), + command.isGuildOnly(), + command.isNSFW(), + enabledFor, + context.localizationFunction() + )); + } + + /// Transforms this definition into [CommandData]. + /// + /// @return the [CommandData] + @NotNull + @Override + public CommandData toJDAEntity() { + var command = Commands.context(commandType, name); + command.setGuildOnly(guildOnly) + .setNSFW(nsfw) + .setDefaultPermissions(DefaultMemberPermissions.enabledFor(enabledPermissions)) + .setLocalizationFunction(localizationFunction); + return command; + } + + @NotNull + @Override + public String displayName() { + return name; + } +} diff --git a/src/main/java/com/github/kaktushose/jda/commands/reflect/ParameterDefinition.java b/src/main/java/com/github/kaktushose/jda/commands/definitions/interactions/command/ParameterDefinition.java similarity index 54% rename from src/main/java/com/github/kaktushose/jda/commands/reflect/ParameterDefinition.java rename to src/main/java/com/github/kaktushose/jda/commands/definitions/interactions/command/ParameterDefinition.java index 1e9eee659..0aa4e7953 100644 --- a/src/main/java/com/github/kaktushose/jda/commands/reflect/ParameterDefinition.java +++ b/src/main/java/com/github/kaktushose/jda/commands/definitions/interactions/command/ParameterDefinition.java @@ -1,11 +1,13 @@ -package com.github.kaktushose.jda.commands.reflect; +package com.github.kaktushose.jda.commands.definitions.interactions.command; import com.github.kaktushose.jda.commands.annotations.constraints.Constraint; import com.github.kaktushose.jda.commands.annotations.constraints.Max; import com.github.kaktushose.jda.commands.annotations.constraints.Min; import com.github.kaktushose.jda.commands.annotations.interactions.Choices; -import com.github.kaktushose.jda.commands.annotations.interactions.Optional; import com.github.kaktushose.jda.commands.annotations.interactions.Param; +import com.github.kaktushose.jda.commands.definitions.Definition; +import com.github.kaktushose.jda.commands.definitions.description.ParameterDescription; +import com.github.kaktushose.jda.commands.definitions.features.JDAEntity; import com.github.kaktushose.jda.commands.dispatching.validation.Validator; import com.github.kaktushose.jda.commands.dispatching.validation.ValidatorRegistry; import net.dv8tion.jda.api.entities.Member; @@ -16,39 +18,44 @@ import net.dv8tion.jda.api.entities.channel.middleman.AudioChannel; import net.dv8tion.jda.api.entities.channel.middleman.GuildChannel; import net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel; -import net.dv8tion.jda.api.interactions.commands.Command.Choice; +import net.dv8tion.jda.api.interactions.commands.Command; import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.interactions.commands.build.OptionData; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.lang.annotation.Annotation; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.lang.reflect.Parameter; import java.util.*; import static java.util.Map.entry; -/** - * Representation of a command parameter. - * - * @see Constraint - * @see Choices - * @see Param - * @since 2.0.0 - */ +/// Representation of a slash command parameter. +/// +/// @param type the [Class] type of the parameter +/// @param optional whether this parameter is optional +/// @param autoComplete whether this parameter supports autocomplete +/// @param defaultValue the default value of this parameter or `null` +/// @param name the name of the parameter +/// @param description the description of the parameter +/// @param choices a [SequencedCollection] of possible [Command.Choice]s for this parameter +/// @param constraints a [Collection] of [ConstraintDefinition]s of this parameter public record ParameterDefinition( - Class type, - boolean isOptional, - String defaultValue, - boolean isPrimitive, - String name, - String description, - List choices, - List constraints -) { - - private static final Map, Class> TYPE_MAPPINGS = Map.ofEntries( + @NotNull Class type, + boolean optional, + boolean autoComplete, + @Nullable String defaultValue, + @NotNull String name, + @NotNull String description, + @NotNull SequencedCollection choices, + @NotNull Collection constraints +) implements Definition, JDAEntity { + + /// A mapping of primitive types and their wrapper classes. + @ApiStatus.Internal + public static final Map, Class> TYPE_MAPPINGS = Map.ofEntries( entry(byte.class, Byte.class), entry(short.class, Short.class), entry(int.class, Integer.class), @@ -69,7 +76,6 @@ public record ParameterDefinition( entry(Boolean.class, OptionType.BOOLEAN), entry(Character.class, OptionType.STRING), entry(String.class, OptionType.STRING), - entry(String[].class, OptionType.STRING), entry(User.class, OptionType.USER), entry(Member.class, OptionType.USER), entry(GuildChannel.class, OptionType.CHANNEL), @@ -97,27 +103,22 @@ public record ParameterDefinition( )) ); - /** - * Builds a new ParameterDefinition. - * - * @param parameter the {@link Parameter} of the command method - * @param registry an instance of the corresponding {@link ValidatorRegistry} - * @return a new ParameterDefinition - */ + /// Builds a new [ParameterDefinition]. + /// + /// @param parameter the [ParameterDescription] to build the [ParameterDefinition] from + /// @param autoComplete whether the [ParameterDescription] should support autocomplete + /// @param validatorRegistry the corresponding [ValidatorRegistry] + /// @return the [ParameterDefinition] @NotNull - public static ParameterDefinition build(@NotNull Parameter parameter, @NotNull ValidatorRegistry registry) { - if (parameter.isVarArgs()) { - throw new IllegalArgumentException("VarArgs is not supported for parameters!"); - } - - Class parameterType = parameter.getType(); - parameterType = TYPE_MAPPINGS.getOrDefault(parameterType, parameterType); - - // Optional - final boolean isOptional = parameter.isAnnotationPresent(Optional.class); - String defaultValue = ""; - if (isOptional) { - defaultValue = parameter.getAnnotation(Optional.class).value(); + public static ParameterDefinition build(ParameterDescription parameter, + boolean autoComplete, + @NotNull ValidatorRegistry validatorRegistry) { + final var parameterType = TYPE_MAPPINGS.getOrDefault(parameter.type(), parameter.type()); + + var optional = parameter.annotation(com.github.kaktushose.jda.commands.annotations.interactions.Optional.class); + var defaultValue = ""; + if (optional.isPresent()) { + defaultValue = optional.get().value(); } if (defaultValue.isEmpty()) { defaultValue = null; @@ -125,90 +126,75 @@ public static ParameterDefinition build(@NotNull Parameter parameter, @NotNull V // index constraints List constraints = new ArrayList<>(); - for (Annotation annotation : parameter.getAnnotations()) { - Class annotationType = annotation.annotationType(); - if (!annotationType.isAnnotationPresent(Constraint.class)) { - continue; - } - - // annotation object is always different, so we cannot cast it. Thus, we need to get the custom error message via reflection - String message = ""; - try { - Method method = annotationType.getDeclaredMethod("message"); - message = (String) method.invoke(annotation); - } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException ignored) { - } - if (message.isEmpty()) { - message = "Parameter validation failed"; - } - - java.util.Optional optional = registry.get(annotationType, parameterType); - if (optional.isPresent()) { - constraints.add(new ConstraintDefinition(optional.get(), message, annotation)); - } - } + parameter.annotations().stream() + .filter(it -> it.annotationType().isAnnotationPresent(Constraint.class)) + .forEach(it -> { + var validator = validatorRegistry.get(it.annotationType(), parameterType); + validator.ifPresent(value -> constraints.add(ConstraintDefinition.build(value, it))); + }); // Param - String name = parameter.getName(); + String name = parameter.name(); String description = "empty description"; - if (parameter.isAnnotationPresent(Param.class)) { - Param param = parameter.getAnnotation(Param.class); - name = param.name().isEmpty() ? name : param.name(); - description = param.value(); + var param = parameter.annotation(Param.class); + if (param.isPresent()) { + name = param.get().name().isEmpty() ? name : param.get().name(); + description = param.get().value(); } name = name.replaceAll("([a-z])([A-Z]+)", "$1_$2").toLowerCase(); - List choices = new ArrayList<>(); + List commandChoices = new ArrayList<>(); // Options - if (parameter.isAnnotationPresent(Choices.class)) { - Choices opt = parameter.getAnnotation(Choices.class); - for (String option : opt.value()) { + var choices = parameter.annotation(Choices.class); + if (choices.isPresent()) { + for (String option : choices.get().value()) { String[] parsed = option.split(":", 2); if (parsed.length < 1) { continue; } if (parsed.length < 2) { - choices.add(new Choice(parsed[0], parsed[0])); + commandChoices.add(new Command.Choice(parsed[0], parsed[0])); continue; } - choices.add(new Choice(parsed[0], parsed[1])); + commandChoices.add(new Command.Choice(parsed[0], parsed[1])); } } - - // this value is only used to determine if a default value must be present (primitives cannot be null) - boolean usesPrimitives = TYPE_MAPPINGS.containsKey(parameter.getType()); - return new ParameterDefinition( parameterType, - isOptional, + optional.isPresent(), + autoComplete, defaultValue, - usesPrimitives, name, description, - choices, + commandChoices, constraints ); } - /** - * Transforms this parameter definition to a {@link OptionData}. - * - * @param isAutoComplete whether this {@link OptionData} should support auto complete - * @return the transformed {@link OptionData} - */ - public OptionData toOptionData(boolean isAutoComplete) { + @NotNull + @Override + public String displayName() { + return name; + } + + /// Transforms this definition into [OptionData]. + /// + /// @return the [OptionData] + @NotNull + @Override + public OptionData toJDAEntity() { OptionType optionType = OPTION_TYPE_MAPPINGS.getOrDefault(type, OptionType.STRING); OptionData optionData = new OptionData( optionType, name, description, - !isOptional + !optional ); optionData.addChoices(choices); if (optionType.canSupportChoices() && choices.isEmpty()) { - optionData.setAutoComplete(isAutoComplete); + optionData.setAutoComplete(autoComplete); } constraints.stream().filter(constraint -> @@ -223,4 +209,36 @@ public OptionData toOptionData(boolean isAutoComplete) { return optionData; } + + /// Representation of a parameter constraint defined by a constraint annotation. + /// + /// @param validator the corresponding [Validator] + /// @param message the message to display if the constraint fails + /// @param annotation the corresponding annotation object + public record ConstraintDefinition(Validator validator, String message, Object annotation) implements Definition { + + /// Builds a new [ConstraintDefinition]. + /// + /// @param validator the corresponding [Validator] + /// @param annotation the corresponding annotation object + public static ConstraintDefinition build(@NotNull Validator validator, @NotNull Annotation annotation) { + // annotation object is always different, so we cannot cast it. Thus, we need to get the custom error message via reflection + var message = ""; + try { + Method method = annotation.getClass().getDeclaredMethod("message"); + message = (String) method.invoke(annotation); + } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException _) { + } + if (message.isEmpty()) { + message = "Parameter validation failed"; + } + return new ConstraintDefinition(validator, message, annotation); + } + + @NotNull + @Override + public String displayName() { + return validator.getClass().getName(); + } + } } diff --git a/src/main/java/com/github/kaktushose/jda/commands/definitions/interactions/command/SlashCommandDefinition.java b/src/main/java/com/github/kaktushose/jda/commands/definitions/interactions/command/SlashCommandDefinition.java new file mode 100644 index 000000000..34a911cec --- /dev/null +++ b/src/main/java/com/github/kaktushose/jda/commands/definitions/interactions/command/SlashCommandDefinition.java @@ -0,0 +1,192 @@ +package com.github.kaktushose.jda.commands.definitions.interactions.command; + +import com.github.kaktushose.jda.commands.annotations.interactions.CommandScope; +import com.github.kaktushose.jda.commands.annotations.interactions.Cooldown; +import com.github.kaktushose.jda.commands.annotations.interactions.SlashCommand; +import com.github.kaktushose.jda.commands.definitions.Definition; +import com.github.kaktushose.jda.commands.definitions.description.ClassDescription; +import com.github.kaktushose.jda.commands.definitions.description.MethodDescription; +import com.github.kaktushose.jda.commands.definitions.interactions.AutoCompleteDefinition; +import com.github.kaktushose.jda.commands.definitions.interactions.MethodBuildContext; +import com.github.kaktushose.jda.commands.dispatching.events.interactions.CommandEvent; +import com.github.kaktushose.jda.commands.internal.Helpers; +import net.dv8tion.jda.api.Permission; +import net.dv8tion.jda.api.interactions.commands.Command; +import net.dv8tion.jda.api.interactions.commands.DefaultMemberPermissions; +import net.dv8tion.jda.api.interactions.commands.build.Commands; +import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData; +import net.dv8tion.jda.api.interactions.commands.build.SubcommandData; +import net.dv8tion.jda.api.interactions.commands.localization.LocalizationFunction; +import net.dv8tion.jda.internal.utils.Checks; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; +import java.util.concurrent.TimeUnit; + +/// Representation of a slash command. +/// +/// @param clazzDescription the [ClassDescription] of the declaring class of the [#methodDescription()] +/// @param methodDescription the [MethodDescription] of the method this definition is bound to +/// @param permissions a [Collection] of permissions for this command +/// @param name the name of the command +/// @param scope the [CommandScope] of this command +/// @param guildOnly whether this command can only be executed in guilds +/// @param nsfw whether this command is nsfw +/// @param enabledPermissions a possibly-empty [Set] of [Permission]s this command will be enabled for +/// @param localizationFunction the [LocalizationFunction] to use for this command +/// @param description the command description +/// @param commandParameters a [SequencedCollection] of [ParameterDefinition]s +/// @param cooldown the corresponding [CooldownDefinition] +/// @param isAutoComplete whether this command supports auto complete +public record SlashCommandDefinition( + @NotNull ClassDescription clazzDescription, + @NotNull MethodDescription methodDescription, + @NotNull Collection permissions, + @NotNull String name, + @NotNull CommandScope scope, + boolean guildOnly, + boolean nsfw, + @NotNull Set enabledPermissions, + @NotNull LocalizationFunction localizationFunction, + @NotNull String description, + @NotNull SequencedCollection commandParameters, + @NotNull CooldownDefinition cooldown, + boolean isAutoComplete +) implements CommandDefinition { + + /// Builds a new [SlashCommandDefinition] from the given [MethodBuildContext]. + /// + /// @return an [Optional] holding the [SlashCommandDefinition] + @NotNull + public static Optional build(MethodBuildContext context) { + var method = context.method(); + var interaction = context.interaction(); + var command = method.annotation(SlashCommand.class).orElseThrow(); + + String name = String.join(" ", interaction.value(), command.value()) + .replaceAll(" +", " ") + .trim(); + + if (name.isEmpty()) { + Checks.notBlank(name, "Command name"); + return Optional.empty(); + } + + boolean autoComplete = context.autoCompleteDefinitions().stream() + .map(AutoCompleteDefinition::commands) + .flatMap(Collection::stream) + .anyMatch(name::startsWith); + + // build parameter definitions + List parameters = method.parameters().stream() + .filter(it -> !(CommandEvent.class.isAssignableFrom(it.type()))) + .map(parameter -> ParameterDefinition.build(parameter, autoComplete, context.validatorRegistry())) + .toList(); + + Set enabledFor = Set.of(command.enabledFor()); + if (enabledFor.size() == 1 && enabledFor.contains(Permission.UNKNOWN)) { + enabledFor = Set.of(); + } + + List> signature = new ArrayList<>(); + signature.add(CommandEvent.class); + parameters.forEach(it -> signature.add(it.type())); + if (Helpers.checkSignature(method, signature)) { + return Optional.empty(); + } + + CooldownDefinition cooldownDefinition = CooldownDefinition.build(method.annotation(Cooldown.class).orElse(null)); + if (cooldownDefinition.delay() == 0 && context.cooldownDefinition() != null) { + cooldownDefinition = context.cooldownDefinition(); + } + + return Optional.of(new SlashCommandDefinition( + context.clazz(), + method, + Helpers.permissions(context), + name, + command.scope(), + command.isGuildOnly(), + command.isNSFW(), + enabledFor, + context.localizationFunction(), + command.desc(), + parameters, + cooldownDefinition, + autoComplete + )); + } + + /// Transforms this definition into [SlashCommandData]. + /// + /// @return the [SlashCommandData] + @NotNull + @Override + public SlashCommandData toJDAEntity() { + SlashCommandData command = Commands.slash( + name, + description.replaceAll("N/A", "no description") + ); + command.setGuildOnly(guildOnly) + .setNSFW(nsfw) + .setLocalizationFunction(localizationFunction) + .setDefaultPermissions(DefaultMemberPermissions.enabledFor(enabledPermissions)); + commandParameters.forEach(parameter -> { + if (CommandEvent.class.isAssignableFrom(parameter.type())) { + return; + } + command.addOptions(parameter.toJDAEntity()); + }); + return command; + } + + /// Transforms this definition into [SubcommandData]. + /// + /// @return the [SubcommandData] + public SubcommandData toSubCommandData(String label) { + SubcommandData command = new SubcommandData( + label, + description.replaceAll("N/A", "no description") + + ); + commandParameters.forEach(parameter -> { + command.addOptions(parameter.toJDAEntity()); + }); + return command; + } + + @NotNull + @Override + public String displayName() { + return "/%s".formatted(name); + } + + @NotNull + @Override + public Command.Type commandType() { + return Command.Type.SLASH; + } + + /// Representation of a cooldown definition defined by [Cooldown]. + /// + /// @param delay the delay of the cooldown + /// @param timeUnit the [TimeUnit] of the specified delay + public record CooldownDefinition(long delay, TimeUnit timeUnit) implements Definition { + + /// Builds a new [CooldownDefinition] from the given [Cooldown] annotation. + @NotNull + public static CooldownDefinition build(@Nullable Cooldown cooldown) { + if (cooldown == null) { + return new CooldownDefinition(0, TimeUnit.MILLISECONDS); + } + return new CooldownDefinition(cooldown.value(), cooldown.timeUnit()); + } + + @NotNull + @Override + public String displayName() { + return "Cooldown of %d %s".formatted(delay, timeUnit.name()); + } + } +} diff --git a/src/main/java/com/github/kaktushose/jda/commands/definitions/interactions/command/package-info.java b/src/main/java/com/github/kaktushose/jda/commands/definitions/interactions/command/package-info.java new file mode 100644 index 000000000..ccc62de37 --- /dev/null +++ b/src/main/java/com/github/kaktushose/jda/commands/definitions/interactions/command/package-info.java @@ -0,0 +1,2 @@ +/// [`Definitions`][com.github.kaktushose.jda.commands.definitions.Definition] that represent a command interaction. +package com.github.kaktushose.jda.commands.definitions.interactions.command; diff --git a/src/main/java/com/github/kaktushose/jda/commands/definitions/interactions/component/ButtonDefinition.java b/src/main/java/com/github/kaktushose/jda/commands/definitions/interactions/component/ButtonDefinition.java new file mode 100644 index 000000000..54bdd943f --- /dev/null +++ b/src/main/java/com/github/kaktushose/jda/commands/definitions/interactions/component/ButtonDefinition.java @@ -0,0 +1,100 @@ +package com.github.kaktushose.jda.commands.definitions.interactions.component; + +import com.github.kaktushose.jda.commands.definitions.description.ClassDescription; +import com.github.kaktushose.jda.commands.definitions.description.MethodDescription; +import com.github.kaktushose.jda.commands.definitions.interactions.CustomId; +import com.github.kaktushose.jda.commands.definitions.interactions.MethodBuildContext; +import com.github.kaktushose.jda.commands.definitions.interactions.component.menu.StringSelectMenuDefinition; +import com.github.kaktushose.jda.commands.dispatching.events.interactions.ComponentEvent; +import com.github.kaktushose.jda.commands.internal.Helpers; +import net.dv8tion.jda.api.entities.emoji.Emoji; +import net.dv8tion.jda.api.interactions.components.buttons.Button; +import net.dv8tion.jda.api.interactions.components.buttons.ButtonStyle; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; +import java.util.List; +import java.util.Optional; + +/// Representation of a button. +/// +/// @param clazzDescription the [ClassDescription] of the declaring class of the [#methodDescription()] +/// @param methodDescription the [MethodDescription] of the method this definition is bound to +/// @param permissions a [Collection] of permissions for this button +/// @param label the label of this button +/// @param emoji the [Emoji] of this button or `null` +/// @param link the link of this button or `null` +/// @param style the [ButtonStyle] of this button +public record ButtonDefinition( + @NotNull ClassDescription clazzDescription, + @NotNull MethodDescription methodDescription, + @NotNull Collection permissions, + @NotNull String label, + @Nullable Emoji emoji, + @Nullable String link, + @NotNull ButtonStyle style +) implements ComponentDefinition