diff --git a/src/main/java/net/javadiscord/javabot2/Bot.java b/src/main/java/net/javadiscord/javabot2/Bot.java
index efa52bb..2e9294b 100644
--- a/src/main/java/net/javadiscord/javabot2/Bot.java
+++ b/src/main/java/net/javadiscord/javabot2/Bot.java
@@ -8,6 +8,7 @@
import com.zaxxer.hikari.HikariDataSource;
import lombok.extern.slf4j.Slf4j;
import net.javadiscord.javabot2.command.SlashCommandListener;
+import net.javadiscord.javabot2.command.interaction.InteractionListener;
import net.javadiscord.javabot2.config.BotConfig;
import net.javadiscord.javabot2.db.DbHelper;
import net.javadiscord.javabot2.systems.moderation.ModerationService;
@@ -81,9 +82,20 @@ public static void main(String[] args) {
"commands/moderation.yaml"
);
api.addSlashCommandCreateListener(commandListener);
+ addEventListeners(api);
initScheduledTasks(api);
}
+ /**
+ * Adds all the bot's event listeners to the Javacord instance, except for the
+ * main {@link SlashCommandListener}.
+ * @param api the {@link DiscordApi} instance to add listeners to.
+ */
+ private static void addEventListeners(DiscordApi api) {
+ api.addButtonClickListener(new InteractionListener());
+ api.addSelectMenuChooseListener(new InteractionListener());
+ }
+
/**
* Initializes all the basic data sources that are needed by the bot's other
* capabilities. This should be called before logging in
diff --git a/src/main/java/net/javadiscord/javabot2/command/interaction/InteractionHandlerBuilder.java b/src/main/java/net/javadiscord/javabot2/command/interaction/InteractionHandlerBuilder.java
new file mode 100644
index 0000000..8c1cf0e
--- /dev/null
+++ b/src/main/java/net/javadiscord/javabot2/command/interaction/InteractionHandlerBuilder.java
@@ -0,0 +1,53 @@
+package net.javadiscord.javabot2.command.interaction;
+
+import net.javadiscord.javabot2.command.interaction.button.ButtonAction;
+import net.javadiscord.javabot2.command.interaction.selection_menu.SelectMenuAction;
+import org.javacord.api.entity.message.component.ActionRow;
+import org.javacord.api.entity.message.component.LowLevelComponent;
+import org.javacord.api.interaction.callback.InteractionImmediateResponseBuilder;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Class that is used to build message interactions.
+ */
+public class InteractionHandlerBuilder {
+ private final InteractionImmediateResponseBuilder responseBuilder;
+ private final List buttons = new ArrayList<>();
+ private final List selectMenus = new ArrayList<>();
+
+ public InteractionHandlerBuilder(InteractionImmediateResponseBuilder responseBuilder) {
+ this.responseBuilder = responseBuilder;
+ }
+
+ /**
+ * Adds one or multiple {@link ButtonAction}s.
+ * @param buttonActions an array of {@link ButtonAction}s.
+ * @return the current instance in order to allow chain call methods.
+ */
+ public InteractionHandlerBuilder addButtons(ButtonAction... buttonActions) {
+ for (var action : buttonActions) buttons.add(action.getButton());
+ return this;
+ }
+
+ /**
+ * Adds one or multiple {@link SelectMenuAction}s.
+ * @param selectMenuActions an array of {@link SelectMenuAction}s.
+ * @return the current instance in order to allow chain call methods.
+ */
+ public InteractionHandlerBuilder addSelectionMenus(SelectMenuAction... selectMenuActions) {
+ for (var action : selectMenuActions) {
+ selectMenus.add(action.getSelectMenu());
+ }
+ return this;
+ }
+
+ /**
+ * Returns the provided Response Builder.
+ * @return the current instance in order to allow chain call methods.
+ */
+ public InteractionImmediateResponseBuilder getResponseBuilder() {
+ return responseBuilder.addComponents(ActionRow.of(buttons), ActionRow.of(selectMenus));
+ }
+}
diff --git a/src/main/java/net/javadiscord/javabot2/command/interaction/InteractionListener.java b/src/main/java/net/javadiscord/javabot2/command/interaction/InteractionListener.java
new file mode 100644
index 0000000..676ad73
--- /dev/null
+++ b/src/main/java/net/javadiscord/javabot2/command/interaction/InteractionListener.java
@@ -0,0 +1,57 @@
+package net.javadiscord.javabot2.command.interaction;
+
+import net.javadiscord.javabot2.command.ResponseException;
+import net.javadiscord.javabot2.command.interaction.button.ButtonHandler;
+import net.javadiscord.javabot2.command.interaction.selection_menu.SelectionMenuHandler;
+import org.javacord.api.event.interaction.ButtonClickEvent;
+import org.javacord.api.event.interaction.SelectMenuChooseEvent;
+import org.javacord.api.listener.interaction.ButtonClickListener;
+import org.javacord.api.listener.interaction.SelectMenuChooseListener;
+
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ * Listener for all supported interactions.
+ */
+public class InteractionListener implements ButtonClickListener, SelectMenuChooseListener {
+
+ @Override
+ public void onButtonClick(ButtonClickEvent event) {
+ var id = event.getButtonInteraction().getCustomId().split(":");
+ ButtonHandler handler = (ButtonHandler) getHandlerByName(id[0]);
+ try {
+ handler.handleButtonInteraction(event.getButtonInteraction()).respond();
+ } catch (ResponseException e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public void onSelectMenuChoose(SelectMenuChooseEvent event) {
+ var id = event.getSelectMenuInteraction().getCustomId().split(":");
+ SelectionMenuHandler handler = (SelectionMenuHandler) getHandlerByName(id[0]);
+ try {
+ handler.handleSelectMenuInteraction(event.getSelectMenuInteraction()).respond();
+ } catch (ResponseException e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Tries to get a Class by the specified name
+ * (Class paths are baked into the button id) and returns it.
+ * @param name the class name
+ * @return The handler class
+ */
+ private Object getHandlerByName(String name) {
+ try {
+ return Class.forName(name)
+ .getDeclaredConstructor()
+ .newInstance();
+ } catch (ClassNotFoundException | NoSuchMethodException | InstantiationException |
+ IllegalAccessException | InvocationTargetException e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+}
diff --git a/src/main/java/net/javadiscord/javabot2/command/interaction/button/ButtonAction.java b/src/main/java/net/javadiscord/javabot2/command/interaction/button/ButtonAction.java
new file mode 100644
index 0000000..18374bd
--- /dev/null
+++ b/src/main/java/net/javadiscord/javabot2/command/interaction/button/ButtonAction.java
@@ -0,0 +1,148 @@
+package net.javadiscord.javabot2.command.interaction.button;
+
+import org.javacord.api.entity.emoji.Emoji;
+import org.javacord.api.entity.message.component.Button;
+import org.javacord.api.entity.message.component.ButtonBuilder;
+import org.javacord.api.entity.message.component.ButtonStyle;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Class that represents a single Button Interaction.
+ */
+public class ButtonAction {
+
+ private final String label;
+ private final ButtonStyle buttonStyle;
+ private Class extends ButtonHandler> handler;
+ private final List params;
+ private boolean disabled = false;
+ private String url;
+ private Emoji emoji;
+
+ /**
+ * Constructor of the Button Action.
+ * @param label the button's label
+ * @param buttonStyle the button's {@link ButtonStyle}
+ */
+ public ButtonAction(String label, ButtonStyle buttonStyle) {
+ this.params = new ArrayList<>();
+ this.label = label;
+ this.buttonStyle = buttonStyle;
+ }
+
+ /**
+ * Sets a handler class for this button interaction.
+ * The class must extend {@link ButtonHandler}.
+ * @param handler a class that should handle the button interaction.
+ * @return the current instance in order to allow chain call methods.
+ */
+ public ButtonAction handledBy(Class extends ButtonHandler> handler) {
+ this.handler = handler;
+ return this;
+ }
+
+ /**
+ * Adds a parameter, which is later baked into the button id.
+ * @param param an object that is later converted to a string to bake it into the button id as a parameter.
+ * @return the current instance in order to allow chain call methods.
+ */
+ public ButtonAction addParam(Object param) {
+ params.add(String.valueOf(param));
+ return this;
+ }
+
+ /**
+ * Enables/Disables the button based on the provided boolean.
+ * @param disabled the boolean which enables/disables the button.
+ * @return the current instance in order to allow chain call methods.
+ */
+ public ButtonAction setDisabled(boolean disabled) {
+ this.disabled = disabled;
+ return this;
+ }
+
+ /**
+ * Sets an Url for this button.
+ * This only works for buttons with the {@link ButtonStyle#LINK} button style.
+ * @param url the url as a string.
+ * @return the current instance in order to allow chain call methods.
+ */
+ public ButtonAction setUrl(String url) {
+ this.url = url;
+ return this;
+ }
+
+ /**
+ * Sets an Emoji for this button.
+ * @param emoji the emoji which should be used.
+ * @return the current instance in order to allow chain call methods.
+ */
+ public ButtonAction setEmoji(Emoji emoji) {
+ this.emoji = emoji;
+ return this;
+ }
+
+ /**
+ * Returns the compiled button id. Every parameter is seperated with a colon.
+ * The first argument is always the handler's class path.
+ * After that, all set params ({@link ButtonAction#addParam(Object)}) get appended to the id.
+ * @return the compiled button id.
+ */
+ public String getCustomId() {
+ StringBuilder id = new StringBuilder(handler.getName());
+ for (String param : params) {
+ id.append(":").append(param);
+ }
+ return id.toString();
+ }
+
+ /**
+ * Returns the button's {@link ButtonStyle}.
+ * @return the button's {@link ButtonStyle}.
+ */
+ public ButtonStyle getButtonStyle() {
+ return buttonStyle;
+ }
+
+ /**
+ * Returns the button's label.
+ * @return the button's label
+ */
+ public String getLabel() {
+ return label;
+ }
+
+ /**
+ * Returns the button's handler class.
+ * @return The handler class
+ */
+ public Class extends ButtonHandler> getHandler() {
+ return handler;
+ }
+
+ /**
+ * Returns a list with all parameters of the button.
+ * @return A List with all parameters.
+ */
+ public List getParams() {
+ return params;
+ }
+
+ /**
+ * Returns the complete button.
+ * @return the compiled button.
+ */
+ public Button getButton() {
+ var builder = new ButtonBuilder()
+ .setLabel(label)
+ .setStyle(buttonStyle)
+ .setDisabled(disabled);
+ if (buttonStyle != ButtonStyle.LINK) builder.setCustomId(getCustomId());
+ if (url != null && url.length() > 0 && buttonStyle == ButtonStyle.LINK) builder.setUrl(url);
+ if (emoji != null) builder.setEmoji(emoji);
+
+ return builder.build();
+ }
+}
diff --git a/src/main/java/net/javadiscord/javabot2/command/interaction/button/ButtonHandler.java b/src/main/java/net/javadiscord/javabot2/command/interaction/button/ButtonHandler.java
new file mode 100644
index 0000000..d94bd5f
--- /dev/null
+++ b/src/main/java/net/javadiscord/javabot2/command/interaction/button/ButtonHandler.java
@@ -0,0 +1,13 @@
+package net.javadiscord.javabot2.command.interaction.button;
+
+import net.javadiscord.javabot2.command.ResponseException;
+import org.javacord.api.interaction.ButtonInteraction;
+import org.javacord.api.interaction.callback.InteractionImmediateResponseBuilder;
+
+/**
+ * An interface that should be implemented by any class that is utilizing
+ * button interactions.
+ */
+public interface ButtonHandler {
+ InteractionImmediateResponseBuilder handleButtonInteraction(ButtonInteraction interaction) throws ResponseException;
+}
diff --git a/src/main/java/net/javadiscord/javabot2/command/interaction/selection_menu/SelectMenuAction.java b/src/main/java/net/javadiscord/javabot2/command/interaction/selection_menu/SelectMenuAction.java
new file mode 100644
index 0000000..da97923
--- /dev/null
+++ b/src/main/java/net/javadiscord/javabot2/command/interaction/selection_menu/SelectMenuAction.java
@@ -0,0 +1,142 @@
+package net.javadiscord.javabot2.command.interaction.selection_menu;
+
+import org.javacord.api.entity.message.component.SelectMenu;
+import org.javacord.api.entity.message.component.SelectMenuBuilder;
+import org.javacord.api.entity.message.component.SelectMenuOption;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Class that represents a single Select Menu Interaction.
+ */
+public class SelectMenuAction {
+
+ private Class extends SelectionMenuHandler> handler;
+ private List options;
+ private String placeholder;
+ private int minValue = 0;
+ private int maxValue = 25;
+ private boolean disabled = false;
+
+ /**
+ * Constructor of the Select Menu Action.
+ */
+ public SelectMenuAction() {
+ this.options = new ArrayList<>();
+ }
+
+ /**
+ * Sets a handler class for this select menu interaction.
+ * The class must extend {@link SelectionMenuHandler}.
+ * @param handler a class that should handle the select menu interaction.
+ * @return the current instance in order to allow chain call methods.
+ */
+ public SelectMenuAction handledBy(Class extends SelectionMenuHandler> handler) {
+ this.handler = handler;
+ return this;
+ }
+
+ /**
+ * Sets the select menu's placeholder text.
+ * @param placeholder the select menu's placeholder.
+ * @return the current instance in order to allow chain call methods.
+ */
+ public SelectMenuAction setPlaceholder(String placeholder) {
+ this.placeholder = placeholder;
+ return this;
+ }
+
+ /**
+ * Enables/Disables the select menu based on the provided boolean.
+ * @param disabled the boolean which enables/disables the select menu.
+ * @return the current instance in order to allow chain call methods.
+ */
+ public SelectMenuAction setDisabled(boolean disabled) {
+ this.disabled = disabled;
+ return this;
+ }
+
+ /**
+ * Sets the min value for select menu options.
+ * @param minValue the min value for select menu options.
+ * @return the current instance in order to allow chain call methods.
+ */
+ public SelectMenuAction setMinValue(int minValue) {
+ this.minValue = minValue;
+ return this;
+ }
+
+ /**
+ * Sets the max value for select menu options.
+ * @param maxValue the max value for select menu options.
+ * @return the current instance in order to allow chain call methods.
+ */
+ public SelectMenuAction setMaxValue(int maxValue) {
+ this.maxValue = maxValue;
+ return this;
+ }
+
+ /**
+ * Adds a single option to the select menu.
+ * @param option a single {@link SelectMenuOption}.
+ * @return the current instance in order to allow chain call methods.
+ */
+ public SelectMenuAction addOption(SelectMenuOption option) {
+ options.add(option);
+ return this;
+ }
+
+ /**
+ * Adds multiple options to the select menu.
+ * @param options an array with all {@link SelectMenuOption}s.
+ * @return the current instance in order to allow chain call methods.
+ */
+ public SelectMenuAction addOptions(SelectMenuOption... options) {
+ this.options.addAll(Arrays.asList(options));
+ return this;
+ }
+
+ /**
+ * Returns the select menu's custom id.
+ * (which is just the path of the handler class.)
+ * @return the custom id.
+ */
+ public String getCustomId() {
+ return handler.getName();
+ }
+
+ /**
+ * Returns the select menu's handler class.
+ * @return the select menu's handler class.
+ */
+ public Class extends SelectionMenuHandler> getHandler() {
+ return handler;
+ }
+
+ /**
+ * Returns a list with all options.
+ * @return a list with all options.
+ */
+ public List getOptions() {
+ return options;
+ }
+
+ /**
+ * Returns the complete select menu.
+ * @return the complete select menu.
+ */
+ public SelectMenu getSelectMenu() {
+ if (options.isEmpty()) throw new IllegalStateException("SelectMenu options may not be empty!");
+ if (minValue > maxValue) throw new IllegalArgumentException("minValue may not be greater than maxValue!");
+ var builder = new SelectMenuBuilder()
+ .setCustomId(getCustomId())
+ .setMinimumValues(minValue)
+ .setMaximumValues(Math.min(maxValue, options.size())) // maxValue cannot be greater than the provided amount of options
+ .addOptions(options)
+ .setDisabled(disabled);
+ if (placeholder != null && placeholder.length() > 0) builder.setPlaceholder(placeholder);
+ return builder.build();
+ }
+}
diff --git a/src/main/java/net/javadiscord/javabot2/command/interaction/selection_menu/SelectionMenuHandler.java b/src/main/java/net/javadiscord/javabot2/command/interaction/selection_menu/SelectionMenuHandler.java
new file mode 100644
index 0000000..254ed29
--- /dev/null
+++ b/src/main/java/net/javadiscord/javabot2/command/interaction/selection_menu/SelectionMenuHandler.java
@@ -0,0 +1,13 @@
+package net.javadiscord.javabot2.command.interaction.selection_menu;
+
+import net.javadiscord.javabot2.command.ResponseException;
+import org.javacord.api.interaction.SelectMenuInteraction;
+import org.javacord.api.interaction.callback.InteractionImmediateResponseBuilder;
+
+/**
+ * An interface that should be implemented by any class that is utilizing
+ * select menu interactions.
+ */
+public interface SelectionMenuHandler {
+ InteractionImmediateResponseBuilder handleSelectMenuInteraction(SelectMenuInteraction interaction) throws ResponseException;
+}