diff --git a/src/main/java/com/alttd/buttonManager/buttons/suggestionReview/ButtonSuggestionReviewAccept.java b/src/main/java/com/alttd/buttonManager/buttons/suggestionReview/ButtonSuggestionReviewAccept.java index 4231747..f048635 100644 --- a/src/main/java/com/alttd/buttonManager/buttons/suggestionReview/ButtonSuggestionReviewAccept.java +++ b/src/main/java/com/alttd/buttonManager/buttons/suggestionReview/ButtonSuggestionReviewAccept.java @@ -107,7 +107,7 @@ public class ButtonSuggestionReviewAccept extends DiscordButton { } public void sendSuggestionInForum(ForumChannel forumChannel, TextChannel modLog, MessageEmbed.Field field, MessageEmbed suggestionMessage, ButtonInteractionEvent event) { - MessageCreateData messageCreateData = new MessageCreateBuilder().addContent(field.getValue()).build(); + MessageCreateData messageCreateData = new MessageCreateBuilder().addContent(field.getValue() + "\u200B").build(); forumChannel.createForumPost(field.getName(), messageCreateData).queue(success -> { event.getMessage().delete().queue(RestAction.getDefaultSuccess(), Util::handleFailure); diff --git a/src/main/java/com/alttd/commandManager/CommandManager.java b/src/main/java/com/alttd/commandManager/CommandManager.java index b685d21..c186f27 100644 --- a/src/main/java/com/alttd/commandManager/CommandManager.java +++ b/src/main/java/com/alttd/commandManager/CommandManager.java @@ -3,6 +3,7 @@ package com.alttd.commandManager; import com.alttd.commandManager.commands.AddCommand.CommandManage; import com.alttd.commandManager.commands.*; import com.alttd.commandManager.commands.PollCommand.CommandPoll; +import com.alttd.contextMenuManager.ContextMenuManager; import com.alttd.database.Database; import com.alttd.modalManager.ModalManager; import com.alttd.util.Logger; @@ -29,12 +30,12 @@ public class CommandManager extends ListenerAdapter { private final List commands; private final HashMap> commandList = new HashMap<>(); - public CommandManager(JDA jda, ModalManager modalManager) { + public CommandManager(JDA jda, ModalManager modalManager, ContextMenuManager contextMenuManager) { commandList.put("manage", new ArrayList<>(List.of(new ScopeInfo(CommandScope.GLOBAL, 0)))); loadCommands(); Logger.info("Loading commands..."); commands = List.of( - new CommandManage(jda, this), + new CommandManage(jda, this, contextMenuManager), new CommandHelp(jda, this), new CommandPoll(jda, this), new CommandSuggestion(jda, modalManager, this), diff --git a/src/main/java/com/alttd/commandManager/commands/AddCommand/CommandManage.java b/src/main/java/com/alttd/commandManager/commands/AddCommand/CommandManage.java index 546c85c..e08839b 100644 --- a/src/main/java/com/alttd/commandManager/commands/AddCommand/CommandManage.java +++ b/src/main/java/com/alttd/commandManager/commands/AddCommand/CommandManage.java @@ -4,6 +4,7 @@ import com.alttd.commandManager.CommandManager; import com.alttd.commandManager.DiscordCommand; import com.alttd.commandManager.SubCommand; import com.alttd.commandManager.SubOption; +import com.alttd.contextMenuManager.ContextMenuManager; import com.alttd.util.Logger; import com.alttd.util.Util; import net.dv8tion.jda.api.JDA; @@ -23,7 +24,7 @@ public class CommandManage extends DiscordCommand { private final HashMap subOptionsMap = new HashMap<>(); private final CommandData commandData; - public CommandManage(JDA jda, CommandManager commandManager) { + public CommandManage(JDA jda, CommandManager commandManager, ContextMenuManager contextMenuManager) { commandData = Commands.slash(getName(), "Enable commands and assign permissions") .addSubcommands( new SubcommandData("enable", "Enable a command in a channel") @@ -33,8 +34,8 @@ public class CommandManage extends DiscordCommand { ); commandData.setDefaultPermissions(DefaultMemberPermissions.enabledFor(Permission.ADMINISTRATOR)); Util.registerSubOptions(subOptionsMap, - new SubCommandEnable(commandManager, null, this), - new SubCommandEnable(commandManager, null, this) + new SubCommandEnable(commandManager, contextMenuManager, null, this), + new SubCommandDisable(commandManager, null, this) ); Util.registerCommand(commandManager, jda, commandData, getName()); } diff --git a/src/main/java/com/alttd/commandManager/commands/AddCommand/SubCommandEnable.java b/src/main/java/com/alttd/commandManager/commands/AddCommand/SubCommandEnable.java index aa7ab7d..b9c7705 100644 --- a/src/main/java/com/alttd/commandManager/commands/AddCommand/SubCommandEnable.java +++ b/src/main/java/com/alttd/commandManager/commands/AddCommand/SubCommandEnable.java @@ -1,20 +1,19 @@ package com.alttd.commandManager.commands.AddCommand; import com.alttd.commandManager.*; +import com.alttd.contextMenuManager.ContextMenuManager; +import com.alttd.contextMenuManager.DiscordContextMenu; import com.alttd.database.Database; import com.alttd.templates.Parser; import com.alttd.templates.Template; import com.alttd.util.Logger; import com.alttd.util.Util; -import com.google.protobuf.GeneratedMessageV3; -import com.mysql.cj.log.Log; import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent; import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import net.dv8tion.jda.api.interactions.commands.OptionMapping; import java.sql.PreparedStatement; -import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; @@ -23,10 +22,12 @@ import java.util.stream.Collectors; public class SubCommandEnable extends SubCommand { private final CommandManager commandManager; + private final ContextMenuManager contextMenuManager; - protected SubCommandEnable(CommandManager commandManager, SubCommandGroup parentGroup, DiscordCommand parent) { + protected SubCommandEnable(CommandManager commandManager, ContextMenuManager contextMenuManager, SubCommandGroup parentGroup, DiscordCommand parent) { super(parentGroup, parent); this.commandManager = commandManager; + this.contextMenuManager = contextMenuManager; } @Override @@ -50,11 +51,22 @@ public class SubCommandEnable extends SubCommand { String commandName = option.getAsString(); DiscordCommand command = commandManager.getCommand(commandName); - if (command == null) { - event.replyEmbeds(Util.genericErrorEmbed("Error", "Unable to find a command called [" + commandName + "].")).setEphemeral(true).queue(); + if (command != null) { + tryEnableCommand(command, guild, commandName, event); return; } + DiscordContextMenu contextMenu = contextMenuManager.getContext(commandName); + if (contextMenu != null) { + tryEnableContextMenu(contextMenu, guild, commandName, event); + //todo stuff + return; + } + + event.replyEmbeds(Util.genericErrorEmbed("Error", "Unable to find a command called [" + commandName + "].")).setEphemeral(true).queue(); + } + + private void tryEnableCommand(DiscordCommand command, Guild guild, String commandName, SlashCommandInteractionEvent event) { if (enableCommand(command, guild.getIdLong())) { Util.registerCommand(guild, command.getCommandData(), command.getName()); event.replyEmbeds(Util.genericSuccessEmbed("Enabled command", @@ -71,6 +83,23 @@ public class SubCommandEnable extends SubCommand { } } + private void tryEnableContextMenu(DiscordContextMenu contextMenu, Guild guild, String commandName, SlashCommandInteractionEvent event) { + if (enableContextMenu(contextMenu, guild.getIdLong())) { + Util.registerCommand(guild, contextMenu.getUserContextInteraction(), contextMenu.getContextMenuId()); + event.replyEmbeds(Util.genericSuccessEmbed("Enabled command", + Parser.parse("Successfully enabled in !", + Template.of("command", commandName.toLowerCase()), + Template.of("guild", guild.getName()) + ))).setEphemeral(true).queue(); + } else { + event.replyEmbeds(Util.genericErrorEmbed("Failed to enable command", + Parser.parse("Unable to enable in , is it already enabled?", + Template.of("command", commandName.toLowerCase()), + Template.of("guild", guild.getName()) + ))).setEphemeral(true).queue(); + } + } + private boolean enableCommand(DiscordCommand command, long guildId) { if (!commandManager.enableCommand(command.getName(), new ScopeInfo(CommandScope.GUILD, guildId))) return false; @@ -100,6 +129,35 @@ public class SubCommandEnable extends SubCommand { return true; } + private boolean enableContextMenu(DiscordContextMenu contextMenu, long guildId) { + if (!commandManager.enableCommand(contextMenu.getContextMenuId(), new ScopeInfo(CommandScope.GUILD, guildId))) + return false; + String sql = "INSERT INTO commands (command_name, scope, location_id) VALUES(?, ?, ?)"; + PreparedStatement statement = null; + + try { + statement = Database.getDatabase().getConnection().prepareStatement(sql); + statement.setString(1, contextMenu.getContextMenuId()); + statement.setString(2, "GUILD"); + statement.setLong(3, guildId); + if (statement.executeUpdate() == 0) { + Logger.warning("Unable to enable command: % for guild: %", contextMenu.getContextMenuId(), String.valueOf(guildId)); + return false; + } + } catch (SQLException exception) { + Logger.sql(exception); + return false; + } finally { + try { + if (statement != null) + statement.close(); + } catch (SQLException exception) { + Logger.sql(exception); + } + } + return true; + } + @Override public void suggest(CommandAutoCompleteInteractionEvent event) { OptionMapping option = event.getOption("command"); @@ -110,13 +168,21 @@ public class SubCommandEnable extends SubCommand { } String commandName = option.getAsString().toLowerCase(); ScopeInfo scopeInfo = new ScopeInfo(CommandScope.GLOBAL, event.getGuild().getIdLong()); - event.replyChoiceStrings(commandManager.getCommands().stream() - .map(DiscordCommand::getName) - .filter(name -> name.toLowerCase().startsWith(commandName)) - .filter(name -> !commandManager.getActiveLocations(name).contains(scopeInfo)) - .limit(25) - .collect(Collectors.toList())) - .queue(); + List collect = commandManager.getCommands().stream() + .map(DiscordCommand::getName) + .filter(name -> name.toLowerCase().startsWith(commandName)) + .filter(name -> !commandManager.getActiveLocations(name).contains(scopeInfo)) + .limit(25) + .collect(Collectors.toList()); + + collect.addAll(contextMenuManager.getContexts().stream() + .map(DiscordContextMenu::getContextMenuId) + .filter(name -> name.toLowerCase().startsWith(commandName)) + .filter(name -> !commandManager.getActiveLocations(name).contains(scopeInfo)) + .limit(25) + .collect(Collectors.toList())); + + event.replyChoiceStrings(collect).queue(); } @Override diff --git a/src/main/java/com/alttd/contextMenuManager/ContextMenuManager.java b/src/main/java/com/alttd/contextMenuManager/ContextMenuManager.java new file mode 100644 index 0000000..94e2ab6 --- /dev/null +++ b/src/main/java/com/alttd/contextMenuManager/ContextMenuManager.java @@ -0,0 +1,76 @@ +package com.alttd.contextMenuManager; + +import com.alttd.contextMenuManager.contextMenus.ContextMenuRespondSuggestion; +import com.alttd.modalManager.ModalManager; +import com.alttd.util.Util; +import net.dv8tion.jda.api.EmbedBuilder; +import net.dv8tion.jda.api.events.interaction.command.MessageContextInteractionEvent; +import net.dv8tion.jda.api.events.interaction.command.UserContextInteractionEvent; +import net.dv8tion.jda.api.hooks.ListenerAdapter; +import net.dv8tion.jda.api.requests.RestAction; + +import javax.annotation.Nonnull; +import java.awt.*; +import java.util.List; +import java.util.Optional; + +public class ContextMenuManager extends ListenerAdapter { + + private final List contextMenus; + + public ContextMenuManager(ModalManager modalManager) { + contextMenus = List.of( + new ContextMenuRespondSuggestion(modalManager) + ); + } + + public DiscordContextMenu getContext(String name) { + for (DiscordContextMenu contextMenu : contextMenus) { + if (contextMenu.getContextMenuId().equalsIgnoreCase(name)) + return contextMenu; + } + return null; + } + + public List getContexts() { + return contextMenus; + } + + @Override + public void onUserContextInteraction(@Nonnull UserContextInteractionEvent event) { + String name = event.getInteraction().getName(); + Optional first = contextMenus.stream() + .filter(discordModal -> discordModal.getContextMenuId().equalsIgnoreCase(name)) + .findFirst(); + if (first.isEmpty()) { + event.replyEmbeds(new EmbedBuilder() + .setTitle("Invalid command") + .setDescription("Unable to process user context interaction with id: [" + name + "].") + .setColor(Color.RED) + .build()) + .setEphemeral(true) + .queue(RestAction.getDefaultSuccess(), Util::handleFailure); + return; + } + first.get().execute(event); + } + + @Override + public void onMessageContextInteraction(@Nonnull MessageContextInteractionEvent event) { + String name = event.getInteraction().getName(); + Optional first = contextMenus.stream() + .filter(discordModal -> discordModal.getContextMenuId().equalsIgnoreCase(name)) + .findFirst(); + if (first.isEmpty()) { + event.replyEmbeds(new EmbedBuilder() + .setTitle("Invalid command") + .setDescription("Unable to process user context interaction with id: [" + name + "].") + .setColor(Color.RED) + .build()) + .setEphemeral(true) + .queue(RestAction.getDefaultSuccess(), Util::handleFailure); + return; + } + first.get().execute(event); + } +} diff --git a/src/main/java/com/alttd/contextMenuManager/DiscordContextMenu.java b/src/main/java/com/alttd/contextMenuManager/DiscordContextMenu.java new file mode 100644 index 0000000..c01f5ce --- /dev/null +++ b/src/main/java/com/alttd/contextMenuManager/DiscordContextMenu.java @@ -0,0 +1,17 @@ +package com.alttd.contextMenuManager; + +import net.dv8tion.jda.api.events.interaction.command.MessageContextInteractionEvent; +import net.dv8tion.jda.api.events.interaction.command.UserContextInteractionEvent; +import net.dv8tion.jda.api.interactions.commands.build.CommandData; + +public abstract class DiscordContextMenu { + + public abstract String getContextMenuId(); + + public abstract void execute(UserContextInteractionEvent event); + + public abstract void execute(MessageContextInteractionEvent event); + + public abstract CommandData getUserContextInteraction(); + +} diff --git a/src/main/java/com/alttd/contextMenuManager/contextMenus/ContextMenuRespondSuggestion.java b/src/main/java/com/alttd/contextMenuManager/contextMenus/ContextMenuRespondSuggestion.java new file mode 100644 index 0000000..7e63c3e --- /dev/null +++ b/src/main/java/com/alttd/contextMenuManager/contextMenus/ContextMenuRespondSuggestion.java @@ -0,0 +1,90 @@ +package com.alttd.contextMenuManager.contextMenus; + +import com.alttd.contextMenuManager.DiscordContextMenu; +import com.alttd.database.queries.commandOutputChannels.CommandOutputChannels; +import com.alttd.database.queries.commandOutputChannels.OutputType; +import com.alttd.modalManager.ModalManager; +import com.alttd.modalManager.modals.ModalReplySuggestion; +import com.alttd.util.Util; +import net.dv8tion.jda.api.Permission; +import net.dv8tion.jda.api.entities.Message; +import net.dv8tion.jda.api.entities.channel.ChannelType; +import net.dv8tion.jda.api.entities.channel.concrete.ThreadChannel; +import net.dv8tion.jda.api.entities.channel.middleman.GuildChannel; +import net.dv8tion.jda.api.entities.channel.unions.IThreadContainerUnion; +import net.dv8tion.jda.api.entities.channel.unions.MessageChannelUnion; +import net.dv8tion.jda.api.events.interaction.command.MessageContextInteractionEvent; +import net.dv8tion.jda.api.events.interaction.command.UserContextInteractionEvent; +import net.dv8tion.jda.api.interactions.commands.DefaultMemberPermissions; +import net.dv8tion.jda.api.interactions.commands.build.CommandData; +import net.dv8tion.jda.api.interactions.commands.build.Commands; +import net.dv8tion.jda.api.interactions.components.Modal; +import net.dv8tion.jda.api.requests.RestAction; + +public class ContextMenuRespondSuggestion extends DiscordContextMenu { + + private final ModalManager modalManager; + + public ContextMenuRespondSuggestion(ModalManager modalManager) { + this.modalManager = modalManager; + } + + @Override + public String getContextMenuId() { + return "Respond To Suggestion"; + } + + @Override + public void execute(UserContextInteractionEvent event) { + event.getInteraction().replyEmbeds(Util.genericErrorEmbed("Error", "This interaction should have been a message interaction")) + .setEphemeral(true).queue(RestAction.getDefaultSuccess(), Util::handleFailure); + } + + @Override + public void execute(MessageContextInteractionEvent event) { + Message message = event.getInteraction().getTarget(); + if (!isSuggestion(message)) { + event.replyEmbeds(Util.genericErrorEmbed("Error", "This is not a suggestion")) + .setEphemeral(true).queue(RestAction.getDefaultSuccess(), Util::handleFailure); + return; + } + + Modal replySuggestion = modalManager.getModalFor("reply_suggestion"); + if (replySuggestion == null) { + event.replyEmbeds(Util.genericErrorEmbed("Error", "Unable to find reply suggestion modal")) + .setEphemeral(true).queue(RestAction.getDefaultSuccess(), Util::handleFailure); + return; + } + + ModalReplySuggestion.putMessage(event.getUser().getIdLong(), message); //TODO find a better way to do this + event.replyModal(replySuggestion).queue(RestAction.getDefaultSuccess(), Util::handleFailure); + } + + @Override + public CommandData getUserContextInteraction() { + return Commands.message(getContextMenuId()) + .setGuildOnly(true) + .setDefaultPermissions(DefaultMemberPermissions.enabledFor(Permission.ADMINISTRATOR)); + } + + public boolean isSuggestion(Message message) { + GuildChannel channel = CommandOutputChannels.getOutputChannel(message.getGuild(), OutputType.SUGGESTION); + if (channel == null) + return false; + MessageChannelUnion messageChannel = message.getChannel(); + if (channel.getType().equals(ChannelType.FORUM)) { + if (messageChannel.getType() != ChannelType.GUILD_PUBLIC_THREAD) { + return false; + } + ThreadChannel threadChannel = messageChannel.asThreadChannel(); + IThreadContainerUnion parentChannel = threadChannel.getParentChannel(); + if (!parentChannel.getType().equals(ChannelType.FORUM)) + return false; + + return message.getIdLong() == messageChannel.getIdLong() && message.getAuthor().equals(message.getJDA().getSelfUser()); + } else { + return channel.equals(messageChannel); + } + + } +} diff --git a/src/main/java/com/alttd/listeners/JDAListener.java b/src/main/java/com/alttd/listeners/JDAListener.java index ee7bb99..6efd4bb 100644 --- a/src/main/java/com/alttd/listeners/JDAListener.java +++ b/src/main/java/com/alttd/listeners/JDAListener.java @@ -2,6 +2,7 @@ package com.alttd.listeners; import com.alttd.buttonManager.ButtonManager; import com.alttd.commandManager.CommandManager; +import com.alttd.contextMenuManager.ContextMenuManager; import com.alttd.modalManager.ModalManager; import com.alttd.request.RequestManager; import com.alttd.util.Logger; @@ -26,8 +27,9 @@ public class JDAListener extends ListenerAdapter { Logger.info("JDA ready to register commands."); ButtonManager buttonManager = new ButtonManager(); ModalManager modalManager = new ModalManager(buttonManager); - CommandManager commandManager = new CommandManager(jda, modalManager); - jda.addEventListener(buttonManager, modalManager, commandManager); + ContextMenuManager contextMenuManager = new ContextMenuManager(modalManager); + CommandManager commandManager = new CommandManager(jda, modalManager, contextMenuManager); + jda.addEventListener(buttonManager, modalManager, commandManager, contextMenuManager); // RequestManager.init(); } diff --git a/src/main/java/com/alttd/modalManager/ModalManager.java b/src/main/java/com/alttd/modalManager/ModalManager.java index 2cb30d1..1a3a608 100644 --- a/src/main/java/com/alttd/modalManager/ModalManager.java +++ b/src/main/java/com/alttd/modalManager/ModalManager.java @@ -2,6 +2,7 @@ package com.alttd.modalManager; import com.alttd.buttonManager.ButtonManager; import com.alttd.modalManager.modals.ModalEvidence; +import com.alttd.modalManager.modals.ModalReplySuggestion; import com.alttd.modalManager.modals.ModalSuggestion; import com.alttd.util.Util; import net.dv8tion.jda.api.EmbedBuilder; @@ -23,7 +24,8 @@ public class ModalManager extends ListenerAdapter { public ModalManager(ButtonManager buttonManager) { modals = List.of( new ModalSuggestion(buttonManager), - new ModalEvidence()); + new ModalEvidence(), + new ModalReplySuggestion()); } @Override diff --git a/src/main/java/com/alttd/modalManager/modals/ModalReplySuggestion.java b/src/main/java/com/alttd/modalManager/modals/ModalReplySuggestion.java new file mode 100644 index 0000000..5d76ea9 --- /dev/null +++ b/src/main/java/com/alttd/modalManager/modals/ModalReplySuggestion.java @@ -0,0 +1,90 @@ +package com.alttd.modalManager.modals; + +import com.alttd.modalManager.DiscordModal; +import com.alttd.util.Util; +import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.entities.Message; +import net.dv8tion.jda.api.events.interaction.ModalInteractionEvent; +import net.dv8tion.jda.api.interactions.components.ActionRow; +import net.dv8tion.jda.api.interactions.components.Modal; +import net.dv8tion.jda.api.interactions.components.text.TextInput; +import net.dv8tion.jda.api.interactions.components.text.TextInputStyle; +import net.dv8tion.jda.api.interactions.modals.ModalMapping; +import net.dv8tion.jda.api.requests.RestAction; + +import java.util.HashMap; + +public class ModalReplySuggestion extends DiscordModal { + + private static final HashMap userToMessageMap = new HashMap<>(); + + public static synchronized void putMessage(long userId, Message message) { + userToMessageMap.put(userId, message); + } + + private static synchronized Message pullMessage(long userId) { + return userToMessageMap.remove(userId); + } + + @Override + public String getModalId() { + return "reply_suggestion"; + } + + @Override + public void execute(ModalInteractionEvent event) { + ModalMapping modalMapping = event.getInteraction().getValue("response"); + if (modalMapping == null) { + event.replyEmbeds(Util.genericErrorEmbed("Error", "Unable to find response in modal")) + .setEphemeral(true).queue(RestAction.getDefaultSuccess(), Util::handleFailure); + return; + } + + String response = modalMapping.getAsString(); + if (response.isEmpty()) { + event.replyEmbeds(Util.genericErrorEmbed("Error", "Response in modal is empty")) + .setEphemeral(true).queue(RestAction.getDefaultSuccess(), Util::handleFailure); + return; + } + + Member member = event.getMember(); + if (member == null) { + event.replyEmbeds(Util.genericErrorEmbed("Error", "This modal only works from within a guild")) + .setEphemeral(true).queue(RestAction.getDefaultSuccess(), Util::handleFailure); + return; + } + + Message message = pullMessage(member.getIdLong()); + if (message == null) { + event.replyEmbeds(Util.genericErrorEmbed("Error", "Unable to find a message for this modal")) + .setEphemeral(true).queue(RestAction.getDefaultSuccess(), Util::handleFailure); + return; + } + + String[] split = message.getContentRaw().split("\u200B"); + if (split.length == 0) { + event.replyEmbeds(Util.genericErrorEmbed("Error", "The suggestion to be edited has no content")) + .setEphemeral(true).queue(RestAction.getDefaultSuccess(), Util::handleFailure); + return; + } + + event.editMessage(split[0] + "\u200B\n\n" + "Response by: " + member.getAsMention() + response) + .queue(success -> event.replyEmbeds(Util.genericSuccessEmbed("Success", "Responded to the suggestion!")) + .setEphemeral(true).queue(RestAction.getDefaultSuccess(), Util::handleFailure), + failure -> event.replyEmbeds(Util.genericErrorEmbed("Error", "Unable to edit the suggestion")) + .setEphemeral(true).queue(RestAction.getDefaultSuccess(), Util::handleFailure)); + } + + @Override + public Modal getModal() { + TextInput body = TextInput.create("response", "Response", TextInputStyle.PARAGRAPH) + .setPlaceholder("Response...") + .setRequiredRange(10, 1024) + .setRequired(true) + .build(); + + return Modal.create(getModalId(), "Suggestion Response") + .addActionRows(ActionRow.of(body)) + .build(); + } +}