diff --git a/application/build.gradle b/application/build.gradle index 3ed6987a3f..50e97d9a39 100644 --- a/application/build.gradle +++ b/application/build.gradle @@ -32,7 +32,7 @@ jib { } container { mainClass = 'org.togetherjava.tjbot.BootstrapLauncher' - setCreationTime(java.time.Instant.now().toString()) + setCreationTime(Instant.now().toString()) } } @@ -45,7 +45,7 @@ shadowJar { dependencies { implementation project(':database') - implementation 'net.dv8tion:JDA:4.4.0_352' + implementation 'net.dv8tion:JDA:5.0.0-alpha.5' implementation 'org.apache.logging.log4j:log4j-core:2.16.0' runtimeOnly 'org.apache.logging.log4j:log4j-slf4j18-impl:2.16.0' @@ -71,4 +71,4 @@ dependencies { application { mainClass = 'org.togetherjava.tjbot.BootstrapLauncher' -} +} \ No newline at end of file diff --git a/application/src/main/java/org/togetherjava/tjbot/Application.java b/application/src/main/java/org/togetherjava/tjbot/Application.java index 7383bebb8d..f821d18c31 100644 --- a/application/src/main/java/org/togetherjava/tjbot/Application.java +++ b/application/src/main/java/org/togetherjava/tjbot/Application.java @@ -20,7 +20,7 @@ * Main class of the application. Use {@link #main(String[])} to start an instance of it. *

* New commands can be created by implementing - * {@link net.dv8tion.jda.api.events.interaction.SlashCommandEvent} or extending + * {@link net.dv8tion.jda.api.events.interaction.SlashCommandInteractionEvent} or extending * {@link org.togetherjava.tjbot.commands.SlashCommandAdapter}. They can then be registered in * {@link Features}. */ diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/BotCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/BotCommand.java new file mode 100644 index 0000000000..14f64817e6 --- /dev/null +++ b/application/src/main/java/org/togetherjava/tjbot/commands/BotCommand.java @@ -0,0 +1,162 @@ +package org.togetherjava.tjbot.commands; + +import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent; +import net.dv8tion.jda.api.events.interaction.component.SelectMenuInteractionEvent; +import net.dv8tion.jda.api.interactions.commands.Command; +import net.dv8tion.jda.api.interactions.commands.build.CommandData; +import org.jetbrains.annotations.NotNull; +import org.togetherjava.tjbot.commands.componentids.ComponentId; +import org.togetherjava.tjbot.commands.componentids.ComponentIdGenerator; +import org.togetherjava.tjbot.commands.componentids.Lifespan; + +import java.util.List; + +/** + * Represents a Discord command. + *

+ * All commands have to implement this interface. For convenience, there is a + * {@link BotCommandAdapter} available that implemented most methods already. A new command can then + * be registered by adding it to {@link Features}. + *

+ *

+ * Commands can either be visible globally in Discord or just to specific guilds. Some + * configurations can be made via {@link CommandData}, which is then to be returned by + * {@link #getData()} where the system will then pick it up from. + *

+ * After registration, the system will notify a command whenever one of its corresponding command + * method, buttons ({@link #onButtonClick(ButtonInteractionEvent, List)}) or menus + * ({@link #onSelectionMenu(SelectMenuInteractionEvent, List)}) have been triggered. + *

+ *

+ * Some example commands are available in {@link org.togetherjava.tjbot.commands.basic}. + */ +public interface BotCommand extends Feature { + + /** + * Gets the name of the command. + *

+ * After registration of the command, the name must not change anymore. + * + * @return the name of the command + */ + @NotNull + String getName(); + + + /** + * Gets the type of this command. + *

+ * After registration of the command, the type must not change anymore. + * + * @return the type of the command + */ + @NotNull + Command.Type getType(); + + + /** + * Gets the visibility of this command. + *

+ * After registration of the command, the visibility must not change anymore. + * + * @return the visibility of the command + */ + @NotNull + CommandVisibility getVisibility(); + + /** + * Gets the command data belonging to this command. + *

+ * See {@link net.dv8tion.jda.api.interactions.commands.build.Commands} for details on how to + * create and configure instances of it. + *

+ *

+ * This method may be called multiple times, implementations must not create new data each time + * but instead configure it once beforehand. The core system will automatically call this method + * to register the command to Discord. + * + * @return the command data of this command + */ + @NotNull + CommandData getData(); + + + /** + * Buttons can be attached below messages, and all buttons should provide the styling Discord + * provides. This means, red only for destructive actions, green only for successful etc. For + * examples on how they look, see + * Discord's + * support article. + * + * Triggered by the core system when a button corresponding to this implementation (based on + * {@link #getData()}) has been clicked. + *

+ * This method may be called multi-threaded. In particular, there are no guarantees that it will + * be executed on the same thread repeatedly or on the same thread that other event methods have + * been called on. + *

+ * Details are available in the given event and the event also enables implementations to + * respond to it. + *

+ * This method will be called in a multi-threaded context and the event may not be hold valid + * forever. + * + * @param event the event that triggered this + * @param args the arguments transported with the button, see + * {@link #acceptComponentIdGenerator(ComponentIdGenerator)} for details on how these are + * created + */ + void onButtonClick(@NotNull ButtonInteractionEvent event, @NotNull List args); + + /** + * Select menus can be attached below messages, just like Buttons. They allow you to configure a + * certain set of options, that the user can choose. For examples on how they look, see Discord's + * support article. + * + * Triggered by the core system when a selection menu corresponding to this implementation + * (based on {@link #getData()}) has been clicked. + *

+ * This method may be called multi-threaded. In particular, there are no guarantees that it will + * be executed on the same thread repeatedly or on the same thread that other event methods have + * been called on. + *

+ * Details are available in the given event and the event also enables implementations to + * respond to it. + *

+ * This method will be called in a multi-threaded context and the event may not be hold valid + * forever. + * + * @param event the event that triggered this + * @param args the arguments transported with the selection menu, see + * {@link #acceptComponentIdGenerator(ComponentIdGenerator)} for details on how these are + * created + */ + void onSelectionMenu(@NotNull SelectMenuInteractionEvent event, @NotNull List args); + + /** + * Triggered by the core system during its setup phase. It will provide the command a component + * id generator through this method, which can be used to generate component ids, as used for + * button or selection menus. + * + *

+ * The component ID has to be a UUID-string (see {@link java.util.UUID}), which is associated to + * a specific database entry, containing meta information about the command being executed. Such + * a database entry can be created and a UUID be obtained by using + * {@link ComponentIdGenerator#generate(ComponentId, Lifespan)}, as provided by the instance + * given to this method during system setup. The required {@link ComponentId} instance accepts + * optional extra arguments, which, if provided, can be picked up during the corresponding event + * (see {@link #onButtonClick(ButtonInteractionEvent, List)}, + * {@link #onSelectionMenu(SelectMenuInteractionEvent, List)}). + *

+ * Alternatively, if {@link BotCommandAdapter} has been extended, it also offers a handy + * {@link BotCommandAdapter#generateComponentId(String...)} method to ease the flow. + *

+ * See Component-IDs on + * our Wiki for more details and examples of how to use component IDs. + * + * + * @param generator the provided component id generator + */ + void acceptComponentIdGenerator(@NotNull ComponentIdGenerator generator); +} diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/BotCommandAdapter.java b/application/src/main/java/org/togetherjava/tjbot/commands/BotCommandAdapter.java new file mode 100644 index 0000000000..5d7d7751ef --- /dev/null +++ b/application/src/main/java/org/togetherjava/tjbot/commands/BotCommandAdapter.java @@ -0,0 +1,140 @@ +package org.togetherjava.tjbot.commands; + +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; +import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent; +import net.dv8tion.jda.api.events.interaction.component.SelectMenuInteractionEvent; +import net.dv8tion.jda.api.interactions.commands.Command; +import net.dv8tion.jda.api.interactions.commands.build.CommandData; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.togetherjava.tjbot.commands.componentids.ComponentId; +import org.togetherjava.tjbot.commands.componentids.ComponentIdGenerator; +import org.togetherjava.tjbot.commands.componentids.Lifespan; + +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +/** + * Adapter implementation of a {@link BotCommand}. The minimal setup only requires implementation of + * their respective command method. A new command can then be registered by adding it to + * {@link Features}. + *

+ * Further, {@link #onButtonClick(ButtonInteractionEvent, List)} and + * {@link #onSelectionMenu(SelectMenuInteractionEvent, List)} can be overridden if desired. The + * default implementation is empty, the adapter will not react to such events. + *

+ *

+ * The adapter manages some getters for you, you've to create the {@link CommandData} yourself. See + * {@link #BotCommandAdapter(CommandData, CommandVisibility)}} for more info on that. Minimal + * modifications can be done on the {@link CommandData} returned by {@link #getData()}. + *

+ *

+ * If implementations want to add buttons or selection menus, it is highly advised to use component + * IDs generated by {@link #generateComponentId(String...)}, which will automatically create IDs + * that are valid per {@link SlashCommand#onSlashCommand(SlashCommandInteractionEvent)}. + *

+ *

+ * Some example commands are available in {@link org.togetherjava.tjbot.commands.basic}. + * Registration of commands can be done in {@link Features}. + */ +public abstract class BotCommandAdapter implements BotCommand { + private final String name; + private final Command.Type type; + private final CommandVisibility visibility; + private final CommandData data; + private ComponentIdGenerator componentIdGenerator; + + /** + * Creates a new adapter with the given data. + * + * @param data the data for this command + * @param visibility the visibility of the command + */ + protected BotCommandAdapter(@NotNull CommandData data, @NotNull CommandVisibility visibility) { + this.data = Objects.requireNonNull(data, "The data shouldn't be null"); + this.visibility = Objects.requireNonNull(visibility, "The visibility shouldn't be null"); + + this.name = data.getName(); + this.type = data.getType(); + } + + @Override + public final @NotNull String getName() { + return name; + } + + @NotNull + @Override + public Command.Type getType() { + return type; + } + + @Override + public final @NotNull CommandVisibility getVisibility() { + return visibility; + } + + @Override + public @NotNull CommandData getData() { + return data; + } + + @Override + @Contract(mutates = "this") + public final void acceptComponentIdGenerator(@NotNull ComponentIdGenerator generator) { + componentIdGenerator = + Objects.requireNonNull(generator, "The given generator cannot be null"); + } + + @SuppressWarnings("NoopMethodInAbstractClass") + @Override + public void onButtonClick(@NotNull ButtonInteractionEvent event, @NotNull List args) { + // Adapter does not react by default, subclasses may change this behavior + } + + @SuppressWarnings("NoopMethodInAbstractClass") + @Override + public void onSelectionMenu(@NotNull SelectMenuInteractionEvent event, + @NotNull List args) { + // Adapter does not react by default, subclasses may change this behavior + } + + /** + * Helper method to generate component IDs that are considered valid per + * {@link #acceptComponentIdGenerator(ComponentIdGenerator)}. + *

+ * They can be used to create buttons or selection menus and transport additional data + * throughout the event (e.g. the user id who created the button dialog). + *

+ * IDs generated by this method have a regular lifespan, meaning that they might get evicted and + * expire after not being used for a long time. Use + * {@link #generateComponentId(Lifespan, String...)} to set other lifespans, if desired. + * + * @param args the extra arguments that should be part of the ID + * @return the generated component ID + */ + @SuppressWarnings("OverloadedVarargsMethod") + protected final @NotNull String generateComponentId(@NotNull String... args) { + return generateComponentId(Lifespan.REGULAR, args); + } + + /** + * Helper method to generate component IDs that are considered valid per + * {@link #acceptComponentIdGenerator(ComponentIdGenerator)}. + *

+ * They can be used to create buttons or selection menus and transport additional data + * throughout the event (e.g. the user id who created the button dialog). + * + * @param lifespan the lifespan of the component id, controls when an id that was not used for a + * long time might be evicted and expire + * @param args the extra arguments that should be part of the ID + * @return the generated component ID + */ + @SuppressWarnings({"OverloadedVarargsMethod", "WeakerAccess"}) + protected final @NotNull String generateComponentId(@NotNull Lifespan lifespan, + @NotNull String... args) { + return componentIdGenerator.generate(new ComponentId(getName(), Arrays.asList(args)), + lifespan); + } +} diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/SlashCommandVisibility.java b/application/src/main/java/org/togetherjava/tjbot/commands/CommandVisibility.java similarity index 89% rename from application/src/main/java/org/togetherjava/tjbot/commands/SlashCommandVisibility.java rename to application/src/main/java/org/togetherjava/tjbot/commands/CommandVisibility.java index e0d184c764..f8a2284590 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/SlashCommandVisibility.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/CommandVisibility.java @@ -3,7 +3,7 @@ /** * Visibility of a slash command, i.e. in which context it can be used by users. */ -public enum SlashCommandVisibility { +public enum CommandVisibility { /** * The command can be used within the context of a guild. */ diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/Features.java b/application/src/main/java/org/togetherjava/tjbot/commands/Features.java index 94c8e74a90..2edf3ccefd 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/Features.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/Features.java @@ -82,6 +82,10 @@ public enum Features { features.add(new RoleSelectCommand()); features.add(new TopHelpersCommand(database)); + // Message context commands + + // User context commands + // Mixtures features.add(new FreeCommand()); diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/MessageContextCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/MessageContextCommand.java new file mode 100644 index 0000000000..d53eb56b08 --- /dev/null +++ b/application/src/main/java/org/togetherjava/tjbot/commands/MessageContextCommand.java @@ -0,0 +1,63 @@ +package org.togetherjava.tjbot.commands; + +import net.dv8tion.jda.api.entities.Emoji; +import net.dv8tion.jda.api.events.interaction.command.MessageContextInteractionEvent; +import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent; +import net.dv8tion.jda.api.events.interaction.component.SelectMenuInteractionEvent; +import net.dv8tion.jda.api.interactions.commands.build.CommandData; +import net.dv8tion.jda.api.interactions.components.ComponentInteraction; +import net.dv8tion.jda.api.interactions.components.buttons.ButtonStyle; +import org.jetbrains.annotations.NotNull; +import org.togetherjava.tjbot.commands.componentids.ComponentIdGenerator; + +import java.util.List; + +/** + * A message context-command is a command, accessible when right-clicking on a message. + * + *

+ * Represents a Discord message context-command. Mostly decorating + * {@link net.dv8tion.jda.api.interactions.commands.Command}. + *

+ * All message context-commands have to implement this interface. For convenience, there is a + * {@link BotCommandAdapter} available that implemented most methods already. A new command can then + * be registered by adding it to {@link Features}. + *

+ *

+ * Context commands can either be visible globally in Discord or just to specific guilds. Minor + * adjustments can be made via {@link CommandData}, which is then to be returned by + * {@link #getData()} where the system will then pick it up from. + *

+ * After registration, the system will notify a command whenever one of its corresponding message + * context-commands ({@link #onMessageContext(MessageContextInteractionEvent)}), buttons + * ({@link #onButtonClick(ButtonInteractionEvent, List)}) or menus + * ({@link #onSelectionMenu(SelectMenuInteractionEvent, List)}) have been triggered. + *

+ *

+ * Some example commands are available in {@link org.togetherjava.tjbot.commands.basic}. + */ +public interface MessageContextCommand extends BotCommand { + + /** + * Triggered by the core system when a message context-command corresponding to this + * implementation (based on {@link #getData()}) has been triggered. + *

+ * This method may be called multithreaded. In particular, there are no guarantees that it will + * be executed on the same thread repeatedly or on the same thread that other event methods have + * been called on. + *

+ * Details are available in the given event and the event also enables implementations to + * respond to it. + *

+ * Buttons or menus have to be created with a component ID (see + * {@link ComponentInteraction#getComponentId()}, + * {@link net.dv8tion.jda.api.interactions.components.buttons.Button#of(ButtonStyle, String, Emoji)}) + * in a very specific format, otherwise the core system will fail to identify the command that + * corresponded to the button or menu click event and is unable to route it back. + *

+ * See {@link #acceptComponentIdGenerator(ComponentIdGenerator)} for more info. + * + * @param event the event that triggered this + */ + void onMessageContext(@NotNull MessageContextInteractionEvent event); +} diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/MessageReceiver.java b/application/src/main/java/org/togetherjava/tjbot/commands/MessageReceiver.java index e43284ee31..ee679a47f0 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/MessageReceiver.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/MessageReceiver.java @@ -1,7 +1,7 @@ package org.togetherjava.tjbot.commands; -import net.dv8tion.jda.api.events.message.guild.GuildMessageReceivedEvent; -import net.dv8tion.jda.api.events.message.guild.GuildMessageUpdateEvent; +import net.dv8tion.jda.api.events.message.MessageReceivedEvent; +import net.dv8tion.jda.api.events.message.MessageUpdateEvent; import org.jetbrains.annotations.NotNull; import java.util.regex.Pattern; @@ -38,7 +38,7 @@ public interface MessageReceiver extends Feature { * @param event the event that triggered this, containing information about the corresponding * message that was sent and received */ - void onMessageReceived(@NotNull GuildMessageReceivedEvent event); + void onMessageReceived(@NotNull MessageReceivedEvent event); /** * Triggered by the core system whenever an existing message was edited in a text channel of a @@ -47,5 +47,5 @@ public interface MessageReceiver extends Feature { * @param event the event that triggered this, containing information about the corresponding * message that was edited */ - void onMessageUpdated(@NotNull GuildMessageUpdateEvent event); + void onMessageUpdated(@NotNull MessageUpdateEvent event); } diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/MessageReceiverAdapter.java b/application/src/main/java/org/togetherjava/tjbot/commands/MessageReceiverAdapter.java index 506d2aa6e7..3d13b3b3e9 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/MessageReceiverAdapter.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/MessageReceiverAdapter.java @@ -1,7 +1,7 @@ package org.togetherjava.tjbot.commands; -import net.dv8tion.jda.api.events.message.guild.GuildMessageReceivedEvent; -import net.dv8tion.jda.api.events.message.guild.GuildMessageUpdateEvent; +import net.dv8tion.jda.api.events.message.MessageReceivedEvent; +import net.dv8tion.jda.api.events.message.MessageUpdateEvent; import org.jetbrains.annotations.NotNull; import java.util.regex.Pattern; @@ -10,8 +10,8 @@ * Adapter implementation of a {@link MessageReceiver}. A new receiver can then be registered by * adding it to {@link Features}. *

- * {@link #onMessageReceived(GuildMessageReceivedEvent)} and - * {@link #onMessageUpdated(GuildMessageUpdateEvent)} can be overridden if desired. The default + * {@link #onMessageReceived(MessageReceivedEvent)} and + * {@link #onMessageUpdated(MessageUpdateEvent)} can be overridden if desired. The default * implementation is empty, the adapter will not react to such events. */ public abstract class MessageReceiverAdapter implements MessageReceiver { @@ -35,13 +35,13 @@ protected MessageReceiverAdapter(@NotNull Pattern channelNamePattern) { @SuppressWarnings("NoopMethodInAbstractClass") @Override - public void onMessageReceived(@NotNull GuildMessageReceivedEvent event) { + public void onMessageReceived(@NotNull MessageReceivedEvent event) { // Adapter does not react by default, subclasses may change this behavior } @SuppressWarnings("NoopMethodInAbstractClass") @Override - public void onMessageUpdated(@NotNull GuildMessageUpdateEvent event) { + public void onMessageUpdated(@NotNull MessageUpdateEvent event) { // Adapter does not react by default, subclasses may change this behavior } } diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/SlashCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/SlashCommand.java index a5a01c20e8..5a6daa4b64 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/SlashCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/SlashCommand.java @@ -1,20 +1,28 @@ package org.togetherjava.tjbot.commands; import net.dv8tion.jda.api.entities.Emoji; -import net.dv8tion.jda.api.events.interaction.ButtonClickEvent; -import net.dv8tion.jda.api.events.interaction.SelectionMenuEvent; -import net.dv8tion.jda.api.events.interaction.SlashCommandEvent; +import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; +import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent; +import net.dv8tion.jda.api.events.interaction.component.SelectMenuInteractionEvent; +import net.dv8tion.jda.api.interactions.commands.Command; import net.dv8tion.jda.api.interactions.commands.build.CommandData; -import net.dv8tion.jda.api.interactions.components.ButtonStyle; +import net.dv8tion.jda.api.interactions.commands.build.Commands; +import net.dv8tion.jda.api.interactions.commands.build.OptionData; +import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData; import net.dv8tion.jda.api.interactions.components.ComponentInteraction; +import net.dv8tion.jda.api.interactions.components.buttons.ButtonStyle; import org.jetbrains.annotations.NotNull; -import org.togetherjava.tjbot.commands.componentids.ComponentId; import org.togetherjava.tjbot.commands.componentids.ComponentIdGenerator; -import org.togetherjava.tjbot.commands.componentids.Lifespan; import java.util.List; /** + * A slash-command is a command in Discord, with slash (/) as the prefix. These commands offer + * enhanced functionality and superior UX over text-commands. An example slash-command is the + * `/thread` command, this allows you to create threads using your keyboard. Try it out yourself! + * + *

* Represents a Discord slash-command. Mostly decorating * {@link net.dv8tion.jda.api.interactions.commands.Command}. *

@@ -28,32 +36,19 @@ * is then to be returned by {@link #getData()} where the system will then pick it up from. *

* After registration, the system will notify a command whenever one of its corresponding slash - * commands ({@link #onSlashCommand(SlashCommandEvent)}), buttons - * ({@link #onButtonClick(ButtonClickEvent, List)}) or menus - * ({@link #onSelectionMenu(SelectionMenuEvent, List)}) have been triggered. + * commands ({@link #onSlashCommand(SlashCommandInteractionEvent)}), buttons + * ({@link #onButtonClick(ButtonInteractionEvent, List)}) or menus + * ({@link #onSelectionMenu(SelectMenuInteractionEvent, List)}) have been triggered. *

*

* Some example commands are available in {@link org.togetherjava.tjbot.commands.basic}. */ -public interface SlashCommand extends Feature { - - /** - * Gets the name of the command. - *

- * Requirements for this are documented in {@link CommandData#CommandData(String, String)}. - *

- *

- * After registration of the command, the name must not change anymore. - * - * @return the name of the command - */ - @NotNull - String getName(); +public interface SlashCommand extends BotCommand { /** * Gets the description of the command. *

- * Requirements for this are documented in {@link CommandData#CommandData(String, String)}. + * Requirements for this are documented in {@link Commands#slash(String, String)}. *

*

* After registration of the command, the description must not change anymore. @@ -63,23 +58,13 @@ public interface SlashCommand extends Feature { @NotNull String getDescription(); - /** - * Gets the visibility of this command. - *

- * After registration of the command, the visibility must not change anymore. - * - * @return the visibility of the command - */ - @NotNull - SlashCommandVisibility getVisibility(); - /** * Gets the command data belonging to this command. *

* The data can be used to configure the settings for this command, i.e. adding options, * subcommands, menus and more. *

- * See {@link CommandData} for details on how to create and configure instances of it. + * See {@link SlashCommandData} for details on how to create and configure instances of it. *

*

* This method may be called multiple times, implementations must not create new data each time @@ -89,13 +74,13 @@ public interface SlashCommand extends Feature { * @return the command data of this command */ @NotNull - CommandData getData(); + SlashCommandData getData(); /** * Triggered by the core system when a slash command corresponding to this implementation (based * on {@link #getData()}) has been triggered. *

- * This method may be called multi-threaded. In particular, there are no guarantees that it will + * This method may be called multithreaded. In particular, there are no guarantees that it will * be executed on the same thread repeatedly or on the same thread that other event methods have * been called on. *

@@ -104,80 +89,37 @@ public interface SlashCommand extends Feature { *

* Buttons or menus have to be created with a component ID (see * {@link ComponentInteraction#getComponentId()}, - * {@link net.dv8tion.jda.api.interactions.components.Button#of(ButtonStyle, String, Emoji)}) in - * a very specific format, otherwise the core system will fail to identify the command that + * {@link net.dv8tion.jda.api.interactions.components.buttons.Button#of(ButtonStyle, String, Emoji)}) + * in a very specific format, otherwise the core system will fail to identify the command that * corresponded to the button or menu click event and is unable to route it back. *

- * The component ID has to be a UUID-string (see {@link java.util.UUID}), which is associated to - * a specific database entry, containing meta information about the command being executed. Such - * a database entry can be created and a UUID be obtained by using - * {@link ComponentIdGenerator#generate(ComponentId, Lifespan)}, as provided by the instance - * given to {@link #acceptComponentIdGenerator(ComponentIdGenerator)} during system setup. The - * required {@link ComponentId} instance accepts optional extra arguments, which, if provided, - * can be picked up during the corresponding event (see - * {@link #onButtonClick(ButtonClickEvent, List)}, - * {@link #onSelectionMenu(SelectionMenuEvent, List)}). - *

- * Alternatively, if {@link SlashCommandAdapter} has been extended, it also offers a handy - * {@link SlashCommandAdapter#generateComponentId(String...)} method to ease the flow. - *

- * See Component-IDs on - * our Wiki for more details and examples of how to use component IDs. - *

- * This method will be called in a multi-threaded context and the event may not be hold valid - * forever. - * - * @param event the event that triggered this - */ - void onSlashCommand(@NotNull SlashCommandEvent event); - - /** - * Triggered by the core system when a button corresponding to this implementation (based on - * {@link #getData()}) has been clicked. - *

- * This method may be called multi-threaded. In particular, there are no guarantees that it will - * be executed on the same thread repeatedly or on the same thread that other event methods have - * been called on. - *

- * Details are available in the given event and the event also enables implementations to - * respond to it. - *

- * This method will be called in a multi-threaded context and the event may not be hold valid - * forever. + * See {@link #acceptComponentIdGenerator(ComponentIdGenerator)} for more info on the ID's. * * @param event the event that triggered this - * @param args the arguments transported with the button, see - * {@link #onSlashCommand(SlashCommandEvent)} for details on how these are created */ - void onButtonClick(@NotNull ButtonClickEvent event, @NotNull List args); + void onSlashCommand(@NotNull SlashCommandInteractionEvent event); /** - * Triggered by the core system when a selection menu corresponding to this implementation - * (based on {@link #getData()}) has been clicked. - *

- * This method may be called multi-threaded. In particular, there are no guarantees that it will + * Autocompletion is comparable, but not the same as slash-command choices. Choices allow you to + * set a static list of {@value OptionData#MAX_CHOICES} possible "choices" to the commmand. + * Autocomplete allows you to dynamically give the user a list of + * {@value OptionData#MAX_CHOICES} possible choices. These choices can be generated based on the + * input of the user, the functionality is comparable to Google's autocompletion when searching + * for something. + *

+ * Triggered by the core system when a slash command's autocomplete corresponding to this + * implementation (based on {@link #getData()}) has been triggered. Don't forget to enable + * autocomplete using {@link OptionData#setAutoComplete(boolean)}! + *

+ * This method may be called multithreaded. In particular, there are no guarantees that it will * be executed on the same thread repeatedly or on the same thread that other event methods have * been called on. - *

+ *

* Details are available in the given event and the event also enables implementations to - * respond to it. - *

- * This method will be called in a multi-threaded context and the event may not be hold valid - * forever. + * respond to it.
+ * See {@link #acceptComponentIdGenerator(ComponentIdGenerator)} for more info on the ID's. * * @param event the event that triggered this - * @param args the arguments transported with the selection menu, see - * {@link #onSlashCommand(SlashCommandEvent)} for details on how these are created - */ - void onSelectionMenu(@NotNull SelectionMenuEvent event, @NotNull List args); - - /** - * Triggered by the core system during its setup phase. It will provide the command a component - * id generator through this method, which can be used to generate component ids, as used for - * button or selection menus. See {@link #onSlashCommand(SlashCommandEvent)} for details on how - * to use this. - * - * @param generator the provided component id generator */ - void acceptComponentIdGenerator(@NotNull ComponentIdGenerator generator); + void onAutoComplete(@NotNull CommandAutoCompleteInteractionEvent event); } diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/SlashCommandAdapter.java b/application/src/main/java/org/togetherjava/tjbot/commands/SlashCommandAdapter.java index f49d82a639..ce0dce4c5d 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/SlashCommandAdapter.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/SlashCommandAdapter.java @@ -1,52 +1,50 @@ package org.togetherjava.tjbot.commands; -import net.dv8tion.jda.api.events.interaction.ButtonClickEvent; -import net.dv8tion.jda.api.events.interaction.SelectionMenuEvent; -import net.dv8tion.jda.api.events.interaction.SlashCommandEvent; +import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; +import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent; +import net.dv8tion.jda.api.events.interaction.component.SelectMenuInteractionEvent; 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.build.SlashCommandData; import org.jetbrains.annotations.NotNull; -import org.togetherjava.tjbot.commands.componentids.ComponentId; -import org.togetherjava.tjbot.commands.componentids.ComponentIdGenerator; -import org.togetherjava.tjbot.commands.componentids.Lifespan; -import java.util.Arrays; import java.util.List; -import java.util.Objects; /** * Adapter implementation of a {@link SlashCommand}. The minimal setup only requires implementation - * of {@link #onSlashCommand(SlashCommandEvent)}. A new command can then be registered by adding it - * to {@link Features}. + * of {@link #onSlashCommand(SlashCommandInteractionEvent)}. A new command can then be registered by + * adding it to {@link Features}. *

- * Further, {@link #onButtonClick(ButtonClickEvent, List)} and - * {@link #onSelectionMenu(SelectionMenuEvent, List)} can be overridden if desired. The default - * implementation is empty, the adapter will not react to such events. + * Further, {@link #onButtonClick(ButtonInteractionEvent, List)} and + * {@link #onSelectionMenu(SelectMenuInteractionEvent, List)} can be overridden if desired. The + * default implementation is empty, the adapter will not react to such events. *

*

* The adapter manages all command related data itself, which can be provided during construction - * (see {@link #SlashCommandAdapter(String, String, SlashCommandVisibility)}). In order to add - * options, subcommands or similar command configurations, use {@link #getData()} and mutate the - * returned data object (see {@link CommandData} for details on how to work with this class). + * (see {@link #SlashCommandAdapter(String, String, CommandVisibility)}). In order to add options, + * subcommands or similar command configurations, use {@link #getData()} and mutate the returned + * data object (see {@link CommandData} for details on how to work with this class). *

*

* If implementations want to add buttons or selection menus, it is highly advised to use component * IDs generated by {@link #generateComponentId(String...)}, which will automatically create IDs - * that are valid per {@link SlashCommand#onSlashCommand(SlashCommandEvent)}. + * that are valid per {@link SlashCommand#onSlashCommand(SlashCommandInteractionEvent)}. *

*

* Some example commands are available in {@link org.togetherjava.tjbot.commands.basic}. A minimal - * setup would consist of a class like + * setup would consist of a class like: * *

  * {
  *     @code
  *     public class PingCommand extends SlashCommandAdapter {
  *         public PingCommand() {
- *             super("ping", "Responds with 'Pong!'", SlashCommandVisibility.GUILD);
+ *             super("ping", "Responds with 'Pong!'", CommandVisibility.GUILD);
  *         }
  *
  *         @Override
- *         public void onSlashCommand(@NotNull SlashCommandEvent event) {
+ *         public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) {
  *             event.reply("Pong!").queue();
  *         }
  *     }
@@ -55,34 +53,25 @@
  * 

* and registration of an instance of that class in {@link Features}. */ -public abstract class SlashCommandAdapter implements SlashCommand { - private final String name; +public abstract class SlashCommandAdapter extends BotCommandAdapter implements SlashCommand { private final String description; - private final SlashCommandVisibility visibility; - private final CommandData data; - private ComponentIdGenerator componentIdGenerator; + private final SlashCommandData data; /** * Creates a new adapter with the given data. * * @param name the name of this command, requirements for this are documented in - * {@link CommandData#CommandData(String, String)} + * {@link Commands#slash(String, String)} * @param description the description of this command, requirements for this are documented in - * {@link CommandData#CommandData(String, String)} + * {@link Commands#slash(String, String)} * @param visibility the visibility of the command */ protected SlashCommandAdapter(@NotNull String name, @NotNull String description, - SlashCommandVisibility visibility) { - this.name = name; + @NotNull CommandVisibility visibility) { + super(Commands.slash(name, description), visibility); this.description = description; - this.visibility = visibility; - data = new CommandData(name, description); - } - - @Override - public final @NotNull String getName() { - return name; + this.data = (SlashCommandData) super.getData(); } @Override @@ -90,68 +79,15 @@ protected SlashCommandAdapter(@NotNull String name, @NotNull String description, return description; } + @NotNull @Override - public final @NotNull SlashCommandVisibility getVisibility() { - return visibility; - } - - @Override - public final @NotNull CommandData getData() { + public final SlashCommandData getData() { return data; } - @Override - public final void acceptComponentIdGenerator(@NotNull ComponentIdGenerator generator) { - componentIdGenerator = generator; - } - - @SuppressWarnings("NoopMethodInAbstractClass") - @Override - public void onButtonClick(@NotNull ButtonClickEvent event, @NotNull List args) { - // Adapter does not react by default, subclasses may change this behavior - } - @SuppressWarnings("NoopMethodInAbstractClass") @Override - public void onSelectionMenu(@NotNull SelectionMenuEvent event, @NotNull List args) { + public void onAutoComplete(@NotNull CommandAutoCompleteInteractionEvent event) { // Adapter does not react by default, subclasses may change this behavior } - - /** - * Helper method to generate component IDs that are considered valid per - * {@link SlashCommand#onSlashCommand(SlashCommandEvent)}. - *

- * They can be used to create buttons or selection menus and transport additional data - * throughout the event (e.g. the user id who created the button dialog). - *

- * IDs generated by this method have a regular lifespan, meaning that they might get evicted and - * expire after not being used for a long time. Use - * {@link #generateComponentId(Lifespan, String...)} to set other lifespans, if desired. - * - * @param args the extra arguments that should be part of the ID - * @return the generated component ID - */ - @SuppressWarnings("OverloadedVarargsMethod") - protected final @NotNull String generateComponentId(@NotNull String... args) { - return generateComponentId(Lifespan.REGULAR, args); - } - - /** - * Helper method to generate component IDs that are considered valid per - * {@link SlashCommand#onSlashCommand(SlashCommandEvent)}. - *

- * They can be used to create buttons or selection menus and transport additional data - * throughout the event (e.g. the user id who created the button dialog). - * - * @param lifespan the lifespan of the component id, controls when an id that was not used for a - * long time might be evicted and expire - * @param args the extra arguments that should be part of the ID - * @return the generated component ID - */ - @SuppressWarnings({"OverloadedVarargsMethod", "WeakerAccess"}) - protected final @NotNull String generateComponentId(@NotNull Lifespan lifespan, - @NotNull String... args) { - return Objects.requireNonNull(componentIdGenerator) - .generate(new ComponentId(getName(), Arrays.asList(args)), lifespan); - } } diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/UserContextCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/UserContextCommand.java new file mode 100644 index 0000000000..ee6026279b --- /dev/null +++ b/application/src/main/java/org/togetherjava/tjbot/commands/UserContextCommand.java @@ -0,0 +1,64 @@ +package org.togetherjava.tjbot.commands; + +import net.dv8tion.jda.api.entities.Emoji; +import net.dv8tion.jda.api.events.interaction.command.UserContextInteractionEvent; +import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent; +import net.dv8tion.jda.api.events.interaction.component.SelectMenuInteractionEvent; +import net.dv8tion.jda.api.interactions.commands.build.CommandData; +import net.dv8tion.jda.api.interactions.components.ComponentInteraction; +import net.dv8tion.jda.api.interactions.components.buttons.ButtonStyle; +import org.jetbrains.annotations.NotNull; +import org.togetherjava.tjbot.commands.componentids.ComponentIdGenerator; + +import java.util.List; + +/** + * A user context-command is a command, accessible when right-clicking on a member in a guild. These + * commands aren't accessible within DM's. + * + *

+ * Represents a Discord user context-command. Mostly decorating + * {@link net.dv8tion.jda.api.interactions.commands.Command}. + *

+ * All user context-commands have to implement this interface. For convenience, there is a + * {@link BotCommandAdapter} available that implemented most methods already. A new command can then + * be registered by adding it to {@link Features}. + *

+ *

+ * Context commands can either be visible globally in Discord or just to specific guilds. Minor + * adjustments can be made via {@link CommandData}, which is then to be returned by + * {@link #getData()} where the system will then pick it up from. + *

+ * After registration, the system will notify a command whenever one of its corresponding user + * context-commands ({@link #onUserContext(UserContextInteractionEvent)}), buttons + * ({@link #onButtonClick(ButtonInteractionEvent, List)}) or menus + * ({@link #onSelectionMenu(SelectMenuInteractionEvent, List)}) have been triggered. + *

+ *

+ * Some example commands are available in {@link org.togetherjava.tjbot.commands.basic}. + */ +public interface UserContextCommand extends BotCommand { + + /** + * Triggered by the core system when a user context-command corresponding to this implementation + * (based on {@link #getData()}) has been triggered. + *

+ * This method may be called multithreaded. In particular, there are no guarantees that it will + * be executed on the same thread repeatedly or on the same thread that other event methods have + * been called on. + *

+ * Details are available in the given event and the event also enables implementations to + * respond to it. + *

+ * Buttons or menus have to be created with a component ID (see + * {@link ComponentInteraction#getComponentId()}, + * {@link net.dv8tion.jda.api.interactions.components.buttons.Button#of(ButtonStyle, String, Emoji)}) + * in a very specific format, otherwise the core system will fail to identify the command that + * corresponded to the button or menu click event and is unable to route it back. + *

+ * See {@link #acceptComponentIdGenerator(ComponentIdGenerator)} for more info. + * + * @param event the event that triggered this + */ + void onUserContext(@NotNull UserContextInteractionEvent event); +} diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/basic/PingCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/basic/PingCommand.java index eec68480c3..260bf6bbbb 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/basic/PingCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/basic/PingCommand.java @@ -1,9 +1,9 @@ package org.togetherjava.tjbot.commands.basic; -import net.dv8tion.jda.api.events.interaction.SlashCommandEvent; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import org.jetbrains.annotations.NotNull; import org.togetherjava.tjbot.commands.SlashCommandAdapter; -import org.togetherjava.tjbot.commands.SlashCommandVisibility; +import org.togetherjava.tjbot.commands.CommandVisibility; /** * Implementation of an example command to illustrate how to respond to a user. @@ -15,7 +15,7 @@ public final class PingCommand extends SlashCommandAdapter { * Creates an instance of the ping pong command. */ public PingCommand() { - super("ping", "Bot responds with 'Pong!'", SlashCommandVisibility.GUILD); + super("ping", "Bot responds with 'Pong!'", CommandVisibility.GUILD); } /** @@ -24,7 +24,7 @@ public PingCommand() { * @param event the corresponding event */ @Override - public void onSlashCommand(@NotNull SlashCommandEvent event) { + public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) { event.reply("Pong!").queue(); } } diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/basic/RoleSelectCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/basic/RoleSelectCommand.java index c7931e86ef..2d08f5725f 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/basic/RoleSelectCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/basic/RoleSelectCommand.java @@ -4,24 +4,26 @@ import net.dv8tion.jda.api.MessageBuilder; import net.dv8tion.jda.api.Permission; import net.dv8tion.jda.api.entities.*; -import net.dv8tion.jda.api.events.interaction.SelectionMenuEvent; -import net.dv8tion.jda.api.events.interaction.SlashCommandEvent; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; +import net.dv8tion.jda.api.events.interaction.component.SelectMenuInteractionEvent; import net.dv8tion.jda.api.interactions.Interaction; +import net.dv8tion.jda.api.interactions.callbacks.IReplyCallback; import net.dv8tion.jda.api.interactions.commands.OptionMapping; import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.interactions.commands.build.OptionData; import net.dv8tion.jda.api.interactions.commands.build.SubcommandData; import net.dv8tion.jda.api.interactions.components.ActionRow; import net.dv8tion.jda.api.interactions.components.ComponentInteraction; +import net.dv8tion.jda.api.interactions.components.selections.SelectMenu; +import net.dv8tion.jda.api.interactions.components.selections.SelectMenuInteraction; import net.dv8tion.jda.api.interactions.components.selections.SelectOption; -import net.dv8tion.jda.api.interactions.components.selections.SelectionMenu; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.togetherjava.tjbot.commands.CommandVisibility; import org.togetherjava.tjbot.commands.SlashCommandAdapter; -import org.togetherjava.tjbot.commands.SlashCommandVisibility; import org.togetherjava.tjbot.commands.componentids.Lifespan; import java.awt.*; @@ -63,7 +65,7 @@ public final class RoleSelectCommand extends SlashCommandAdapter { */ public RoleSelectCommand() { super("role-select", "Sends a message where users can select their roles", - SlashCommandVisibility.GUILD); + CommandVisibility.GUILD); SubcommandData allRoles = new SubcommandData(ALL_OPTION, "Lists all the rolls in the server for users") @@ -89,7 +91,7 @@ private static SelectOption mapToSelectOption(@NotNull Role role) { } @Override - public void onSlashCommand(@NotNull final SlashCommandEvent event) { + public void onSlashCommand(@NotNull final SlashCommandInteractionEvent event) { Member member = Objects.requireNonNull(event.getMember(), "Member is null"); if (!member.hasPermission(Permission.MANAGE_ROLES)) { event.reply("You dont have the right permissions to use this command") @@ -105,8 +107,8 @@ public void onSlashCommand(@NotNull final SlashCommandEvent event) { return; } - SelectionMenu.Builder menu = - SelectionMenu.create(generateComponentId(Lifespan.PERMANENT, member.getId())); + SelectMenu.Builder menu = + SelectMenu.create(generateComponentId(Lifespan.PERMANENT, member.getId())); boolean isEphemeral = false; if (CHOOSE_OPTION.equals(event.getSubcommandName())) { @@ -139,13 +141,13 @@ public void onSlashCommand(@NotNull final SlashCommandEvent event) { * Adds role options to a selection menu. *

* - * @param event the {@link SlashCommandEvent} - * @param menu the menu to add options to {@link SelectionMenu.Builder} + * @param event the {@link SlashCommandInteractionEvent} + * @param menu the menu to add options to {@link SelectMenu.Builder} * @param placeHolder the placeholder for the menu {@link String} * @param minValues the minimum number of selections. nullable {@link Integer} */ private static void addMenuOptions(@NotNull final Interaction event, - @NotNull final SelectionMenu.Builder menu, @NotNull final String placeHolder, + @NotNull final SelectMenu.Builder menu, @NotNull final String placeHolder, @Nullable final Integer minValues) { Guild guild = Objects.requireNonNull(event.getGuild(), "The given guild cannot be null"); @@ -188,7 +190,7 @@ private static void addMenuOptions(@NotNull final Interaction event, } @Override - public void onSelectionMenu(@NotNull final SelectionMenuEvent event, + public void onSelectionMenu(@NotNull final SelectMenuInteractionEvent event, @NotNull final List args) { Guild guild = Objects.requireNonNull(event.getGuild(), "The given guild cannot be null"); @@ -211,13 +213,13 @@ public void onSelectionMenu(@NotNull final SelectionMenuEvent event, } /** - * Handles selection of a {@link SelectionMenuEvent}. + * Handles selection of a {@link SelectMenuInteractionEvent}. * - * @param event the unacknowledged {@link SelectionMenuEvent} + * @param event the unacknowledged {@link SelectMenuInteractionEvent} * @param selectedRoles the {@link Role roles} selected * @param guild the {@link Guild} */ - private static void handleRoleSelection(final @NotNull SelectionMenuEvent event, + private static void handleRoleSelection(final @NotNull SelectMenuInteractionEvent event, final @NotNull Collection selectedRoles, final Guild guild) { Collection rolesToAdd = new ArrayList<>(selectedRoles.size()); Collection rolesToRemove = new ArrayList<>(selectedRoles.size()); @@ -253,7 +255,7 @@ private static Function roleFromSelectOptionFunction(Guild g } /** - * Handles the selection of the {@link SelectionMenu} if it came from a builder. + * Handles the selection of the {@link SelectMenu} if it came from a builder. * * @param event the unacknowledged {@link ComponentInteraction} * @param selectedRoles the {@link Role roles} selected by the {@link User} from the @@ -261,11 +263,10 @@ private static Function roleFromSelectOptionFunction(Guild g */ private void handleNewRoleBuilderSelection(@NotNull final ComponentInteraction event, final @NotNull Collection selectedRoles) { - SelectionMenu.Builder menu = - SelectionMenu.create(generateComponentId(event.getUser().getId())) - .setPlaceholder("Select your roles") - .setMaxValues(selectedRoles.size()) - .setMinValues(0); + SelectMenu.Builder menu = SelectMenu.create(generateComponentId(event.getUser().getId())) + .setPlaceholder("Select your roles") + .setMaxValues(selectedRoles.size()) + .setMinValues(0); selectedRoles.forEach(role -> menu.addOption(role.getName(), role.getId())); @@ -297,7 +298,7 @@ private static void handleNullRole(final @NotNull SelectOption selectedOption) { * @param additionRoles the roles to add * @param removalRoles the roles to remove */ - private static void handleRoleModifications(@NotNull final Interaction event, + private static void handleRoleModifications(@NotNull final IReplyCallback event, final Member member, final @NotNull Guild guild, final Collection additionRoles, final Collection removalRoles) { guild.modifyMemberRoles(member, additionRoles, removalRoles) diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/basic/VcActivityCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/basic/VcActivityCommand.java index 6c575e43af..1b9a63f1c7 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/basic/VcActivityCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/basic/VcActivityCommand.java @@ -1,24 +1,21 @@ package org.togetherjava.tjbot.commands.basic; import net.dv8tion.jda.api.Permission; -import net.dv8tion.jda.api.entities.GuildVoiceState; -import net.dv8tion.jda.api.entities.Invite; -import net.dv8tion.jda.api.entities.Member; -import net.dv8tion.jda.api.entities.VoiceChannel; -import net.dv8tion.jda.api.events.interaction.SlashCommandEvent; +import net.dv8tion.jda.api.entities.*; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import net.dv8tion.jda.api.interactions.commands.Command; import net.dv8tion.jda.api.interactions.commands.OptionMapping; import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.interactions.commands.build.OptionData; import net.dv8tion.jda.api.interactions.commands.build.SubcommandData; -import net.dv8tion.jda.api.requests.restaction.interactions.ReplyAction; +import net.dv8tion.jda.api.requests.restaction.interactions.ReplyCallbackAction; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.togetherjava.tjbot.commands.SlashCommandAdapter; -import org.togetherjava.tjbot.commands.SlashCommandVisibility; +import org.togetherjava.tjbot.commands.CommandVisibility; import java.util.List; import java.util.Map; import java.util.Objects; @@ -96,7 +93,7 @@ public final class VcActivityCommand extends SlashCommandAdapter { public VcActivityCommand() { super("vc-activity", "Starts a VC activity (you need to be in an voice channel to run this command)", - SlashCommandVisibility.GUILD); + CommandVisibility.GUILD); SubcommandData applicationSubCommand = @@ -117,12 +114,12 @@ public VcActivityCommand() { @Override - public void onSlashCommand(@NotNull SlashCommandEvent event) { + public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) { Member member = Objects.requireNonNull(event.getMember(), "member is null"); GuildVoiceState voiceState = Objects.requireNonNull(member.getVoiceState(), "Voicestates aren't being cached, check the JDABuilder"); - if (!voiceState.inVoiceChannel()) { + if (!voiceState.inAudioChannel()) { event.reply("You need to be in a voicechannel to run this command!") .setEphemeral(true) .queue(); @@ -130,7 +127,14 @@ public void onSlashCommand(@NotNull SlashCommandEvent event) { return; } - VoiceChannel voiceChannel = Objects.requireNonNull(voiceState.getChannel()); + AudioChannel audioChannel = Objects.requireNonNull(voiceState.getChannel()); + + if (!(audioChannel instanceof VoiceChannel voiceChannel)) { + event.reply("You've to be in a voicechannel, not a stage channel!") + .setEphemeral(true) + .queue(); + return; + } Member selfMember = Objects.requireNonNull(event.getGuild()).getSelfMember(); if (!selfMember.hasPermission(Permission.CREATE_INSTANT_INVITE)) { @@ -177,7 +181,7 @@ public void onSlashCommand(@NotNull SlashCommandEvent event) { return Optional.empty(); } - private static void handleSubcommand(@NotNull SlashCommandEvent event, + private static void handleSubcommand(@NotNull SlashCommandInteractionEvent event, @NotNull VoiceChannel voiceChannel, @NotNull String applicationId, @Nullable Integer maxUses, @Nullable Integer maxAgeDays, @NotNull String applicationName) { @@ -193,8 +197,9 @@ private static void handleSubcommand(@NotNull SlashCommandEvent event, } - private static @NotNull ReplyAction replyInvite(@NotNull SlashCommandEvent event, - @NotNull Invite invite, @NotNull String applicationName) { + private static @NotNull ReplyCallbackAction replyInvite( + @NotNull SlashCommandInteractionEvent event, @NotNull Invite invite, + @NotNull String applicationName) { return event.reply(""" %s wants to start %s. Feel free to join by clicking %s , enjoy! @@ -202,7 +207,7 @@ private static void handleSubcommand(@NotNull SlashCommandEvent event, """.formatted(event.getUser().getAsTag(), applicationName, invite.getUrl())); } - private static void handleErrors(@NotNull SlashCommandEvent event, + private static void handleErrors(@NotNull SlashCommandInteractionEvent event, @Nullable Throwable throwable) { event.reply("Something went wrong :/").queue(); logger.warn("Something went wrong in the VcActivityCommand", throwable); diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/componentids/ComponentId.java b/application/src/main/java/org/togetherjava/tjbot/commands/componentids/ComponentId.java index eaeb9b17b8..b264e535c2 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/componentids/ComponentId.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/componentids/ComponentId.java @@ -1,14 +1,14 @@ package org.togetherjava.tjbot.commands.componentids; -import net.dv8tion.jda.api.events.interaction.SlashCommandEvent; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import org.jetbrains.annotations.NotNull; import java.util.List; /** * Payload carried by component IDs. See - * {@link org.togetherjava.tjbot.commands.SlashCommand#onSlashCommand(SlashCommandEvent)} for its - * usages. + * {@link org.togetherjava.tjbot.commands.SlashCommand#onSlashCommand(SlashCommandInteractionEvent)} + * for its usages. * * @param commandName the name of the command that handles the event associated to this component * ID, when triggered diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/componentids/ComponentIdGenerator.java b/application/src/main/java/org/togetherjava/tjbot/commands/componentids/ComponentIdGenerator.java index 52bffc901a..2b47ea1845 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/componentids/ComponentIdGenerator.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/componentids/ComponentIdGenerator.java @@ -1,9 +1,9 @@ package org.togetherjava.tjbot.commands.componentids; import net.dv8tion.jda.api.entities.Emoji; -import net.dv8tion.jda.api.events.interaction.SlashCommandEvent; -import net.dv8tion.jda.api.interactions.components.ButtonStyle; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import net.dv8tion.jda.api.interactions.components.ComponentInteraction; +import net.dv8tion.jda.api.interactions.components.buttons.ButtonStyle; import org.jetbrains.annotations.NotNull; /** @@ -12,8 +12,9 @@ * Component IDs are used during button or selection menu events. They can carry arbitrary data and * are persisted by the system. *

- * See {@link org.togetherjava.tjbot.commands.SlashCommand#onSlashCommand(SlashCommandEvent)} for - * more context on how to use this. + * See + * {@link org.togetherjava.tjbot.commands.SlashCommand#onSlashCommand(SlashCommandInteractionEvent)} + * for more context on how to use this. *

* The interface {@link ComponentIdParser} is the counterpart to this, offering parsing back the * payload from the ID. @@ -25,8 +26,8 @@ public interface ComponentIdGenerator { * interactions, such as button or selection menus. *

* See {@link ComponentInteraction#getComponentId()} and - * {@link net.dv8tion.jda.api.interactions.components.Button#of(ButtonStyle, String, Emoji)} for - * details on where the generated ID can be used. + * {@link net.dv8tion.jda.api.interactions.components.buttons.Button#of(ButtonStyle, String, Emoji)} + * for details on where the generated ID can be used. * * @param componentId the component ID payload to persist and generate a valid ID for * @param lifespan the lifespan of the generated and persisted component ID diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/componentids/ComponentIdParser.java b/application/src/main/java/org/togetherjava/tjbot/commands/componentids/ComponentIdParser.java index a6188da8b9..aa7ede56e1 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/componentids/ComponentIdParser.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/componentids/ComponentIdParser.java @@ -1,9 +1,9 @@ package org.togetherjava.tjbot.commands.componentids; import net.dv8tion.jda.api.entities.Emoji; -import net.dv8tion.jda.api.events.interaction.SlashCommandEvent; -import net.dv8tion.jda.api.interactions.components.ButtonStyle; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import net.dv8tion.jda.api.interactions.components.ComponentInteraction; +import net.dv8tion.jda.api.interactions.components.buttons.ButtonStyle; import org.jetbrains.annotations.NotNull; import java.util.Optional; @@ -14,8 +14,9 @@ * Component IDs are used during button or selection menu events. They can carry arbitrary data and * are persisted by the system. *

- * See {@link org.togetherjava.tjbot.commands.SlashCommand#onSlashCommand(SlashCommandEvent)} for - * more context on how to use this. + * See + * {@link org.togetherjava.tjbot.commands.SlashCommand#onSlashCommand(SlashCommandInteractionEvent)} + * for more context on how to use this. *

* The interface {@link ComponentIdGenerator} is the counterpart to this, offering generation of IDs * from payload. @@ -27,8 +28,8 @@ public interface ComponentIdParser { * interactions, such as button or selection menus. *

* See {@link ComponentInteraction#getComponentId()} and - * {@link net.dv8tion.jda.api.interactions.components.Button#of(ButtonStyle, String, Emoji)} for - * details on where the ID was originally transported with. + * {@link net.dv8tion.jda.api.interactions.components.buttons.Button#of(ButtonStyle, String, Emoji)} + * for details on where the ID was originally transported with. * * @param uuid the UUID to parse which represents the component ID * @return the payload associated to the given UUID, if empty the component ID either never diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/componentids/ComponentIdStore.java b/application/src/main/java/org/togetherjava/tjbot/commands/componentids/ComponentIdStore.java index 8aca4f38c3..ad4dcde67e 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/componentids/ComponentIdStore.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/componentids/ComponentIdStore.java @@ -4,7 +4,6 @@ import com.fasterxml.jackson.dataformat.csv.CsvMapper; import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; -import net.dv8tion.jda.api.events.interaction.SlashCommandEvent; import org.jetbrains.annotations.NotNull; import org.jooq.Result; import org.slf4j.Logger; @@ -27,8 +26,8 @@ /** * Thread-safe storage for component IDs. Can put, persist and get back component IDs based on * UUIDs. Component IDs are used for button and selection menu commands, see - * {@link org.togetherjava.tjbot.commands.SlashCommand#onSlashCommand(SlashCommandEvent)} for - * details. + * {@link org.togetherjava.tjbot.commands.SlashCommand#onSlashCommand(SlashCommandInteractionEvent)} + * for details. *

* Use {@link #putOrThrow(UUID, ComponentId, Lifespan)} to put and persist a component ID; and * {@link #get(UUID)} to get it back. Component IDs are persisted during application runs and can diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/componentids/package-info.java b/application/src/main/java/org/togetherjava/tjbot/commands/componentids/package-info.java index 1353844d85..239d79060a 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/componentids/package-info.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/componentids/package-info.java @@ -2,7 +2,7 @@ * This package provides utilities to generate, persist and parse component IDs. *

* See - * {@link org.togetherjava.tjbot.commands.SlashCommand#onSlashCommand(net.dv8tion.jda.api.events.interaction.SlashCommandEvent)} + * {@link org.togetherjava.tjbot.commands.SlashCommand#onSlashCommand(net.dv8tion.jda.api.events.interaction.SlashCommandInteractionEvent)} * for details on component IDs. *

* The class {@link org.togetherjava.tjbot.commands.componentids.ComponentIdStore} is the central diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/free/ChannelMonitor.java b/application/src/main/java/org/togetherjava/tjbot/commands/free/ChannelMonitor.java index af2619dfc9..33d3723db4 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/free/ChannelMonitor.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/free/ChannelMonitor.java @@ -64,7 +64,7 @@ public void addChannelForStatus(@NotNull final TextChannel channel) { * This method tests whether a guild id is configured for monitoring in the free command system. * To add a guild for monitoring see {@link org.togetherjava.tjbot.config.FreeCommandConfig} or * {@link #addChannelForStatus(TextChannel)}. - * + * * @param guildId the id of the guild to test. * @return whether the guild is configured in the free command system or not. */ @@ -77,7 +77,7 @@ public boolean isMonitoringGuild(final long guildId) { * system. To add a channel for monitoring see * {@link org.togetherjava.tjbot.config.FreeCommandConfig} or * {@link #addChannelToMonitor(long)}. - * + * * @param channelId the id of the channel to test. * @return {@code true} if the channel is configured in the system, {@code false} otherwise. */ @@ -136,7 +136,7 @@ public boolean isChannelInactive(@NotNull final TextChannel channel) { /** * This method sets the channel's status to 'busy' see {@link ChannelStatus#setBusy(long)} for * details. - * + * * @param channelId the id for the channel status to modify. * @param userId the id of the user changing the status to busy. * @throws IllegalArgumentException if the channel passed is not monitored. See @@ -149,7 +149,7 @@ public void setChannelBusy(final long channelId, final long userId) { /** * This method sets the channel's status to 'free', see {@link ChannelStatus#setFree()} for * details. - * + * * @param channelId the id for the channel status to modify. * @throws IllegalArgumentException if the channel passed is not monitored. See * {@link #addChannelToMonitor(long)} @@ -171,7 +171,7 @@ public void setChannelFree(final long channelId) { /** * This method provides a stream of the id's for channels where statuses are displayed. This is * streamed purely as a simple method of encapsulation. - * + * * @return a stream of channel id's */ public @NotNull Stream statusIds() { @@ -194,7 +194,7 @@ public void setChannelFree(final long channelId) { * It first updates the channel names, order and grouping(categories) according to * {@link net.dv8tion.jda.api.JDA} for the monitored channels. So that the output is always * consistent with remote changes. - * + * * @param guild the guild the message is intended for. * @return a string representing the busy/free status of channels in this guild. The String * includes emojis and other discord specific markup. Attempting to display this @@ -215,7 +215,7 @@ public String statusMessage(@NotNull final Guild guild) { // pointless ... added to remove warnings continue; } - Category category = channel.getParent(); + Category category = channel.getParentCategory(); if (category != null && !category.getName().equals(categoryName)) { categoryName = category.getName(); // append the category name on a new line with markup for underlining @@ -239,7 +239,7 @@ public String statusMessage(@NotNull final Guild guild) { * This method is run automatically during startup and should be run on a set schedule, as * defined in {@link org.togetherjava.tjbot.config.FreeCommandConfig}. The scheduled execution * is not currently implemented - * + * * @param guild the guild for which to test the channel statuses of. */ public void updateStatusFor(@NotNull Guild guild) { @@ -284,7 +284,7 @@ public void updateStatusFor(@NotNull Guild guild) { /** * The toString method for this class, it generates a human-readable text string of the * currently monitored channels and the channels the status are printed in. - * + * * @return the human-readable text string that describes this class. */ @Override diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/free/FreeCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/free/FreeCommand.java index ee289c0402..bff772511f 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/free/FreeCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/free/FreeCommand.java @@ -8,15 +8,15 @@ import net.dv8tion.jda.api.entities.TextChannel; import net.dv8tion.jda.api.events.GenericEvent; import net.dv8tion.jda.api.events.ReadyEvent; -import net.dv8tion.jda.api.events.interaction.SlashCommandEvent; -import net.dv8tion.jda.api.events.message.guild.GuildMessageReceivedEvent; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; +import net.dv8tion.jda.api.events.message.MessageReceivedEvent; import net.dv8tion.jda.api.requests.RestAction; import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.togetherjava.tjbot.commands.EventReceiver; import org.togetherjava.tjbot.commands.SlashCommandAdapter; -import org.togetherjava.tjbot.commands.SlashCommandVisibility; +import org.togetherjava.tjbot.commands.CommandVisibility; import org.togetherjava.tjbot.config.Config; import org.togetherjava.tjbot.config.FreeCommandConfig; @@ -24,7 +24,7 @@ import java.time.Instant; import java.util.*; -// TODO (can SlashCommandVisibility be narrower than GUILD?) +// TODO (can CommandVisibility be narrower than GUILD?) // TODO monitor all channels when list is empty? monitor none? // TODO (use other emojis? use images?) // TODO add command to add/remove/status channels to monitor? @@ -76,7 +76,7 @@ public final class FreeCommand extends SlashCommandAdapter implements EventRecei */ public FreeCommand() { super(COMMAND_NAME, "Marks this channel as free for another user to ask a question", - SlashCommandVisibility.GUILD); + CommandVisibility.GUILD); channelIdToMessageIdForStatus = new HashMap<>(); channelMonitor = new ChannelMonitor(); @@ -119,12 +119,12 @@ public void onReady(@NotNull final ReadyEvent event) { *

* If this is called on from a channel that was not configured for monitoring (see * {@link FreeCommandConfig}) the user will receive an ephemeral message stating such. - * + * * @param event the event that triggered this * @throws IllegalStateException if this method is called for a Global Slash Command */ @Override - public void onSlashCommand(@NotNull final SlashCommandEvent event) { + public void onSlashCommand(@NotNull final SlashCommandInteractionEvent event) { logger.debug("/free used by {} on channel {}", event.getUser().getAsTag(), event.getChannel().getName()); if (!handleShouldBeProcessed(event)) { @@ -154,7 +154,7 @@ public void onSlashCommand(@NotNull final SlashCommandEvent event) { * @param event the event to test for validity. * @return true if the event should be processed false otherwise. */ - private boolean handleShouldBeProcessed(@NotNull final SlashCommandEvent event) { + private boolean handleShouldBeProcessed(@NotNull final SlashCommandInteractionEvent event) { if (!isReady) { logger.debug( "Slash command requested by {} in {}(channel: {}) before command is ready.", @@ -240,7 +240,7 @@ private void checkBusyStatusAllChannels(@NotNull JDA jda) { return guild; } - private @NotNull Guild requiresGuild(SlashCommandEvent event) { + private @NotNull Guild requiresGuild(SlashCommandInteractionEvent event) { Guild guild = event.getGuild(); if (guild == null) { throw new IllegalStateException( @@ -289,26 +289,30 @@ private void checkBusyStatusAllChannels(@NotNull JDA jda) { public void onEvent(@NotNull GenericEvent event) { if (event instanceof ReadyEvent readyEvent) { onReady(readyEvent); - } else if (event instanceof GuildMessageReceivedEvent guildEvent) { - if (guildEvent.isWebhookMessage() || guildEvent.getAuthor().isBot()) { + } else if (event instanceof MessageReceivedEvent messageEvent) { + if (!messageEvent.isFromGuild()) { + return; + } + + if (messageEvent.isWebhookMessage() || messageEvent.getAuthor().isBot()) { return; } - if (!channelMonitor.isMonitoringChannel(guildEvent.getChannel().getIdLong())) { + if (!channelMonitor.isMonitoringChannel(messageEvent.getChannel().getIdLong())) { logger.debug( "Channel is not being monitored, ignoring message received in {} from {}", - guildEvent.getChannel().getName(), guildEvent.getAuthor()); + messageEvent.getChannel().getName(), messageEvent.getAuthor()); return; } - if (channelMonitor.isChannelBusy(guildEvent.getChannel().getIdLong())) { + if (channelMonitor.isChannelBusy(messageEvent.getChannel().getIdLong())) { logger.debug( "Channel status is currently busy, ignoring message received in {} from {}", - guildEvent.getChannel().getName(), guildEvent.getAuthor()); + messageEvent.getChannel().getName(), messageEvent.getAuthor()); return; } - channelMonitor.setChannelBusy(guildEvent.getChannel().getIdLong(), - guildEvent.getAuthor().getIdLong()); - displayStatus(channelMonitor.getStatusChannelFor(guildEvent.getGuild())); - guildEvent.getMessage().reply(UserStrings.NEW_QUESTION.message()).queue(); + channelMonitor.setChannelBusy(messageEvent.getChannel().getIdLong(), + messageEvent.getAuthor().getIdLong()); + displayStatus(channelMonitor.getStatusChannelFor(messageEvent.getGuild())); + messageEvent.getMessage().reply(UserStrings.NEW_QUESTION.message()).queue(); } } diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/free/FreeUtil.java b/application/src/main/java/org/togetherjava/tjbot/commands/free/FreeUtil.java index 99a002c257..beec330198 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/free/FreeUtil.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/free/FreeUtil.java @@ -2,7 +2,7 @@ import net.dv8tion.jda.api.entities.Message; import net.dv8tion.jda.api.entities.TextChannel; -import net.dv8tion.jda.api.interactions.Interaction; +import net.dv8tion.jda.api.interactions.callbacks.IReplyCallback; import net.dv8tion.jda.api.utils.TimeUtil; import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; @@ -23,11 +23,12 @@ enum FreeUtil { /** * Helper method to easily send ephemeral messages to users. - * + * * @param interaction The event or hook that this message is responding to * @param message The text to be display for the user to read. */ - public static void sendErrorMessage(@NotNull Interaction interaction, @NotNull String message) { + public static void sendErrorMessage(@NotNull IReplyCallback interaction, + @NotNull String message) { interaction.reply(message).setEphemeral(true).queue(); } @@ -89,7 +90,7 @@ public static void sendErrorMessage(@NotNull Interaction interaction, @NotNull S * Method that calculates a time value a specific duration before now. The duration is * configured in {@link FreeCommandConfig#INACTIVE_UNIT} and * {@link FreeCommandConfig#INACTIVE_DURATION}. - * + * * @return the time value a set duration before now. */ public static @NotNull OffsetDateTime inactiveTimeLimit() { diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/TeXCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/TeXCommand.java index 468fe7fda2..4ee7423852 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/TeXCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/TeXCommand.java @@ -1,10 +1,10 @@ package org.togetherjava.tjbot.commands.mathcommands; -import net.dv8tion.jda.api.events.interaction.ButtonClickEvent; -import net.dv8tion.jda.api.events.interaction.SlashCommandEvent; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; +import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent; import net.dv8tion.jda.api.interactions.commands.OptionType; -import net.dv8tion.jda.api.interactions.components.Button; -import net.dv8tion.jda.api.interactions.components.ButtonStyle; +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.scilab.forge.jlatexmath.ParseException; import org.scilab.forge.jlatexmath.TeXConstants; @@ -12,7 +12,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.togetherjava.tjbot.commands.SlashCommandAdapter; -import org.togetherjava.tjbot.commands.SlashCommandVisibility; +import org.togetherjava.tjbot.commands.CommandVisibility; import javax.imageio.ImageIO; import java.awt.*; @@ -46,13 +46,13 @@ public class TeXCommand extends SlashCommandAdapter { public TeXCommand() { super("tex", "This command accepts a latex expression and generates an image corresponding to it.", - SlashCommandVisibility.GUILD); + CommandVisibility.GUILD); getData().addOption(OptionType.STRING, LATEX_OPTION, "The latex which is rendered as an image", true); } @Override - public void onSlashCommand(@NotNull final SlashCommandEvent event) { + public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) { String latex = Objects.requireNonNull(event.getOption(LATEX_OPTION)).getAsString(); String userID = (Objects.requireNonNull(event.getMember()).getId()); TeXFormula formula; @@ -92,8 +92,9 @@ public void onSlashCommand(@NotNull final SlashCommandEvent event) { .queue(); } + @Override - public void onButtonClick(@NotNull final ButtonClickEvent event, + public void onButtonClick(@NotNull final ButtonInteractionEvent event, @NotNull final List args) { if (!args.get(0).equals(Objects.requireNonNull(event.getMember()).getId())) { event.reply("You are not the person who executed the command, you cannot do that") diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/AuditCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/AuditCommand.java index a9391a5871..cb53bf8e14 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/AuditCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/AuditCommand.java @@ -3,8 +3,8 @@ import net.dv8tion.jda.api.EmbedBuilder; import net.dv8tion.jda.api.JDA; import net.dv8tion.jda.api.entities.*; -import net.dv8tion.jda.api.events.interaction.SlashCommandEvent; -import net.dv8tion.jda.api.interactions.Interaction; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; +import net.dv8tion.jda.api.interactions.callbacks.IReplyCallback; import net.dv8tion.jda.api.interactions.commands.OptionMapping; import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.requests.RestAction; @@ -12,7 +12,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.togetherjava.tjbot.commands.SlashCommandAdapter; -import org.togetherjava.tjbot.commands.SlashCommandVisibility; +import org.togetherjava.tjbot.commands.CommandVisibility; import org.togetherjava.tjbot.config.Config; import java.time.ZoneOffset; @@ -42,7 +42,7 @@ public final class AuditCommand extends SlashCommandAdapter { */ public AuditCommand(@NotNull ModerationActionsStore actionsStore) { super(COMMAND_NAME, "Lists all moderation actions that have been taken against a user", - SlashCommandVisibility.GUILD); + CommandVisibility.GUILD); getData().addOption(OptionType.USER, TARGET_OPTION, "The user who to retrieve actions for", true); @@ -110,7 +110,7 @@ public AuditCommand(@NotNull ModerationActionsStore actionsStore) { } @Override - public void onSlashCommand(@NotNull SlashCommandEvent event) { + public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) { OptionMapping targetOption = Objects.requireNonNull(event.getOption(TARGET_OPTION), "The target is null"); User target = targetOption.getAsUser(); @@ -128,7 +128,7 @@ public void onSlashCommand(@NotNull SlashCommandEvent event) { @SuppressWarnings("BooleanMethodNameMustStartWithQuestion") private boolean handleChecks(@NotNull Member bot, @NotNull Member author, - @Nullable Member target, @NotNull Guild guild, @NotNull Interaction event) { + @Nullable Member target, @NotNull Guild guild, @NotNull IReplyCallback event) { // Member doesn't exist if attempting to audit a user who is not part of the guild. if (target != null && !ModerationUtils.handleCanInteractWithTarget(ACTION_VERB, bot, author, target, event)) { @@ -138,7 +138,7 @@ private boolean handleChecks(@NotNull Member bot, @NotNull Member author, } private void auditUser(@NotNull User user, @NotNull ISnowflake guild, - @NotNull Interaction event) { + @NotNull IReplyCallback event) { List actions = actionsStore.getActionsByTargetAscending(guild.getIdLong(), user.getIdLong()); diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/BanCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/BanCommand.java index f11d15772c..cdc3672a9d 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/BanCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/BanCommand.java @@ -1,12 +1,12 @@ package org.togetherjava.tjbot.commands.moderation; +import net.dv8tion.jda.api.JDA; import net.dv8tion.jda.api.Permission; import net.dv8tion.jda.api.entities.*; -import net.dv8tion.jda.api.events.GenericEvent; -import net.dv8tion.jda.api.events.interaction.SlashCommandEvent; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import net.dv8tion.jda.api.exceptions.ErrorResponseException; -import net.dv8tion.jda.api.interactions.Interaction; import net.dv8tion.jda.api.interactions.InteractionHook; +import net.dv8tion.jda.api.interactions.callbacks.IReplyCallback; import net.dv8tion.jda.api.interactions.commands.OptionMapping; import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.interactions.commands.build.OptionData; @@ -19,7 +19,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.togetherjava.tjbot.commands.SlashCommandAdapter; -import org.togetherjava.tjbot.commands.SlashCommandVisibility; +import org.togetherjava.tjbot.commands.CommandVisibility; import org.togetherjava.tjbot.config.Config; import java.time.Instant; @@ -57,7 +57,7 @@ public final class BanCommand extends SlashCommandAdapter { * @param actionsStore used to store actions issued by this command */ public BanCommand(@NotNull ModerationActionsStore actionsStore) { - super(COMMAND_NAME, "Bans the given user from the server", SlashCommandVisibility.GUILD); + super(COMMAND_NAME, "Bans the given user from the server", CommandVisibility.GUILD); OptionData durationData = new OptionData(OptionType.STRING, DURATION_OPTION, "the duration of the ban, permanent or temporary", true); @@ -76,7 +76,7 @@ public BanCommand(@NotNull ModerationActionsStore actionsStore) { } private static RestAction handleAlreadyBanned(@NotNull Guild.Ban ban, - @NotNull Interaction event) { + @NotNull IReplyCallback event) { String reason = ban.getReason(); String reasonText = reason == null || reason.isBlank() ? "" : " (reason: %s)".formatted(reason); @@ -88,7 +88,7 @@ private static RestAction handleAlreadyBanned(@NotNull Guild.Ba private static RestAction sendDm(@NotNull ISnowflake target, @Nullable ModerationUtils.TemporaryData temporaryData, @NotNull String reason, - @NotNull Guild guild, @NotNull GenericEvent event) { + @NotNull Guild guild, @NotNull JDA jda) { String durationMessage = temporaryData == null ? "permanently" : "for " + temporaryData.duration(); String dmMessage = @@ -99,8 +99,7 @@ private static RestAction sendDm(@NotNull ISnowflake target, """ .formatted(durationMessage, guild.getName(), reason); - return event.getJDA() - .openPrivateChannelById(target.getId()) + return jda.openPrivateChannelById(target.getId()) .flatMap(channel -> channel.sendMessage(dmMessage)) .mapToResult() .map(Result::isSuccess); @@ -120,7 +119,7 @@ private static RestAction sendDm(@NotNull ISnowflake target, } private static Optional> handleNotAlreadyBannedResponse( - @NotNull Throwable alreadyBannedFailure, @NotNull Interaction event, + @NotNull Throwable alreadyBannedFailure, @NotNull IReplyCallback event, @NotNull Guild guild, @NotNull User target) { if (alreadyBannedFailure instanceof ErrorResponseException errorResponseException) { if (errorResponseException.getErrorResponse() == ErrorResponse.UNKNOWN_BAN) { @@ -145,8 +144,9 @@ private static Optional> handleNotAlreadyBannedRespo @SuppressWarnings("MethodWithTooManyParameters") private RestAction banUserFlow(@NotNull User target, @NotNull Member author, @Nullable ModerationUtils.TemporaryData temporaryData, @NotNull String reason, - int deleteHistoryDays, @NotNull Guild guild, @NotNull SlashCommandEvent event) { - return sendDm(target, temporaryData, reason, guild, event) + int deleteHistoryDays, @NotNull Guild guild, + @NotNull SlashCommandInteractionEvent event) { + return sendDm(target, temporaryData, reason, guild, event.getJDA()) .flatMap(hasSentDm -> banUser(target, author, temporaryData, reason, deleteHistoryDays, guild).map(banResult -> hasSentDm)) .map(hasSentDm -> sendFeedback(hasSentDm, target, author, temporaryData, reason)) @@ -174,7 +174,7 @@ private AuditableRestAction banUser(@NotNull User target, @NotNull Member @SuppressWarnings({"BooleanMethodNameMustStartWithQuestion", "MethodWithTooManyParameters"}) private boolean handleChecks(@NotNull Member bot, @NotNull Member author, @Nullable Member target, @NotNull CharSequence reason, @NotNull Guild guild, - @NotNull Interaction event) { + @NotNull IReplyCallback event) { // Member doesn't exist if attempting to ban a user who is not part of the guild. if (target != null && !ModerationUtils.handleCanInteractWithTarget(ACTION_VERB, bot, author, target, event)) { @@ -195,7 +195,7 @@ private boolean handleChecks(@NotNull Member bot, @NotNull Member author, } @Override - public void onSlashCommand(@NotNull SlashCommandEvent event) { + public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) { OptionMapping targetOption = Objects.requireNonNull(event.getOption(TARGET_OPTION), "The target is null"); User target = targetOption.getAsUser(); diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/KickCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/KickCommand.java index 8c9fcf5dfe..b44a1efda2 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/KickCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/KickCommand.java @@ -1,10 +1,13 @@ package org.togetherjava.tjbot.commands.moderation; +import net.dv8tion.jda.api.JDA; import net.dv8tion.jda.api.Permission; -import net.dv8tion.jda.api.entities.*; -import net.dv8tion.jda.api.events.GenericEvent; -import net.dv8tion.jda.api.events.interaction.SlashCommandEvent; -import net.dv8tion.jda.api.interactions.Interaction; +import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.entities.ISnowflake; +import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.entities.MessageEmbed; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; +import net.dv8tion.jda.api.interactions.callbacks.IReplyCallback; import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.requests.RestAction; import net.dv8tion.jda.api.requests.restaction.AuditableRestAction; @@ -14,7 +17,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.togetherjava.tjbot.commands.SlashCommandAdapter; -import org.togetherjava.tjbot.commands.SlashCommandVisibility; +import org.togetherjava.tjbot.commands.CommandVisibility; import org.togetherjava.tjbot.config.Config; import java.util.Objects; @@ -44,7 +47,7 @@ public final class KickCommand extends SlashCommandAdapter { * @param actionsStore used to store actions issued by this command */ public KickCommand(@NotNull ModerationActionsStore actionsStore) { - super(COMMAND_NAME, "Kicks the given user from the server", SlashCommandVisibility.GUILD); + super(COMMAND_NAME, "Kicks the given user from the server", CommandVisibility.GUILD); getData().addOption(OptionType.USER, TARGET_OPTION, "The user who you want to kick", true) .addOption(OptionType.STRING, REASON_OPTION, "Why the user should be kicked", true); @@ -54,15 +57,16 @@ public KickCommand(@NotNull ModerationActionsStore actionsStore) { this.actionsStore = Objects.requireNonNull(actionsStore); } - private static void handleAbsentTarget(@NotNull Interaction event) { + private static void handleAbsentTarget(@NotNull IReplyCallback event) { event.reply("I can not kick the given user since they are not part of the guild anymore.") .setEphemeral(true) .queue(); } private void kickUserFlow(@NotNull Member target, @NotNull Member author, - @NotNull String reason, @NotNull Guild guild, @NotNull SlashCommandEvent event) { - sendDm(target, reason, guild, event) + @NotNull String reason, @NotNull Guild guild, + @NotNull SlashCommandInteractionEvent event) { + sendDm(target, reason, guild, event.getJDA()) .flatMap(hasSentDm -> kickUser(target, author, reason, guild) .map(kickResult -> hasSentDm)) .map(hasSentDm -> sendFeedback(hasSentDm, target, author, reason)) @@ -71,9 +75,8 @@ private void kickUserFlow(@NotNull Member target, @NotNull Member author, } private static RestAction sendDm(@NotNull ISnowflake target, @NotNull String reason, - @NotNull Guild guild, @NotNull GenericEvent event) { - return event.getJDA() - .openPrivateChannelById(target.getId()) + @NotNull Guild guild, @NotNull JDA jda) { + return jda.openPrivateChannelById(target.getId()) .flatMap(channel -> channel.sendMessage( """ Hey there, sorry to tell you but unfortunately you have been kicked from the server %s. @@ -110,7 +113,7 @@ private AuditableRestAction kickUser(@NotNull Member target, @NotNull Memb @SuppressWarnings({"BooleanMethodNameMustStartWithQuestion", "MethodWithTooManyParameters"}) private boolean handleChecks(@NotNull Member bot, @NotNull Member author, @Nullable Member target, @NotNull CharSequence reason, @NotNull Guild guild, - @NotNull Interaction event) { + @NotNull IReplyCallback event) { // Member doesn't exist if attempting to kick a user who is not part of the guild anymore. if (target == null) { handleAbsentTarget(event); @@ -134,7 +137,7 @@ private boolean handleChecks(@NotNull Member bot, @NotNull Member author, } @Override - public void onSlashCommand(@NotNull SlashCommandEvent event) { + public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) { Member target = Objects.requireNonNull(event.getOption(TARGET_OPTION), "The target is null") .getAsMember(); Member author = Objects.requireNonNull(event.getMember(), "The author is null"); diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/ModerationUtils.java b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/ModerationUtils.java index fcb916be95..7b053d1638 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/ModerationUtils.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/ModerationUtils.java @@ -3,7 +3,7 @@ import net.dv8tion.jda.api.EmbedBuilder; import net.dv8tion.jda.api.Permission; import net.dv8tion.jda.api.entities.*; -import net.dv8tion.jda.api.interactions.Interaction; +import net.dv8tion.jda.api.interactions.callbacks.IReplyCallback; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; @@ -56,7 +56,7 @@ public enum ModerationUtils { * @return whether the reason is valid */ @SuppressWarnings("BooleanMethodNameMustStartWithQuestion") - static boolean handleReason(@NotNull CharSequence reason, @NotNull Interaction event) { + static boolean handleReason(@NotNull CharSequence reason, @NotNull IReplyCallback event) { if (reason.length() <= REASON_MAX_LENGTH) { return true; } @@ -84,7 +84,7 @@ static boolean handleReason(@NotNull CharSequence reason, @NotNull Interaction e */ @SuppressWarnings("BooleanMethodNameMustStartWithQuestion") static boolean handleCanInteractWithTarget(@NotNull String actionVerb, @NotNull Member bot, - @NotNull Member author, @NotNull Member target, @NotNull Interaction event) { + @NotNull Member author, @NotNull Member target, @NotNull IReplyCallback event) { String targetTag = target.getUser().getAsTag(); if (!author.canInteract(target)) { event @@ -119,7 +119,7 @@ static boolean handleCanInteractWithTarget(@NotNull String actionVerb, @NotNull */ @SuppressWarnings("BooleanMethodNameMustStartWithQuestion") static boolean handleCanInteractWithRole(@NotNull Member bot, @NotNull Member author, - @NotNull Role role, @NotNull Interaction event) { + @NotNull Role role, @NotNull IReplyCallback event) { if (!author.canInteract(role)) { event .reply("The role %s is too powerful for you to interact with." @@ -155,7 +155,7 @@ static boolean handleCanInteractWithRole(@NotNull Member bot, @NotNull Member au @SuppressWarnings("BooleanMethodNameMustStartWithQuestion") static boolean handleHasBotPermissions(@NotNull String actionVerb, @NotNull Permission permission, @NotNull IPermissionHolder bot, @NotNull Guild guild, - @NotNull Interaction event) { + @NotNull IReplyCallback event) { if (!bot.hasPermission(permission)) { event .reply("I can not %s users in this guild since I do not have the %s permission." @@ -170,7 +170,8 @@ static boolean handleHasBotPermissions(@NotNull String actionVerb, return true; } - private static void handleAbsentTarget(@NotNull String actionVerb, @NotNull Interaction event) { + private static void handleAbsentTarget(@NotNull String actionVerb, + @NotNull IReplyCallback event) { event .reply("I can not %s the given user since they are not part of the guild anymore." .formatted(actionVerb)) @@ -212,7 +213,7 @@ private static void handleAbsentTarget(@NotNull String actionVerb, @NotNull Inte static boolean handleRoleChangeChecks(@Nullable Role role, @NotNull String actionVerb, @Nullable Member target, @NotNull Member bot, @NotNull Member author, @NotNull Guild guild, @NotNull Predicate hasRequiredRole, - @NotNull CharSequence reason, @NotNull Interaction event) { + @NotNull CharSequence reason, @NotNull IReplyCallback event) { if (role == null) { event .reply("Can not %s the user, unable to find the corresponding role on this server" @@ -260,7 +261,7 @@ static boolean handleRoleChangeChecks(@Nullable Role role, @NotNull String actio @SuppressWarnings("BooleanMethodNameMustStartWithQuestion") static boolean handleHasAuthorPermissions(@NotNull String actionVerb, @NotNull Permission permission, @NotNull IPermissionHolder author, @NotNull Guild guild, - @NotNull Interaction event) { + @NotNull IReplyCallback event) { if (!author.hasPermission(permission)) { event .reply("You can not %s users in this guild since you do not have the %s permission." @@ -287,7 +288,7 @@ static boolean handleHasAuthorPermissions(@NotNull String actionVerb, @SuppressWarnings("BooleanMethodNameMustStartWithQuestion") static boolean handleHasAuthorRole(@NotNull String actionVerb, @NotNull Predicate hasRequiredRole, @NotNull Member author, - @NotNull Interaction event) { + @NotNull IReplyCallback event) { if (author.getRoles().stream().map(Role::getName).anyMatch(hasRequiredRole)) { return true; } diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/MuteCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/MuteCommand.java index f10606c8a4..6281b9dcb9 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/MuteCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/MuteCommand.java @@ -1,9 +1,9 @@ package org.togetherjava.tjbot.commands.moderation; +import net.dv8tion.jda.api.JDA; import net.dv8tion.jda.api.entities.*; -import net.dv8tion.jda.api.events.GenericEvent; -import net.dv8tion.jda.api.events.interaction.SlashCommandEvent; -import net.dv8tion.jda.api.interactions.Interaction; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; +import net.dv8tion.jda.api.interactions.callbacks.IReplyCallback; import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.interactions.commands.build.OptionData; import net.dv8tion.jda.api.requests.RestAction; @@ -14,7 +14,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.togetherjava.tjbot.commands.SlashCommandAdapter; -import org.togetherjava.tjbot.commands.SlashCommandVisibility; +import org.togetherjava.tjbot.commands.CommandVisibility; import org.togetherjava.tjbot.config.Config; import java.time.Instant; @@ -51,7 +51,7 @@ public final class MuteCommand extends SlashCommandAdapter { */ public MuteCommand(@NotNull ModerationActionsStore actionsStore) { super(COMMAND_NAME, "Mutes the given user so that they can not send messages anymore", - SlashCommandVisibility.GUILD); + CommandVisibility.GUILD); OptionData durationData = new OptionData(OptionType.STRING, DURATION_OPTION, "the duration of the mute, permanent or temporary", true); @@ -66,13 +66,13 @@ public MuteCommand(@NotNull ModerationActionsStore actionsStore) { this.actionsStore = Objects.requireNonNull(actionsStore); } - private static void handleAlreadyMutedTarget(@NotNull Interaction event) { + private static void handleAlreadyMutedTarget(@NotNull IReplyCallback event) { event.reply("The user is already muted.").setEphemeral(true).queue(); } private static RestAction sendDm(@NotNull ISnowflake target, @Nullable ModerationUtils.TemporaryData temporaryData, @NotNull String reason, - @NotNull Guild guild, @NotNull GenericEvent event) { + @NotNull Guild guild, @NotNull JDA jda) { String durationMessage = temporaryData == null ? "permanently" : "for " + temporaryData.duration(); String dmMessage = @@ -83,8 +83,7 @@ private static RestAction sendDm(@NotNull ISnowflake target, The reason for the mute is: %s """ .formatted(durationMessage, guild.getName(), reason); - return event.getJDA() - .openPrivateChannelById(target.getId()) + return jda.openPrivateChannelById(target.getId()) .flatMap(channel -> channel.sendMessage(dmMessage)) .mapToResult() .map(Result::isSuccess); @@ -123,8 +122,8 @@ private AuditableRestAction muteUser(@NotNull Member target, @NotNull Memb @SuppressWarnings("MethodWithTooManyParameters") private void muteUserFlow(@NotNull Member target, @NotNull Member author, @Nullable ModerationUtils.TemporaryData temporaryData, @NotNull String reason, - @NotNull Guild guild, @NotNull SlashCommandEvent event) { - sendDm(target, temporaryData, reason, guild, event) + @NotNull Guild guild, @NotNull SlashCommandInteractionEvent event) { + sendDm(target, temporaryData, reason, guild, event.getJDA()) .flatMap(hasSentDm -> muteUser(target, author, temporaryData, reason, guild) .map(banResult -> hasSentDm)) .map(hasSentDm -> sendFeedback(hasSentDm, target, author, temporaryData, reason)) @@ -135,7 +134,7 @@ private void muteUserFlow(@NotNull Member target, @NotNull Member author, @SuppressWarnings({"BooleanMethodNameMustStartWithQuestion", "MethodWithTooManyParameters"}) private boolean handleChecks(@NotNull Member bot, @NotNull Member author, @Nullable Member target, @NotNull CharSequence reason, @NotNull Guild guild, - @NotNull Interaction event) { + @NotNull IReplyCallback event) { if (!ModerationUtils.handleRoleChangeChecks( ModerationUtils.getMutedRole(guild).orElse(null), ACTION_VERB, target, bot, author, guild, hasRequiredRole, reason, event)) { @@ -153,7 +152,7 @@ private boolean handleChecks(@NotNull Member bot, @NotNull Member author, } @Override - public void onSlashCommand(@NotNull SlashCommandEvent event) { + public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) { Member target = Objects.requireNonNull(event.getOption(TARGET_OPTION), "The target is null") .getAsMember(); Member author = Objects.requireNonNull(event.getMember(), "The author is null"); diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/UnbanCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/UnbanCommand.java index 516ee88183..05a796554c 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/UnbanCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/UnbanCommand.java @@ -2,16 +2,16 @@ import net.dv8tion.jda.api.Permission; import net.dv8tion.jda.api.entities.*; -import net.dv8tion.jda.api.events.interaction.SlashCommandEvent; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import net.dv8tion.jda.api.exceptions.ErrorResponseException; -import net.dv8tion.jda.api.interactions.Interaction; +import net.dv8tion.jda.api.interactions.callbacks.IReplyCallback; import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.requests.ErrorResponse; import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.togetherjava.tjbot.commands.SlashCommandAdapter; -import org.togetherjava.tjbot.commands.SlashCommandVisibility; +import org.togetherjava.tjbot.commands.CommandVisibility; import org.togetherjava.tjbot.config.Config; import java.util.Objects; @@ -37,7 +37,7 @@ public final class UnbanCommand extends SlashCommandAdapter { * @param actionsStore used to store actions issued by this command */ public UnbanCommand(@NotNull ModerationActionsStore actionsStore) { - super(COMMAND_NAME, "Unbans the given user from the server", SlashCommandVisibility.GUILD); + super(COMMAND_NAME, "Unbans the given user from the server", CommandVisibility.GUILD); getData() .addOption(OptionType.USER, TARGET_OPTION, "The banned user who you want to unban", @@ -50,7 +50,7 @@ public UnbanCommand(@NotNull ModerationActionsStore actionsStore) { } private void unban(@NotNull User target, @NotNull Member author, @NotNull String reason, - @NotNull Guild guild, @NotNull Interaction event) { + @NotNull Guild guild, @NotNull IReplyCallback event) { guild.unban(target).reason(reason).queue(result -> { MessageEmbed message = ModerationUtils.createActionResponse(author.getUser(), ModerationAction.UNBAN, target, null, reason); @@ -66,7 +66,7 @@ private void unban(@NotNull User target, @NotNull Member author, @NotNull String } private static void handleFailure(@NotNull Throwable unbanFailure, @NotNull User target, - @NotNull Interaction event) { + @NotNull IReplyCallback event) { String targetTag = target.getAsTag(); if (unbanFailure instanceof ErrorResponseException errorResponseException) { if (errorResponseException.getErrorResponse() == ErrorResponse.UNKNOWN_USER) { @@ -90,7 +90,7 @@ private static void handleFailure(@NotNull Throwable unbanFailure, @NotNull User @SuppressWarnings({"BooleanMethodNameMustStartWithQuestion"}) private boolean handleChecks(@NotNull IPermissionHolder bot, @NotNull Member author, - @NotNull CharSequence reason, @NotNull Guild guild, @NotNull Interaction event) { + @NotNull CharSequence reason, @NotNull Guild guild, @NotNull IReplyCallback event) { if (!ModerationUtils.handleHasAuthorRole(ACTION_VERB, hasRequiredRole, author, event)) { return false; } @@ -107,7 +107,7 @@ private boolean handleChecks(@NotNull IPermissionHolder bot, @NotNull Member aut } @Override - public void onSlashCommand(@NotNull SlashCommandEvent event) { + public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) { User target = Objects.requireNonNull(event.getOption(TARGET_OPTION), "The target is null") .getAsUser(); Member author = Objects.requireNonNull(event.getMember(), "The author is null"); diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/UnmuteCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/UnmuteCommand.java index fcd0f66a31..129733d23e 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/UnmuteCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/UnmuteCommand.java @@ -2,8 +2,8 @@ import net.dv8tion.jda.api.entities.*; import net.dv8tion.jda.api.events.GenericEvent; -import net.dv8tion.jda.api.events.interaction.SlashCommandEvent; -import net.dv8tion.jda.api.interactions.Interaction; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; +import net.dv8tion.jda.api.interactions.callbacks.IReplyCallback; import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.requests.RestAction; import net.dv8tion.jda.api.requests.restaction.AuditableRestAction; @@ -13,7 +13,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.togetherjava.tjbot.commands.SlashCommandAdapter; -import org.togetherjava.tjbot.commands.SlashCommandVisibility; +import org.togetherjava.tjbot.commands.CommandVisibility; import org.togetherjava.tjbot.config.Config; import java.util.Objects; @@ -44,7 +44,7 @@ public final class UnmuteCommand extends SlashCommandAdapter { public UnmuteCommand(@NotNull ModerationActionsStore actionsStore) { super(COMMAND_NAME, "Unmutes the given already muted user so that they can send messages again", - SlashCommandVisibility.GUILD); + CommandVisibility.GUILD); getData().addOption(OptionType.USER, TARGET_OPTION, "The user who you want to unmute", true) .addOption(OptionType.STRING, REASON_OPTION, "Why the user should be unmuted", true); @@ -54,7 +54,7 @@ public UnmuteCommand(@NotNull ModerationActionsStore actionsStore) { this.actionsStore = Objects.requireNonNull(actionsStore); } - private static void handleNotMutedTarget(@NotNull Interaction event) { + private static void handleNotMutedTarget(@NotNull IReplyCallback event) { event.reply("The user is not muted.").setEphemeral(true).queue(); } @@ -96,7 +96,8 @@ private AuditableRestAction unmuteUser(@NotNull Member target, @NotNull Me } private void unmuteUserFlow(@NotNull Member target, @NotNull Member author, - @NotNull String reason, @NotNull Guild guild, @NotNull SlashCommandEvent event) { + @NotNull String reason, @NotNull Guild guild, + @NotNull SlashCommandInteractionEvent event) { sendDm(target, reason, guild, event) .flatMap(hasSentDm -> unmuteUser(target, author, reason, guild) .map(banResult -> hasSentDm)) @@ -108,7 +109,7 @@ private void unmuteUserFlow(@NotNull Member target, @NotNull Member author, @SuppressWarnings({"BooleanMethodNameMustStartWithQuestion", "MethodWithTooManyParameters"}) private boolean handleChecks(@NotNull Member bot, @NotNull Member author, @Nullable Member target, @NotNull CharSequence reason, @NotNull Guild guild, - @NotNull Interaction event) { + @NotNull IReplyCallback event) { if (!ModerationUtils.handleRoleChangeChecks( ModerationUtils.getMutedRole(guild).orElse(null), ACTION_VERB, target, bot, author, guild, hasRequiredRole, reason, event)) { @@ -126,7 +127,7 @@ private boolean handleChecks(@NotNull Member bot, @NotNull Member author, } @Override - public void onSlashCommand(@NotNull SlashCommandEvent event) { + public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) { Member target = Objects.requireNonNull(event.getOption(TARGET_OPTION), "The target is null") .getAsMember(); Member author = Objects.requireNonNull(event.getMember(), "The author is null"); diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/WarnCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/WarnCommand.java index 2b2c8265c2..308aef6bd2 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/WarnCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/WarnCommand.java @@ -1,7 +1,7 @@ package org.togetherjava.tjbot.commands.moderation; import net.dv8tion.jda.api.entities.*; -import net.dv8tion.jda.api.events.interaction.SlashCommandEvent; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import net.dv8tion.jda.api.interactions.InteractionHook; import net.dv8tion.jda.api.interactions.commands.OptionMapping; import net.dv8tion.jda.api.interactions.commands.OptionType; @@ -12,7 +12,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.togetherjava.tjbot.commands.SlashCommandAdapter; -import org.togetherjava.tjbot.commands.SlashCommandVisibility; +import org.togetherjava.tjbot.commands.CommandVisibility; import org.togetherjava.tjbot.config.Config; import java.util.Objects; @@ -41,7 +41,7 @@ public final class WarnCommand extends SlashCommandAdapter { * @param actionsStore used to store actions issued by this command */ public WarnCommand(@NotNull ModerationActionsStore actionsStore) { - super("warn", "Warns the given user", SlashCommandVisibility.GUILD); + super("warn", "Warns the given user", CommandVisibility.GUILD); getData().addOption(OptionType.USER, USER_OPTION, "The user who you want to warn", true) .addOption(OptionType.STRING, REASON_OPTION, "Why you want to warn the user", true); @@ -53,7 +53,7 @@ public WarnCommand(@NotNull ModerationActionsStore actionsStore) { private @NotNull RestAction warnUserFlow(@NotNull User target, @NotNull Member author, @NotNull String reason, @NotNull Guild guild, - @NotNull SlashCommandEvent event) { + @NotNull SlashCommandInteractionEvent event) { return dmUser(target, reason, guild, event).map(hasSentDm -> { warnUser(target, author, reason, guild); return hasSentDm; @@ -63,7 +63,8 @@ public WarnCommand(@NotNull ModerationActionsStore actionsStore) { } private static @NotNull RestAction dmUser(@NotNull ISnowflake target, - @NotNull String reason, @NotNull Guild guild, @NotNull SlashCommandEvent event) { + @NotNull String reason, @NotNull Guild guild, + @NotNull SlashCommandInteractionEvent event) { return event.getJDA() .openPrivateChannelById(target.getId()) .flatMap(channel -> channel.sendMessage( @@ -98,7 +99,7 @@ private void warnUser(@NotNull User target, @NotNull Member author, @NotNull Str } @Override - public void onSlashCommand(@NotNull SlashCommandEvent event) { + public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) { OptionMapping targetOption = Objects.requireNonNull(event.getOption(USER_OPTION), "The target is null"); Member author = Objects.requireNonNull(event.getMember(), "The author is null"); @@ -116,7 +117,7 @@ public void onSlashCommand(@NotNull SlashCommandEvent event) { @SuppressWarnings("BooleanMethodNameMustStartWithQuestion") private boolean handleChecks(@NotNull Member bot, @NotNull Member author, - @Nullable Member target, String reason, @NotNull SlashCommandEvent event) { + @Nullable Member target, String reason, @NotNull SlashCommandInteractionEvent event) { if (target != null && !ModerationUtils.handleCanInteractWithTarget(ACTION_VERB, bot, author, target, event)) { return false; diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/system/BotCommandProvider.java b/application/src/main/java/org/togetherjava/tjbot/commands/system/BotCommandProvider.java new file mode 100644 index 0000000000..72a5e1a3e7 --- /dev/null +++ b/application/src/main/java/org/togetherjava/tjbot/commands/system/BotCommandProvider.java @@ -0,0 +1,29 @@ +package org.togetherjava.tjbot.commands.system; + +import org.jetbrains.annotations.NotNull; +import org.togetherjava.tjbot.commands.BotCommand; + +import java.util.Collection; +import java.util.Optional; + +/** + * Provides all registered commands. + */ +public interface BotCommandProvider { + /** + * Gets a list of all currently available and registered commands. + * + * @return all slash commands + */ + @NotNull + Collection getBotCommands(); + + /** + * Gets the command registered under the given name, if any. + * + * @param name the name of the command + * @return the command registered under this name, if any + */ + @NotNull + Optional getBotCommand(@NotNull String name); +} diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/system/BotCore.java b/application/src/main/java/org/togetherjava/tjbot/commands/system/BotCore.java index ce68ecde5f..143453c1dc 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/system/BotCore.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/system/BotCore.java @@ -2,15 +2,18 @@ import net.dv8tion.jda.api.JDA; import net.dv8tion.jda.api.Permission; -import net.dv8tion.jda.api.entities.AbstractChannel; +import net.dv8tion.jda.api.entities.Channel; import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.TextChannel; import net.dv8tion.jda.api.events.ReadyEvent; -import net.dv8tion.jda.api.events.interaction.ButtonClickEvent; -import net.dv8tion.jda.api.events.interaction.SelectionMenuEvent; -import net.dv8tion.jda.api.events.interaction.SlashCommandEvent; -import net.dv8tion.jda.api.events.message.guild.GuildMessageReceivedEvent; -import net.dv8tion.jda.api.events.message.guild.GuildMessageUpdateEvent; +import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent; +import net.dv8tion.jda.api.events.interaction.command.MessageContextInteractionEvent; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; +import net.dv8tion.jda.api.events.interaction.command.UserContextInteractionEvent; +import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent; +import net.dv8tion.jda.api.events.interaction.component.SelectMenuInteractionEvent; +import net.dv8tion.jda.api.events.message.MessageReceivedEvent; +import net.dv8tion.jda.api.events.message.MessageUpdateEvent; import net.dv8tion.jda.api.exceptions.ErrorHandler; import net.dv8tion.jda.api.hooks.ListenerAdapter; import net.dv8tion.jda.api.interactions.commands.Command; @@ -48,13 +51,18 @@ * event listener, using {@link net.dv8tion.jda.api.JDA#addEventListener(Object...)}. Afterwards, * the system is ready and will correctly forward events to all commands. */ -public final class BotCore extends ListenerAdapter implements SlashCommandProvider { +public final class BotCore extends ListenerAdapter implements BotCommandProvider { private static final Logger logger = LoggerFactory.getLogger(BotCore.class); private static final String RELOAD_COMMAND = "reload"; private static final ExecutorService COMMAND_SERVICE = Executors.newCachedThreadPool(); private static final ScheduledExecutorService ROUTINE_SERVICE = Executors.newScheduledThreadPool(5); private final Map nameToSlashCommands; + private final Map nameToUserContextCommands; + private final Map nameToMessageContextCommands; + + private final List commands; + private final ComponentIdParser componentIdParser; private final ComponentIdStore componentIdStore; private final Map channelNameToMessageReceiver = new HashMap<>(); @@ -71,6 +79,11 @@ public final class BotCore extends ListenerAdapter implements SlashCommandProvid public BotCore(@NotNull JDA jda, @NotNull Database database) { Collection features = Features.createFeatures(jda, database); + commands = features.stream() + .filter(BotCommand.class::isInstance) + .map(BotCommand.class::cast) + .toList(); + // Message receivers features.stream() .filter(MessageReceiver.class::isInstance) @@ -105,7 +118,7 @@ public BotCore(@NotNull JDA jda, @NotNull Database database) { nameToSlashCommands = features.stream() .filter(SlashCommand.class::isInstance) .map(SlashCommand.class::cast) - .collect(Collectors.toMap(SlashCommand::getName, Function.identity())); + .collect(Collectors.toMap(BotCommand::getName, Function.identity())); if (nameToSlashCommands.containsKey(RELOAD_COMMAND)) { throw new IllegalStateException( @@ -113,16 +126,25 @@ public BotCore(@NotNull JDA jda, @NotNull Database database) { } nameToSlashCommands.put(RELOAD_COMMAND, new ReloadCommand(this)); + // User context commands + nameToUserContextCommands = features.stream() + .filter(UserContextCommand.class::isInstance) + .map(UserContextCommand.class::cast) + .collect(Collectors.toMap(BotCommand::getName, Function.identity())); + + // Message context commands + nameToMessageContextCommands = features.stream() + .filter(MessageContextCommand.class::isInstance) + .map(MessageContextCommand.class::cast) + .collect(Collectors.toMap(BotCommand::getName, Function.identity())); + + // Component users componentIdStore = new ComponentIdStore(database); componentIdStore.addComponentIdRemovedListener(BotCore::onComponentIdRemoved); componentIdParser = uuid -> componentIdStore.get(UUID.fromString(uuid)); - nameToSlashCommands.values() - .forEach(slashCommand -> slashCommand - .acceptComponentIdGenerator(((componentId, lifespan) -> { - UUID uuid = UUID.randomUUID(); - componentIdStore.putOrThrow(uuid, componentId, lifespan); - return uuid.toString(); - }))); + + // Set component ID generators + nameToSlashCommands.values().forEach(this::setComponentIdGenerator); if (logger.isInfoEnabled()) { logger.info("Available commands: {}", nameToSlashCommands.keySet()); @@ -130,13 +152,15 @@ public BotCore(@NotNull JDA jda, @NotNull Database database) { } @Override - public @NotNull Collection getSlashCommands() { - return Collections.unmodifiableCollection(nameToSlashCommands.values()); + public @NotNull Collection getBotCommands() { + return Collections.unmodifiableCollection(commands); } @Override - public @NotNull Optional getSlashCommand(@NotNull String name) { - return Optional.ofNullable(nameToSlashCommands.get(name)); + public @NotNull Optional getBotCommand(@NotNull String name) { + return Optional.ofNullable(nameToSlashCommands.get(name)) + .or(() -> Optional.ofNullable(nameToUserContextCommands.get(name))) + .or(() -> Optional.ofNullable(nameToMessageContextCommands.get(name))); } @Override @@ -152,19 +176,21 @@ public void onReady(@NotNull ReadyEvent event) { } @Override - public void onGuildMessageReceived(@NotNull GuildMessageReceivedEvent event) { + public void onMessageReceived(@NotNull MessageReceivedEvent event) { getMessageReceiversSubscribedTo(event.getChannel()) .forEach(messageReceiver -> messageReceiver.onMessageReceived(event)); } @Override - public void onGuildMessageUpdate(@NotNull GuildMessageUpdateEvent event) { + public void onMessageUpdate(@NotNull MessageUpdateEvent event) { getMessageReceiversSubscribedTo(event.getChannel()) .forEach(messageReceiver -> messageReceiver.onMessageUpdated(event)); } + + private @NotNull Stream getMessageReceiversSubscribedTo( - @NotNull AbstractChannel channel) { + @NotNull Channel channel) { String channelName = channel.getName(); return channelNameToMessageReceiver.entrySet() .stream() @@ -174,28 +200,55 @@ public void onGuildMessageUpdate(@NotNull GuildMessageUpdateEvent event) { .map(Map.Entry::getValue); } + @Override - public void onSlashCommand(@NotNull SlashCommandEvent event) { + public void onSlashCommandInteraction(@NotNull SlashCommandInteractionEvent event) { logger.debug("Received slash command '{}' (#{}) on guild '{}'", event.getName(), event.getId(), event.getGuild()); COMMAND_SERVICE.execute(() -> requireSlashCommand(event.getName()).onSlashCommand(event)); } @Override - public void onButtonClick(@NotNull ButtonClickEvent event) { + public void onCommandAutoCompleteInteraction( + @NotNull CommandAutoCompleteInteractionEvent event) { + logger.debug("Received autocomplete '{}' (#{}) option '{}' on guild '{}'", event.getName(), + event.getFocusedOption().getName(), event.getId(), event.getGuild()); + COMMAND_SERVICE.execute(() -> requireSlashCommand(event.getName()).onAutoComplete(event)); + } + + + @Override + public void onUserContextInteraction(@NotNull UserContextInteractionEvent event) { + logger.debug("Received user context-command '{}' (#{}) on guild '{}'", event.getName(), + event.getId(), event.getGuild()); + COMMAND_SERVICE.execute(() -> requireUserCommand(event.getName()).onUserContext(event)); + } + + @Override + public void onMessageContextInteraction(@NotNull MessageContextInteractionEvent event) { + logger.debug("Received message context-command '{}' (#{}) on guild '{}'", event.getName(), + event.getId(), event.getGuild()); + COMMAND_SERVICE + .execute(() -> requireMessageCommand(event.getName()).onMessageContext(event)); + } + + + @Override + public void onButtonInteraction(@NotNull ButtonInteractionEvent event) { logger.debug("Received button click '{}' (#{}) on guild '{}'", event.getComponentId(), event.getId(), event.getGuild()); COMMAND_SERVICE.execute(() -> forwardComponentCommand(event, SlashCommand::onButtonClick)); } @Override - public void onSelectionMenu(@NotNull SelectionMenuEvent event) { + public void onSelectMenuInteraction(@NotNull SelectMenuInteractionEvent event) { logger.debug("Received selection menu event '{}' (#{}) on guild '{}'", event.getComponentId(), event.getId(), event.getGuild()); COMMAND_SERVICE .execute(() -> forwardComponentCommand(event, SlashCommand::onSelectionMenu)); } + private void registerReloadCommand(@NotNull Guild guild) { guild.retrieveCommands().queue(commands -> { // Has it been registered already? @@ -271,6 +324,29 @@ private void forwardComponentCommand(@NotNull T return Objects.requireNonNull(nameToSlashCommands.get(name)); } + /** + * Gets the given user context-command by its name and requires that it exists. + * + * @param name the name of the command to get + * @return the command with the given name + * @throws NullPointerException if the command with the given name was not registered + */ + private @NotNull UserContextCommand requireUserCommand(@NotNull String name) { + return Objects.requireNonNull(nameToUserContextCommands.get(name)); + } + + /** + * Gets the given message context-command by its name and requires that it exists. + * + * @param name the name of the command to get + * @return the command with the given name + * @throws NullPointerException if the command with the given name was not registered + */ + private @NotNull MessageContextCommand requireMessageCommand(@NotNull String name) { + return Objects.requireNonNull(nameToMessageContextCommands.get(name)); + } + + private static void handleRegisterErrors(Throwable ex, Guild guild) { new ErrorHandler().handle(ErrorResponse.MISSING_ACCESS, errorResponse -> { // Find a channel that we have permissions to write to @@ -279,7 +355,7 @@ private static void handleRegisterErrors(Throwable ex, Guild guild) { Optional channelToReportTo = guild.getTextChannelCache() .stream() .filter(channel -> guild.getPublicRole() - .hasPermission(channel, Permission.MESSAGE_WRITE)) + .hasPermission(channel, Permission.MESSAGE_SEND)) .findAny(); // Report the problem to the guild @@ -306,6 +382,14 @@ private static void onComponentIdRemoved(ComponentId componentId) { // in the future to, for example, disable buttons or delete the associated message } + private void setComponentIdGenerator(@NotNull BotCommand command) { + command.acceptComponentIdGenerator(((componentId, lifespan) -> { + UUID uuid = UUID.randomUUID(); + componentIdStore.putOrThrow(uuid, componentId, lifespan); + return uuid.toString(); + })); + } + /** * Extension of {@link java.util.function.BiConsumer} but for 3 elements. *

diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/system/ReloadCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/system/ReloadCommand.java index 33613c797a..9fddc6705b 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/system/ReloadCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/system/ReloadCommand.java @@ -4,18 +4,18 @@ import net.dv8tion.jda.api.Permission; import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.Member; -import net.dv8tion.jda.api.events.interaction.ButtonClickEvent; -import net.dv8tion.jda.api.events.interaction.SlashCommandEvent; -import net.dv8tion.jda.api.interactions.components.Button; -import net.dv8tion.jda.api.interactions.components.ButtonStyle; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; +import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent; +import net.dv8tion.jda.api.interactions.components.buttons.Button; +import net.dv8tion.jda.api.interactions.components.buttons.ButtonStyle; import net.dv8tion.jda.api.requests.RestAction; import net.dv8tion.jda.api.requests.restaction.CommandListUpdateAction; import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.togetherjava.tjbot.commands.SlashCommand; +import org.togetherjava.tjbot.commands.BotCommand; +import org.togetherjava.tjbot.commands.CommandVisibility; import org.togetherjava.tjbot.commands.SlashCommandAdapter; -import org.togetherjava.tjbot.commands.SlashCommandVisibility; import org.togetherjava.tjbot.commands.utils.MessageUtils; import java.util.ArrayList; @@ -37,7 +37,7 @@ */ public final class ReloadCommand extends SlashCommandAdapter { private static final Logger logger = LoggerFactory.getLogger(ReloadCommand.class); - private final SlashCommandProvider commandProvider; + private final BotCommandProvider commandProvider; /** * Creates the reload command, using the given provider as source of truth for the commands to @@ -46,15 +46,15 @@ public final class ReloadCommand extends SlashCommandAdapter { * @param commandProvider the provider of slash commands to reload when this command is * triggered */ - public ReloadCommand(@NotNull SlashCommandProvider commandProvider) { + public ReloadCommand(@NotNull BotCommandProvider commandProvider) { super("reload", "Uploads all existing slash-commands to Discord so they are fully up-to-date.", - SlashCommandVisibility.GUILD); + CommandVisibility.GUILD); this.commandProvider = commandProvider; } @Override - public void onSlashCommand(@NotNull SlashCommandEvent event) { + public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) { Member member = Objects.requireNonNull(event.getMember()); if (!member.hasPermission(Permission.MANAGE_SERVER)) { @@ -76,7 +76,7 @@ public void onSlashCommand(@NotNull SlashCommandEvent event) { } @Override - public void onButtonClick(@NotNull ButtonClickEvent event, @NotNull List args) { + public void onButtonClick(@NotNull ButtonInteractionEvent event, @NotNull List args) { // Ignore if another user clicked the button String userId = args.get(0); if (!userId.equals(Objects.requireNonNull(event.getMember()).getId())) { @@ -98,7 +98,7 @@ public void onButtonClick(@NotNull ButtonClickEvent event, @NotNull List // Reload global commands actions.add(updateCommandsIf( - command -> command.getVisibility() == SlashCommandVisibility.GLOBAL, + command -> command.getVisibility() == CommandVisibility.GLOBAL, getGlobalUpdateAction(event.getJDA()))); // Reload guild commands (potentially many guilds) @@ -108,7 +108,7 @@ public void onButtonClick(@NotNull ButtonClickEvent event, @NotNull List // list. However, correctly reducing RestActions in a stream is not trivial. getGuildUpdateActions(event.getJDA()) .map(updateAction -> updateCommandsIf( - command -> command.getVisibility() == SlashCommandVisibility.GUILD, + command -> command.getVisibility() == CommandVisibility.GUILD, updateAction)) .forEach(actions::add); logger.debug("Reloading commands over {} action-upstreams", actions.size()); @@ -135,12 +135,12 @@ public void onButtonClick(@NotNull ButtonClickEvent event, @NotNull List * @return the given upstream for chaining */ private @NotNull CommandListUpdateAction updateCommandsIf( - @NotNull Predicate commandFilter, + @NotNull Predicate commandFilter, @NotNull CommandListUpdateAction updateAction) { - return commandProvider.getSlashCommands() + return commandProvider.getBotCommands() .stream() .filter(commandFilter) - .map(SlashCommand::getData) + .map(BotCommand::getData) .reduce(updateAction, CommandListUpdateAction::addCommands, (x, y) -> x); } diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/system/SlashCommandProvider.java b/application/src/main/java/org/togetherjava/tjbot/commands/system/SlashCommandProvider.java deleted file mode 100644 index ef9f0357bd..0000000000 --- a/application/src/main/java/org/togetherjava/tjbot/commands/system/SlashCommandProvider.java +++ /dev/null @@ -1,29 +0,0 @@ -package org.togetherjava.tjbot.commands.system; - -import org.jetbrains.annotations.NotNull; -import org.togetherjava.tjbot.commands.SlashCommand; - -import java.util.Collection; -import java.util.Optional; - -/** - * Provides all registered slash commands. - */ -public interface SlashCommandProvider { - /** - * Gets a list of all currently available and registered slash commands. - * - * @return all slash commands - */ - @NotNull - Collection getSlashCommands(); - - /** - * Gets the slash command registered under the given name, if any. - * - * @param name the name of the command - * @return the command registered under this name, if any - */ - @NotNull - Optional getSlashCommand(@NotNull String name); -} diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/tags/TagCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/tags/TagCommand.java index ac15f24a51..630aca33bd 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/tags/TagCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/tags/TagCommand.java @@ -1,13 +1,13 @@ package org.togetherjava.tjbot.commands.tags; import net.dv8tion.jda.api.EmbedBuilder; -import net.dv8tion.jda.api.events.interaction.SlashCommandEvent; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import net.dv8tion.jda.api.interactions.commands.OptionMapping; import net.dv8tion.jda.api.interactions.commands.OptionType; -import net.dv8tion.jda.api.requests.restaction.interactions.ReplyAction; +import net.dv8tion.jda.api.requests.restaction.interactions.ReplyCallbackAction; import org.jetbrains.annotations.NotNull; import org.togetherjava.tjbot.commands.SlashCommandAdapter; -import org.togetherjava.tjbot.commands.SlashCommandVisibility; +import org.togetherjava.tjbot.commands.CommandVisibility; import java.time.Instant; import java.util.Objects; @@ -31,7 +31,7 @@ public final class TagCommand extends SlashCommandAdapter { * @param tagSystem the system providing the actual tag data */ public TagCommand(TagSystem tagSystem) { - super("tag", "Display a tags content", SlashCommandVisibility.GUILD); + super("tag", "Display a tags content", CommandVisibility.GUILD); this.tagSystem = tagSystem; @@ -43,7 +43,7 @@ public TagCommand(TagSystem tagSystem) { } @Override - public void onSlashCommand(@NotNull SlashCommandEvent event) { + public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) { String id = Objects.requireNonNull(event.getOption(ID_OPTION)).getAsString(); OptionMapping replyToUserOption = event.getOption(REPLY_TO_USER_OPTION); @@ -51,7 +51,7 @@ public void onSlashCommand(@NotNull SlashCommandEvent event) { return; } - ReplyAction message = event + ReplyCallbackAction message = event .replyEmbeds(new EmbedBuilder().setDescription(tagSystem.getTag(id).orElseThrow()) .setFooter(event.getUser().getName() + " • used " + event.getCommandString()) .setTimestamp(Instant.now()) diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/tags/TagManageCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/tags/TagManageCommand.java index e257db4c53..fe6c0f315b 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/tags/TagManageCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/tags/TagManageCommand.java @@ -3,9 +3,9 @@ import net.dv8tion.jda.api.EmbedBuilder; import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Role; -import net.dv8tion.jda.api.events.interaction.SlashCommandEvent; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import net.dv8tion.jda.api.exceptions.ErrorResponseException; -import net.dv8tion.jda.api.interactions.Interaction; +import net.dv8tion.jda.api.interactions.callbacks.IReplyCallback; import net.dv8tion.jda.api.interactions.commands.CommandInteraction; import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.interactions.commands.build.SubcommandData; @@ -14,7 +14,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.togetherjava.tjbot.commands.SlashCommandAdapter; -import org.togetherjava.tjbot.commands.SlashCommandVisibility; +import org.togetherjava.tjbot.commands.CommandVisibility; import org.togetherjava.tjbot.config.Config; import java.nio.charset.StandardCharsets; @@ -58,7 +58,7 @@ public final class TagManageCommand extends SlashCommandAdapter { * @param tagSystem the system providing the actual tag data */ public TagManageCommand(TagSystem tagSystem) { - super("tag-manage", "Provides commands to manage all tags", SlashCommandVisibility.GUILD); + super("tag-manage", "Provides commands to manage all tags", CommandVisibility.GUILD); this.tagSystem = tagSystem; hasRequiredRole = @@ -89,7 +89,7 @@ public TagManageCommand(TagSystem tagSystem) { .addOption(OptionType.STRING, ID_OPTION, ID_DESCRIPTION, true)); } - private static void sendSuccessMessage(@NotNull Interaction event, @NotNull String id, + private static void sendSuccessMessage(@NotNull IReplyCallback event, @NotNull String id, @NotNull String actionVerb) { logger.info("User '{}' {} the tag with id '{}'.", event.getUser().getId(), actionVerb, id); @@ -114,7 +114,7 @@ private static void sendSuccessMessage(@NotNull Interaction event, @NotNull Stri * @return the parsed message id, if successful */ private static OptionalLong parseMessageIdAndHandle(@NotNull String messageId, - @NotNull Interaction event) { + @NotNull IReplyCallback event) { try { return OptionalLong.of(Long.parseLong(messageId)); } catch (NumberFormatException e) { @@ -128,7 +128,7 @@ private static OptionalLong parseMessageIdAndHandle(@NotNull String messageId, } @Override - public void onSlashCommand(@NotNull SlashCommandEvent event) { + public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) { if (!hasTagManageRole(Objects.requireNonNull(event.getMember()))) { event.reply("Tags can only be managed by users with a corresponding role.") .setEphemeral(true) @@ -148,7 +148,7 @@ public void onSlashCommand(@NotNull SlashCommandEvent event) { } } - private void rawTag(@NotNull SlashCommandEvent event) { + private void rawTag(@NotNull SlashCommandInteractionEvent event) { String id = Objects.requireNonNull(event.getOption(ID_OPTION)).getAsString(); if (tagSystem.handleIsUnknownTag(id, event)) { return; @@ -269,7 +269,7 @@ private void handleActionWithMessage(@NotNull TagStatus requiredTagStatus, * @return whether the status of the given tag is not equal to the required status */ private boolean isWrongTagStatusAndHandle(@NotNull TagStatus requiredTagStatus, - @NotNull String id, @NotNull Interaction event) { + @NotNull String id, @NotNull IReplyCallback event) { if (requiredTagStatus == TagStatus.EXISTS) { return tagSystem.handleIsUnknownTag(id, event); } else if (requiredTagStatus == TagStatus.NOT_EXISTS) { diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/tags/TagSystem.java b/application/src/main/java/org/togetherjava/tjbot/commands/tags/TagSystem.java index a708ef5d5a..0a9090d0d5 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/tags/TagSystem.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/tags/TagSystem.java @@ -1,9 +1,9 @@ package org.togetherjava.tjbot.commands.tags; import net.dv8tion.jda.api.entities.Emoji; -import net.dv8tion.jda.api.interactions.Interaction; -import net.dv8tion.jda.api.interactions.components.Button; -import net.dv8tion.jda.api.interactions.components.ButtonStyle; +import net.dv8tion.jda.api.interactions.callbacks.IReplyCallback; +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.togetherjava.tjbot.commands.utils.StringDistances; import org.togetherjava.tjbot.db.Database; @@ -59,7 +59,7 @@ static Button createDeleteButton(String componentId) { * @return whether the given tag is unknown to the system */ @SuppressWarnings("BooleanMethodNameMustStartWithQuestion") - boolean handleIsUnknownTag(@NotNull String id, @NotNull Interaction event) { + boolean handleIsUnknownTag(@NotNull String id, @NotNull IReplyCallback event) { if (hasTag(id)) { return false; } diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/tags/TagsCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/tags/TagsCommand.java index 761faaebe1..698e98d0fb 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/tags/TagsCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/tags/TagsCommand.java @@ -2,12 +2,13 @@ import net.dv8tion.jda.api.EmbedBuilder; import net.dv8tion.jda.api.Permission; -import net.dv8tion.jda.api.events.interaction.ButtonClickEvent; -import net.dv8tion.jda.api.events.interaction.SlashCommandEvent; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; +import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent; +import net.dv8tion.jda.api.interactions.components.buttons.Button; import org.jetbrains.annotations.NotNull; import org.slf4j.LoggerFactory; import org.togetherjava.tjbot.commands.SlashCommandAdapter; -import org.togetherjava.tjbot.commands.SlashCommandVisibility; +import org.togetherjava.tjbot.commands.CommandVisibility; import java.time.Instant; import org.slf4j.Logger; import java.util.Collection; @@ -42,13 +43,13 @@ public final class TagsCommand extends SlashCommandAdapter { * @param tagSystem the system providing the actual tag data */ public TagsCommand(TagSystem tagSystem) { - super("tags", "Displays all available tags", SlashCommandVisibility.GUILD); + super("tags", "Displays all available tags", CommandVisibility.GUILD); this.tagSystem = tagSystem; } @Override - public void onSlashCommand(@NotNull SlashCommandEvent event) { + public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) { Collection tagIds = tagSystem.getAllIds(); if (tagIds.size() > MAX_TAGS_THRESHOLD_WARNING) { // TODO Implement the edge case @@ -72,7 +73,7 @@ public void onSlashCommand(@NotNull SlashCommandEvent event) { } @Override - public void onButtonClick(@NotNull ButtonClickEvent event, @NotNull List args) { + public void onButtonClick(@NotNull ButtonInteractionEvent event, @NotNull List args) { String userId = args.get(0); if (!event.getUser().getId().equals(userId) && !Objects.requireNonNull(event.getMember()) diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/tophelper/TopHelpersCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/tophelper/TopHelpersCommand.java index 876ce3dcb2..e50cd049cf 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/tophelper/TopHelpersCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/tophelper/TopHelpersCommand.java @@ -6,8 +6,9 @@ import com.github.freva.asciitable.HorizontalAlign; import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Role; -import net.dv8tion.jda.api.events.interaction.SlashCommandEvent; -import net.dv8tion.jda.api.interactions.Interaction; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; +import net.dv8tion.jda.api.interactions.callbacks.IDeferrableCallback; +import net.dv8tion.jda.api.interactions.callbacks.IReplyCallback; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jooq.Records; @@ -15,7 +16,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.togetherjava.tjbot.commands.SlashCommandAdapter; -import org.togetherjava.tjbot.commands.SlashCommandVisibility; +import org.togetherjava.tjbot.commands.CommandVisibility; import org.togetherjava.tjbot.config.Config; import org.togetherjava.tjbot.db.Database; @@ -57,7 +58,7 @@ public final class TopHelpersCommand extends SlashCommandAdapter { * @param database the database containing the message counts of top helpers */ public TopHelpersCommand(@NotNull Database database) { - super(COMMAND_NAME, "Lists top helpers for the last month", SlashCommandVisibility.GUILD); + super(COMMAND_NAME, "Lists top helpers for the last month", CommandVisibility.GUILD); // TODO Add options to optionally pick a time range once JDA/Discord offers a date-picker hasRequiredRole = Pattern.compile(Config.getInstance().getSoftModerationRolePattern()) .asMatchPredicate(); @@ -65,7 +66,7 @@ public TopHelpersCommand(@NotNull Database database) { } @Override - public void onSlashCommand(@NotNull SlashCommandEvent event) { + public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) { if (!handleHasAuthorRole(event.getMember(), event)) { return; } @@ -91,7 +92,7 @@ public void onSlashCommand(@NotNull SlashCommandEvent event) { } @SuppressWarnings("BooleanMethodNameMustStartWithQuestion") - private boolean handleHasAuthorRole(@NotNull Member author, @NotNull Interaction event) { + private boolean handleHasAuthorRole(@NotNull Member author, @NotNull IReplyCallback event) { if (author.getRoles().stream().map(Role::getName).anyMatch(hasRequiredRole)) { return true; } @@ -125,14 +126,14 @@ private boolean handleHasAuthorRole(@NotNull Member author, @NotNull Interaction .fetch(Records.mapping(TopHelperResult::new))); } - private static void handleError(@NotNull Throwable error, @NotNull Interaction event) { + private static void handleError(@NotNull Throwable error, @NotNull IDeferrableCallback event) { logger.warn("Failed to compute top-helpers", error); event.getHook().editOriginal("Sorry, something went wrong.").queue(); } private static void handleTopHelpers(@NotNull Collection topHelpers, @NotNull Collection members, @NotNull TimeRange timeRange, - @NotNull Interaction event) { + @NotNull IDeferrableCallback event) { Map userIdToMember = members.stream().collect(Collectors.toMap(Member::getIdLong, Function.identity())); diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/tophelper/TopHelpersMessageListener.java b/application/src/main/java/org/togetherjava/tjbot/commands/tophelper/TopHelpersMessageListener.java index fadb5b50ff..ff7ce406e8 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/tophelper/TopHelpersMessageListener.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/tophelper/TopHelpersMessageListener.java @@ -1,6 +1,6 @@ package org.togetherjava.tjbot.commands.tophelper; -import net.dv8tion.jda.api.events.message.guild.GuildMessageReceivedEvent; +import net.dv8tion.jda.api.events.message.MessageReceivedEvent; import org.jetbrains.annotations.NotNull; import org.togetherjava.tjbot.commands.MessageReceiverAdapter; import org.togetherjava.tjbot.config.Config; @@ -28,7 +28,7 @@ public TopHelpersMessageListener(@NotNull Database database) { } @Override - public void onMessageReceived(@NotNull GuildMessageReceivedEvent event) { + public void onMessageReceived(@NotNull MessageReceivedEvent event) { if (event.getAuthor().isBot() || event.isWebhookMessage()) { return; } @@ -36,7 +36,7 @@ public void onMessageReceived(@NotNull GuildMessageReceivedEvent event) { addMessageRecord(event); } - private void addMessageRecord(@NotNull GuildMessageReceivedEvent event) { + private void addMessageRecord(@NotNull MessageReceivedEvent event) { database.write(context -> context.newRecord(HELP_CHANNEL_MESSAGES) .setMessageId(event.getMessage().getIdLong()) .setGuildId(event.getGuild().getIdLong()) diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/utils/MessageUtils.java b/application/src/main/java/org/togetherjava/tjbot/commands/utils/MessageUtils.java index 70895df89a..45dd5dee92 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/utils/MessageUtils.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/utils/MessageUtils.java @@ -2,7 +2,7 @@ import net.dv8tion.jda.api.entities.Message; import net.dv8tion.jda.api.interactions.components.ActionRow; -import net.dv8tion.jda.api.interactions.components.Button; +import net.dv8tion.jda.api.interactions.components.buttons.Button; import net.dv8tion.jda.api.utils.MarkdownSanitizer; import org.jetbrains.annotations.NotNull; @@ -42,7 +42,7 @@ public static void disableButtons(@NotNull Message message) { * Escapes every markdown content in the given string. * * If the escaped message is sent to Discord, it will display the original message. - * + * * @param text the text to escape * @return the escaped text */ diff --git a/application/src/test/java/org/togetherjava/tjbot/commands/SlashCommandAdapterTest.java b/application/src/test/java/org/togetherjava/tjbot/commands/SlashCommandAdapterTest.java index 410796c88c..a0de6f0bea 100644 --- a/application/src/test/java/org/togetherjava/tjbot/commands/SlashCommandAdapterTest.java +++ b/application/src/test/java/org/togetherjava/tjbot/commands/SlashCommandAdapterTest.java @@ -1,7 +1,7 @@ package org.togetherjava.tjbot.commands; -import net.dv8tion.jda.api.events.interaction.SlashCommandEvent; -import net.dv8tion.jda.api.interactions.commands.build.CommandData; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; +import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData; import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Test; import org.togetherjava.tjbot.commands.componentids.Lifespan; @@ -12,14 +12,14 @@ final class SlashCommandAdapterTest { private static final String NAME = "foo"; private static final String DESCRIPTION = "Foo command"; - private static final SlashCommandVisibility VISIBILITY = SlashCommandVisibility.GUILD; + private static final CommandVisibility VISIBILITY = CommandVisibility.GUILD; private static final int UNIQUE_ID_ITERATIONS = 20; static SlashCommandAdapter createAdapter() { // noinspection AnonymousInnerClass return new SlashCommandAdapter(NAME, DESCRIPTION, VISIBILITY) { @Override - public void onSlashCommand(@NotNull SlashCommandEvent event) { + public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) { // No implementation needed for the test } }; @@ -43,7 +43,7 @@ void getVisibility() { @Test void getData() { SlashCommandAdapter adapter = createAdapter(); - CommandData data = adapter.getData(); + SlashCommandData data = adapter.getData(); assertEquals(NAME, data.getName(), "adapters name is inconsistent with the base data object"); assertEquals(DESCRIPTION, data.getDescription(), @@ -53,7 +53,7 @@ void getData() { String otherName = NAME + "-bar"; String otherDescription = DESCRIPTION + "-bar"; data.setName(otherName).setDescription(otherDescription); - CommandData otherData = adapter.getData(); + SlashCommandData otherData = adapter.getData(); assertSame(data, otherData, "adapter changed the data object"); assertEquals(otherName, otherData.getName(), "name changes did not carry over"); diff --git a/application/src/test/java/org/togetherjava/tjbot/commands/basic/PingCommandTest.java b/application/src/test/java/org/togetherjava/tjbot/commands/basic/PingCommandTest.java index 457b7e3bec..9e8b71505f 100644 --- a/application/src/test/java/org/togetherjava/tjbot/commands/basic/PingCommandTest.java +++ b/application/src/test/java/org/togetherjava/tjbot/commands/basic/PingCommandTest.java @@ -1,6 +1,6 @@ package org.togetherjava.tjbot.commands.basic; -import net.dv8tion.jda.api.events.interaction.SlashCommandEvent; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import org.junit.jupiter.api.Test; import org.togetherjava.tjbot.commands.SlashCommand; import org.togetherjava.tjbot.jda.JdaTester; @@ -14,7 +14,7 @@ void pingCommand() { SlashCommand command = new PingCommand(); JdaTester jdaTester = new JdaTester(); - SlashCommandEvent event = jdaTester.createSlashCommandEvent(command).build(); + SlashCommandInteractionEvent event = jdaTester.createSlashCommandEvent(command).build(); command.onSlashCommand(event); verify(event, times(1)).reply("Pong!"); diff --git a/application/src/test/java/org/togetherjava/tjbot/commands/system/ReloadCommandTest.java b/application/src/test/java/org/togetherjava/tjbot/commands/system/ReloadCommandTest.java index 28ac0d29cc..1740500b55 100644 --- a/application/src/test/java/org/togetherjava/tjbot/commands/system/ReloadCommandTest.java +++ b/application/src/test/java/org/togetherjava/tjbot/commands/system/ReloadCommandTest.java @@ -2,7 +2,7 @@ import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Test; -import org.togetherjava.tjbot.commands.SlashCommand; +import org.togetherjava.tjbot.commands.BotCommand; import java.util.Collection; import java.util.List; @@ -15,14 +15,14 @@ final class ReloadCommandTest { void ReloadCommand() { @SuppressWarnings({"AnonymousInnerClassWithTooManyMethods", "AnonymousInnerClass", "AnonymousInnerClassMayBeStatic"}) - SlashCommandProvider slashCommandProvider = new SlashCommandProvider() { + BotCommandProvider slashCommandProvider = new BotCommandProvider() { @Override - public @NotNull Collection getSlashCommands() { + public @NotNull Collection getBotCommands() { return List.of(); } @Override - public @NotNull Optional getSlashCommand(@NotNull String name) { + public @NotNull Optional getBotCommand(@NotNull String name) { return Optional.empty(); } }; diff --git a/application/src/test/java/org/togetherjava/tjbot/jda/JdaTester.java b/application/src/test/java/org/togetherjava/tjbot/jda/JdaTester.java index 5c6ed68067..b387cbb039 100644 --- a/application/src/test/java/org/togetherjava/tjbot/jda/JdaTester.java +++ b/application/src/test/java/org/togetherjava/tjbot/jda/JdaTester.java @@ -2,7 +2,7 @@ import net.dv8tion.jda.api.Permission; import net.dv8tion.jda.api.entities.SelfUser; -import net.dv8tion.jda.api.events.interaction.SlashCommandEvent; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import net.dv8tion.jda.api.requests.restaction.MessageAction; import net.dv8tion.jda.api.utils.ConcurrentSessionController; import net.dv8tion.jda.api.utils.cache.CacheFlag; @@ -10,7 +10,7 @@ import net.dv8tion.jda.internal.entities.*; import net.dv8tion.jda.internal.requests.Requester; import net.dv8tion.jda.internal.requests.restaction.MessageActionImpl; -import net.dv8tion.jda.internal.requests.restaction.interactions.ReplyActionImpl; +import net.dv8tion.jda.internal.requests.restaction.interactions.ReplyCallbackActionImpl; import net.dv8tion.jda.internal.utils.config.AuthorizationConfig; import org.jetbrains.annotations.NotNull; import org.mockito.ArgumentMatchers; @@ -31,7 +31,7 @@ * be exploited for testing. *

* An example test using this class might look like: - * + * *

  * {
  *     @code
@@ -105,7 +105,7 @@ public JdaTester() {
 
     /**
      * Creates a Mockito mocked slash command event, which can be used for
-     * {@link SlashCommand#onSlashCommand(SlashCommandEvent)}.
+     * {@link SlashCommand#onSlashCommand(SlashCommandInteractionEvent)}.
      * 

* The method creates a builder that can be used to further adjust the event before creation, * e.g. provide options. @@ -113,11 +113,11 @@ public JdaTester() { * @param command the command to create an event for * @return a builder used to create a Mockito mocked slash command event */ - public @NotNull SlashCommandEventBuilder createSlashCommandEvent( + public @NotNull SlashCommandInteractionEventBuilder createSlashCommandEvent( @NotNull SlashCommand command) { - UnaryOperator mockOperator = event -> { - SlashCommandEvent slashCommandEvent = spy(event); - ReplyActionImpl replyAction = mock(ReplyActionImpl.class); + UnaryOperator mockOperator = event -> { + SlashCommandInteractionEvent slashCommandEvent = spy(event); + ReplyCallbackActionImpl replyAction = mock(ReplyCallbackActionImpl.class); doReturn(replyAction).when(slashCommandEvent).reply(anyString()); when(replyAction.setEphemeral(anyBoolean())).thenReturn(replyAction); @@ -126,7 +126,7 @@ public JdaTester() { return slashCommandEvent; }; - return new SlashCommandEventBuilder(jda, mockOperator).command(command) + return new SlashCommandInteractionEventBuilder(jda, mockOperator).command(command) .token(TEST_TOKEN) .channelId(String.valueOf(TEXT_CHANNEL_ID)) .applicationId(String.valueOf(APPLICATION_ID)) diff --git a/application/src/test/java/org/togetherjava/tjbot/jda/SlashCommandEventBuilder.java b/application/src/test/java/org/togetherjava/tjbot/jda/SlashCommandInteractionEventBuilder.java similarity index 82% rename from application/src/test/java/org/togetherjava/tjbot/jda/SlashCommandEventBuilder.java rename to application/src/test/java/org/togetherjava/tjbot/jda/SlashCommandInteractionEventBuilder.java index 6cca185720..16af3d16bf 100644 --- a/application/src/test/java/org/togetherjava/tjbot/jda/SlashCommandEventBuilder.java +++ b/application/src/test/java/org/togetherjava/tjbot/jda/SlashCommandInteractionEventBuilder.java @@ -2,13 +2,13 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import net.dv8tion.jda.api.events.interaction.SlashCommandEvent; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.interactions.commands.build.OptionData; import net.dv8tion.jda.api.interactions.commands.build.SubcommandData; import net.dv8tion.jda.api.utils.data.DataObject; import net.dv8tion.jda.internal.JDAImpl; -import net.dv8tion.jda.internal.interactions.CommandInteractionImpl; +import net.dv8tion.jda.internal.interactions.command.SlashCommandInteractionImpl; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.togetherjava.tjbot.commands.SlashCommand; @@ -21,7 +21,7 @@ /** * Builder to create slash command events that can be used for example with - * {@link SlashCommand#onSlashCommand(SlashCommandEvent)}. + * {@link SlashCommand#onSlashCommand(SlashCommandInteractionEvent)}. *

* Create instances of this class by using {@link JdaTester#createSlashCommandEvent(SlashCommand)}. *

@@ -31,20 +31,20 @@ * cleared using {@link #clearOptions()}. *

* Refer to the following examples: the command {@code ping} is build using - * + * *

  * {@code
  * // /ping
- * jdaTester.createSlashCommandEvent(command).build();
+ * jdaTester.createSlashCommandInteractionEvent(command).build();
  *
  * // /days start:10.01.2021 end:13.01.2021
- * jdaTester.createSlashCommandEvent(command)
+ * jdaTester.createSlashCommandInteractionEvent(command)
  *   .option("start", "10.01.2021")
  *   .option("end", "13.01.2021")
  *   .build();
  *
  * // /db put key:foo value:bar
- * jdaTester.createSlashCommandEvent(command)
+ * jdaTester.createSlashCommandInteractionEvent(command)
  *   .subcommand("put")
  *   .option("key", "foo")
  *   .option("value", "bar")
@@ -53,10 +53,10 @@
  * 
*/ @SuppressWarnings("ClassWithTooManyFields") -public final class SlashCommandEventBuilder { +public final class SlashCommandInteractionEventBuilder { private static final ObjectMapper JSON = new ObjectMapper(); private final JDAImpl jda; - private final UnaryOperator mockOperator; + private final UnaryOperator mockOperator; private String token; private String channelId; private String applicationId; @@ -66,7 +66,8 @@ public final class SlashCommandEventBuilder { private final Map nameToOption = new HashMap<>(); private String subcommand; - SlashCommandEventBuilder(@NotNull JDAImpl jda, UnaryOperator mockOperator) { + SlashCommandInteractionEventBuilder(@NotNull JDAImpl jda, + UnaryOperator mockOperator) { this.jda = jda; this.mockOperator = mockOperator; } @@ -85,7 +86,8 @@ public final class SlashCommandEventBuilder { * @throws IllegalArgumentException if the option does not exist in the corresponding command, * as specified by its {@link SlashCommand#getData()} */ - public @NotNull SlashCommandEventBuilder option(@NotNull String name, @NotNull String value) { + public @NotNull SlashCommandInteractionEventBuilder option(@NotNull String name, + @NotNull String value) { // TODO Also add overloads for other types requireOption(name, OptionType.STRING); nameToOption.put(name, new Option(name, value, OptionType.STRING)); @@ -97,7 +99,7 @@ public final class SlashCommandEventBuilder { * * @return this builder instance for chaining */ - public @NotNull SlashCommandEventBuilder clearOptions() { + public @NotNull SlashCommandInteractionEventBuilder clearOptions() { nameToOption.clear(); return this; } @@ -113,7 +115,7 @@ public final class SlashCommandEventBuilder { * @throws IllegalArgumentException if the subcommand does not exist in the corresponding * command, as specified by its {@link SlashCommand#getData()} */ - public @NotNull SlashCommandEventBuilder subcommand(@Nullable String subcommand) { + public @NotNull SlashCommandInteractionEventBuilder subcommand(@Nullable String subcommand) { if (subcommand != null) { requireSubcommand(subcommand); } @@ -123,37 +125,37 @@ public final class SlashCommandEventBuilder { } @NotNull - SlashCommandEventBuilder command(@NotNull SlashCommand command) { + SlashCommandInteractionEventBuilder command(@NotNull SlashCommand command) { this.command = command; return this; } @NotNull - SlashCommandEventBuilder channelId(@NotNull String channelId) { + SlashCommandInteractionEventBuilder channelId(@NotNull String channelId) { this.channelId = channelId; return this; } @NotNull - SlashCommandEventBuilder token(@NotNull String token) { + SlashCommandInteractionEventBuilder token(@NotNull String token) { this.token = token; return this; } @NotNull - SlashCommandEventBuilder applicationId(@NotNull String applicationId) { + SlashCommandInteractionEventBuilder applicationId(@NotNull String applicationId) { this.applicationId = applicationId; return this; } @NotNull - SlashCommandEventBuilder guildId(@NotNull String guildId) { + SlashCommandInteractionEventBuilder guildId(@NotNull String guildId) { this.guildId = guildId; return this; } @NotNull - SlashCommandEventBuilder userId(@NotNull String userId) { + SlashCommandInteractionEventBuilder userId(@NotNull String userId) { this.userId = userId; return this; } @@ -164,7 +166,7 @@ SlashCommandEventBuilder userId(@NotNull String userId) { * * @return the created slash command instance */ - public @NotNull SlashCommandEvent build() { + public @NotNull SlashCommandInteractionEvent build() { PayloadSlashCommand event = createEvent(); String json; @@ -174,8 +176,8 @@ SlashCommandEventBuilder userId(@NotNull String userId) { throw new IllegalStateException(e); } - return mockOperator.apply(new SlashCommandEvent(jda, 0, - new CommandInteractionImpl(jda, DataObject.fromJson(json)))); + return mockOperator.apply(new SlashCommandInteractionEvent(jda, 1, + new SlashCommandInteractionImpl(jda, DataObject.fromJson(json)))); } private @NotNull PayloadSlashCommand createEvent() {