From 471544eb55857276b4ccd1e3286ccdd3e7f82151 Mon Sep 17 00:00:00 2001 From: JJeeff248 <52386683+jjeeff248@users.noreply.github.com> Date: Mon, 24 Jan 2022 01:14:13 +0100 Subject: [PATCH 1/9] Most of the work done by JJeeff, afterwards some refactoring by Tijs --- .../togetherjava/tjbot/commands/Commands.java | 0 .../commands/basic/RoleSelectCommand.java | 282 ++++++++++++++++++ 2 files changed, 282 insertions(+) create mode 100644 application/src/main/java/org/togetherjava/tjbot/commands/Commands.java create mode 100644 application/src/main/java/org/togetherjava/tjbot/commands/basic/RoleSelectCommand.java diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/Commands.java b/application/src/main/java/org/togetherjava/tjbot/commands/Commands.java new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..df591a5e19 --- /dev/null +++ b/application/src/main/java/org/togetherjava/tjbot/commands/basic/RoleSelectCommand.java @@ -0,0 +1,282 @@ +package org.togetherjava.tjbot.commands.basic; + +import net.dv8tion.jda.api.EmbedBuilder; +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.entities.MessageEmbed; +import net.dv8tion.jda.api.entities.Role; +import net.dv8tion.jda.api.events.interaction.SelectionMenuEvent; +import net.dv8tion.jda.api.events.interaction.SlashCommandEvent; +import net.dv8tion.jda.api.interactions.Interaction; +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.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.SlashCommandAdapter; +import org.togetherjava.tjbot.commands.SlashCommandVisibility; + +import java.awt.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Objects; + + +/** + * Implements the {@code roleSelect} command. + * + *

+ * Allows users to select their roles without using reactions, instead it uses selection menus where + * you can select multiple roles.
+ * Note: the bot can only use roles below its highest one + */ +public class RoleSelectCommand extends SlashCommandAdapter { + + private static final Logger logger = LoggerFactory.getLogger(RoleSelectCommand.class); + + private static final String ALL_OPTION = "all"; + private static final String CHOOSE_OPTION = "choose"; + + private static final String TITLE_OPTION = "title"; + private static final String DESCRIPTION_OPTION = "description"; + + private static final Color embedColor = new Color(24, 221, 136); + + private static final List messageOptions = List.of( + new OptionData(OptionType.STRING, TITLE_OPTION, "The title for the message", false), + new OptionData(OptionType.STRING, DESCRIPTION_OPTION, "A description for the message", + false)); + + + /** + * Construct an instance + * + * @see RoleSelectCommand + */ + public RoleSelectCommand() { + super("role-select", "Sends a message where users can select their roles", + SlashCommandVisibility.GUILD); + + SubcommandData allRoles = + new SubcommandData(ALL_OPTION, "Lists all the rolls in the server for users") + .addOptions(messageOptions); + + SubcommandData selectRoles = + new SubcommandData(CHOOSE_OPTION, "Choose the roles for users to select") + .addOptions(messageOptions); + + getData().addSubcommands(allRoles, selectRoles); + } + + @Override + public void onSlashCommand(@NotNull final SlashCommandEvent 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") + .setEphemeral(true) + .queue(); + return; + } + + Member selfMember = Objects.requireNonNull(event.getGuild()).getSelfMember(); + if (!selfMember.hasPermission(Permission.MANAGE_ROLES)) { + event.reply("The bot needs the manage role permissions").setEphemeral(true).queue(); + logger.warn("The bot needs the manage role permissions"); + return; + } + + SelectionMenu.Builder menu = SelectionMenu.create(generateComponentId(member.getId())); + boolean ephemeral = false; + + if (Objects.equals(event.getSubcommandName(), CHOOSE_OPTION)) { + addMenuOptions(event, menu, "Select the roles to display", 1); + ephemeral = true; + } else { + addMenuOptions(event, menu, "Select your roles", 0); + } + + // Handle Optional arguments + OptionMapping titleOption = event.getOption(TITLE_OPTION); + OptionMapping descriptionOption = event.getOption(DESCRIPTION_OPTION); + + String title = handleOption(titleOption); + String description = handleOption(descriptionOption); + + if (ephemeral) { + event.replyEmbeds(makeEmbed(title, description)) + .addActionRow(menu.build()) + .setEphemeral(true) + .queue(); + } else { + event.getChannel() + .sendMessageEmbeds(makeEmbed(title, description)) + .setActionRow(menu.build()) + .queue(); + + event.reply("Message sent successfully!").setEphemeral(true).queue(); + } + } + + /** + * Adds role options to a selection menu + *

+ * + * @param event the {@link SlashCommandEvent} + * @param menu the menu to add options to {@link SelectionMenu.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, + @Nullable final Integer minValues) { + + + Guild guild = Objects.requireNonNull(event.getGuild(), "The given guild cannot be null"); + + Role highestBotRole = guild.getSelfMember().getRoles().get(0); + List guildRoles = guild.getRoles(); + + Collection roles = new ArrayList<>( + guildRoles.subList(guildRoles.indexOf(highestBotRole) + 1, guildRoles.size())); + + if (null != minValues) { + menu.setMinValues(minValues); + } + + menu.setPlaceholder(placeHolder).setMaxValues(roles.size()); + + + menu.addOptions(roles.stream() + .filter(Role::isPublicRole) + .filter(role -> !role.getTags().isBot()) + .map(role -> SelectOption.of(role.getName(), role.getId())) + .toList()); + } + + /** + * Creates an embedded message to send with the selection menu + * + *

+ *

+ * + * @param title for the embedded message. nullable {@link String} + * @param description for the embedded message. nullable {@link String} + * @return the formatted embed {@link MessageEmbed} + */ + private static @NotNull MessageEmbed makeEmbed(@Nullable final String title, + @Nullable final CharSequence description) { + + String effectiveTitle = title; + + if (null == effectiveTitle) { + effectiveTitle = "Select your roles:"; + } + + return new EmbedBuilder().setTitle(effectiveTitle) + .setDescription(description) + .setColor(embedColor) + .build(); + } + + @Override + public void onSelectionMenu(@NotNull final SelectionMenuEvent event, + @NotNull final List args) { + Member member = Objects.requireNonNull(event.getMember(), "Member is null"); + Guild guild = + Objects.requireNonNull(event.getGuild(), "The given Guild guild cannot be null"); + List selectedOptions = Objects.requireNonNull(event.getSelectedOptions(), + "The given selectedOptions cannot be null"); + + List selectedRoles = selectedOptions.stream() + .map(selectOption -> guild.getRoleById(selectOption.getValue())) + .filter(Objects::nonNull) + .filter(role -> guild.getSelfMember().canInteract(role)) + .toList(); + + // TODO kinda weird to check it like that? + // True if the event option was 'choose' + if (event.getMessage().isEphemeral()) { + + SelectionMenu.Builder menu = SelectionMenu.create(generateComponentId(member.getId())) + .setPlaceholder("Select your roles") + .setMaxValues(selectedRoles.size()) + .setMinValues(0); + + selectedRoles.forEach(role -> menu.addOption(role.getName(), role.getId())); + + event.getChannel() + .sendMessageEmbeds(event.getMessage().getEmbeds().get(0)) + .setActionRow(menu.build()) + .queue(); + + event.reply("Message sent successfully!").setEphemeral(true).queue(); + return; + } + + List menuOptions = + Objects.requireNonNull(event.getInteraction().getComponent()).getOptions(); + + + // TODO weird naming lol + Collection additionRoles = new ArrayList<>(selectedRoles.size()); + Collection removalRoles = new ArrayList<>(selectedRoles.size()); + + menuOptions.stream().map(selectedOption -> { + // TODO handle in a different way? Because we need the SelectOption it has to be handled + // really odd in a Stream + Role role = guild.getRoleById(selectedOption.getValue()); + + if (null == role) { + logger.info( + "The {} ({}) role has been removed but is still an option in the selection menu", + selectedOption.getLabel(), selectedOption.getValue()); + return null; + } + + return role; + }).filter(Objects::nonNull).forEach(role -> { + if (selectedRoles.contains(role)) { + additionRoles.add(role); + } else { + removalRoles.add(role); + } + }); + + guild.modifyMemberRoles(member, additionRoles, removalRoles) + .flatMap(empty -> event.reply("Updated your roles!").setEphemeral(true)) + .queue(); + + } + + /** + * This gets the OptionMapping and returns the value as a string if there is one + * + *

+ *

+ * + * @param option the {@link OptionMapping} + * @return the value. nullable {@link String} + */ + @Contract("null -> null") + private static @Nullable String handleOption(@Nullable final OptionMapping option) { + if (null == option) { + return null; + } + + if (OptionType.STRING == option.getType()) { + return option.getAsString(); + } else if (OptionType.BOOLEAN == option.getType()) { + return option.getAsBoolean() ? "true" : "false"; + } else { + return null; + } + } +} \ No newline at end of file From 36504b1fc939af8b2b562a27ca9bcff060d497a8 Mon Sep 17 00:00:00 2001 From: Tijs Date: Mon, 24 Jan 2022 12:17:00 +0100 Subject: [PATCH 2/9] Separated logic into methods + documentation Added role icon support for emojis --- .../commands/basic/RoleSelectCommand.java | 168 +++++++++++------- 1 file changed, 105 insertions(+), 63 deletions(-) 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 df591a5e19..d865ed3ae8 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 @@ -1,11 +1,9 @@ package org.togetherjava.tjbot.commands.basic; import net.dv8tion.jda.api.EmbedBuilder; +import net.dv8tion.jda.api.MessageBuilder; 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.entities.MessageEmbed; -import net.dv8tion.jda.api.entities.Role; +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.interactions.Interaction; @@ -13,9 +11,12 @@ 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.SelectOption; import net.dv8tion.jda.api.interactions.components.selections.SelectionMenu; import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; @@ -48,7 +49,7 @@ public class RoleSelectCommand extends SlashCommandAdapter { private static final String TITLE_OPTION = "title"; private static final String DESCRIPTION_OPTION = "description"; - private static final Color embedColor = new Color(24, 221, 136); + private static final Color AMBIENT_COLOR = new Color(24, 221, 136, 255); private static final List messageOptions = List.of( new OptionData(OptionType.STRING, TITLE_OPTION, "The title for the message", false), @@ -58,8 +59,6 @@ public class RoleSelectCommand extends SlashCommandAdapter { /** * Construct an instance - * - * @see RoleSelectCommand */ public RoleSelectCommand() { super("role-select", "Sends a message where users can select their roles", @@ -110,15 +109,16 @@ public void onSlashCommand(@NotNull final SlashCommandEvent event) { String title = handleOption(titleOption); String description = handleOption(descriptionOption); + MessageBuilder messageBuilder = new MessageBuilder(makeEmbed(title, description)) + .setActionRows(ActionRow.of(menu.build())); + if (ephemeral) { - event.replyEmbeds(makeEmbed(title, description)) - .addActionRow(menu.build()) + event.reply(messageBuilder.build()) .setEphemeral(true) .queue(); } else { event.getChannel() - .sendMessageEmbeds(makeEmbed(title, description)) - .setActionRow(menu.build()) + .sendMessage(messageBuilder.build()) .queue(); event.reply("Message sent successfully!").setEphemeral(true).queue(); @@ -151,14 +151,22 @@ private static void addMenuOptions(@NotNull final Interaction event, menu.setMinValues(minValues); } - menu.setPlaceholder(placeHolder).setMaxValues(roles.size()); - - - menu.addOptions(roles.stream() - .filter(Role::isPublicRole) - .filter(role -> !role.getTags().isBot()) - .map(role -> SelectOption.of(role.getName(), role.getId())) - .toList()); + menu.setPlaceholder(placeHolder) + .setMaxValues(roles.size()) + .addOptions(roles.stream() + .filter(role -> !role.isPublicRole()) + .filter(role -> !role.getTags().isBot()) + .map(role -> { + RoleIcon roleIcon = role.getIcon(); + + if (null == roleIcon || !roleIcon.isEmoji()) { + return SelectOption.of(role.getName(), role.getId()); + } else { + return SelectOption.of(role.getName(), role.getId()) + .withEmoji((Emoji.fromUnicode(roleIcon.getEmoji()))); + } + }) + .toList()); } /** @@ -174,86 +182,120 @@ private static void addMenuOptions(@NotNull final Interaction event, private static @NotNull MessageEmbed makeEmbed(@Nullable final String title, @Nullable final CharSequence description) { - String effectiveTitle = title; - - if (null == effectiveTitle) { - effectiveTitle = "Select your roles:"; - } + String effectiveTitle = (null == title) ? "Select your roles:" : title; return new EmbedBuilder().setTitle(effectiveTitle) .setDescription(description) - .setColor(embedColor) + .setColor(AMBIENT_COLOR) .build(); } @Override public void onSelectionMenu(@NotNull final SelectionMenuEvent event, @NotNull final List args) { - Member member = Objects.requireNonNull(event.getMember(), "Member is null"); - Guild guild = - Objects.requireNonNull(event.getGuild(), "The given Guild guild cannot be null"); + + Guild guild = Objects.requireNonNull(event.getGuild(), "The given guild cannot be null"); List selectedOptions = Objects.requireNonNull(event.getSelectedOptions(), "The given selectedOptions cannot be null"); + List selectedRoles = selectedOptions.stream() - .map(selectOption -> guild.getRoleById(selectOption.getValue())) + .map(SelectOption::getValue) + .map(guild::getRoleById) .filter(Objects::nonNull) .filter(role -> guild.getSelfMember().canInteract(role)) .toList(); - // TODO kinda weird to check it like that? - // True if the event option was 'choose' - if (event.getMessage().isEphemeral()) { - - SelectionMenu.Builder menu = SelectionMenu.create(generateComponentId(member.getId())) - .setPlaceholder("Select your roles") - .setMaxValues(selectedRoles.size()) - .setMinValues(0); - - selectedRoles.forEach(role -> menu.addOption(role.getName(), role.getId())); - - event.getChannel() - .sendMessageEmbeds(event.getMessage().getEmbeds().get(0)) - .setActionRow(menu.build()) - .queue(); - event.reply("Message sent successfully!").setEphemeral(true).queue(); - return; + if (event.getMessage().isEphemeral()) { + handleNewRoleBuilderSelection(event, selectedRoles); + } else { + handleRoleSelection(event, selectedRoles, guild); } + } - List menuOptions = - Objects.requireNonNull(event.getInteraction().getComponent()).getOptions(); - - - // TODO weird naming lol - Collection additionRoles = new ArrayList<>(selectedRoles.size()); - Collection removalRoles = new ArrayList<>(selectedRoles.size()); + /** + * Handles selection of a {@link SelectionMenuEvent} + * + * @param event the unacknowledged {@link SelectionMenuEvent} + * @param selectedRoles the {@link Role roles} selected + * @param guild the {@link Guild} + */ + private static void handleRoleSelection(final @NotNull SelectionMenuEvent event, + final @NotNull Collection selectedRoles, final Guild guild) { + Collection rolesToAdd = new ArrayList<>(selectedRoles.size()); + Collection rolesToRemove = new ArrayList<>(selectedRoles.size()); - menuOptions.stream().map(selectedOption -> { - // TODO handle in a different way? Because we need the SelectOption it has to be handled - // really odd in a Stream + event.getInteraction().getComponent().getOptions().stream().map(selectedOption -> { Role role = guild.getRoleById(selectedOption.getValue()); if (null == role) { - logger.info( - "The {} ({}) role has been removed but is still an option in the selection menu", - selectedOption.getLabel(), selectedOption.getValue()); - return null; + handleNullRole(selectedOption); } return role; }).filter(Objects::nonNull).forEach(role -> { if (selectedRoles.contains(role)) { - additionRoles.add(role); + rolesToAdd.add(role); } else { - removalRoles.add(role); + rolesToRemove.add(role); } }); + handleRoleModifications(event, event.getMember(), guild, rolesToAdd, rolesToRemove); + } + + /** + * Handles the selection of the {@link SelectionMenu} 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 + * {@link ComponentInteraction} event + */ + 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); + + selectedRoles.forEach(role -> menu.addOption(role.getName(), role.getId())); + + event.getChannel() + .sendMessageEmbeds(event.getMessage().getEmbeds().get(0)) + .setActionRow(menu.build()) + .queue(); + + event.reply("Message sent successfully!").setEphemeral(true).queue(); + } + + /** + * Logs that the role of the given {@link SelectOption} doesn't exist anymore. + * + * @param selectedOption the {@link SelectOption} + */ + private static void handleNullRole(final @NotNull SelectOption selectedOption) { + logger.info( + "The {} ({}) role has been removed but is still an option in the selection menu", + selectedOption.getLabel(), selectedOption.getValue()); + } + + /** + * Updates the roles of the given member + * + * @param event an unacknowledged {@link Interaction} event + * @param member the member to update the roles of + * @param guild what guild to update the roles in + * @param additionRoles the roles to add + * @param removalRoles the roles to remove + */ + private static void handleRoleModifications(@NotNull final Interaction event, + final Member member, final @NotNull Guild guild, final Collection additionRoles, + final Collection removalRoles) { guild.modifyMemberRoles(member, additionRoles, removalRoles) .flatMap(empty -> event.reply("Updated your roles!").setEphemeral(true)) .queue(); - } /** From 0345cca7d394cfff5d87026fe489bd3ce7f5b9ae Mon Sep 17 00:00:00 2001 From: Tijs Date: Mon, 24 Jan 2022 22:54:03 +0100 Subject: [PATCH 3/9] Spotless and optimized imports --- .../tjbot/commands/basic/RoleSelectCommand.java | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) 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 d865ed3ae8..711f4d0d5d 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 @@ -16,7 +16,6 @@ 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.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; @@ -113,13 +112,9 @@ public void onSlashCommand(@NotNull final SlashCommandEvent event) { .setActionRows(ActionRow.of(menu.build())); if (ephemeral) { - event.reply(messageBuilder.build()) - .setEphemeral(true) - .queue(); + event.reply(messageBuilder.build()).setEphemeral(true).queue(); } else { - event.getChannel() - .sendMessage(messageBuilder.build()) - .queue(); + event.getChannel().sendMessage(messageBuilder.build()).queue(); event.reply("Message sent successfully!").setEphemeral(true).queue(); } From 9805e048c4f2938aacf9da06ddc5b6dc2d2df57e Mon Sep 17 00:00:00 2001 From: Tijs Date: Mon, 24 Jan 2022 22:58:42 +0100 Subject: [PATCH 4/9] I'm crazy, I promise you I applied spotless --- .../togetherjava/tjbot/commands/basic/RoleSelectCommand.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 711f4d0d5d..0de31fbb4c 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 @@ -316,4 +316,4 @@ private static void handleRoleModifications(@NotNull final Interaction event, return null; } } -} \ No newline at end of file +} From ebcc3b9edfafc348e31240ab6eb3f6802326da2c Mon Sep 17 00:00:00 2001 From: Tais993 <49957334+Tais993@users.noreply.github.com> Date: Tue, 25 Jan 2022 02:41:33 -0800 Subject: [PATCH 5/9] Improved grammer / readability of sentences With help of @Heatmanofurioso --- .../togetherjava/tjbot/commands/basic/RoleSelectCommand.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 0de31fbb4c..1b5bd77fbd 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 @@ -36,7 +36,7 @@ *

* Allows users to select their roles without using reactions, instead it uses selection menus where * you can select multiple roles.
- * Note: the bot can only use roles below its highest one +* Note: the bot can only use roles with a position below its highest one */ public class RoleSelectCommand extends SlashCommandAdapter { @@ -289,7 +289,7 @@ private static void handleRoleModifications(@NotNull final Interaction event, final Member member, final @NotNull Guild guild, final Collection additionRoles, final Collection removalRoles) { guild.modifyMemberRoles(member, additionRoles, removalRoles) - .flatMap(empty -> event.reply("Updated your roles!").setEphemeral(true)) + .flatMap(empty -> event.reply("Your roles have been updated!").setEphemeral(true)) .queue(); } From 80a1f8aac89019347d39d223295b52ebb5cdda09 Mon Sep 17 00:00:00 2001 From: Tijs Date: Wed, 26 Jan 2022 12:23:03 +0100 Subject: [PATCH 6/9] Professional looking title DUDE I THOUGHT, ITS SUCH A TINY CHANCE, NO WAY ILL FUCK UP SPOTLESS. THEN I DO? ARE YOU SERIOUS? IATRHAWGRTUSEGTH Se UIRSE --- .../togetherjava/tjbot/commands/basic/RoleSelectCommand.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 1b5bd77fbd..3b0e23ee03 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 @@ -36,7 +36,7 @@ *

* Allows users to select their roles without using reactions, instead it uses selection menus where * you can select multiple roles.
-* Note: the bot can only use roles with a position below its highest one + * Note: the bot can only use roles with a position below its highest one */ public class RoleSelectCommand extends SlashCommandAdapter { From 0f1ce86f9126881305c036cf58ece3c20fd80ddc Mon Sep 17 00:00:00 2001 From: Tijs Date: Thu, 27 Jan 2022 13:26:11 +0100 Subject: [PATCH 7/9] Added RoleSelectCommand to Features --- .../main/java/org/togetherjava/tjbot/commands/Commands.java | 0 .../main/java/org/togetherjava/tjbot/commands/Features.java | 4 +++- 2 files changed, 3 insertions(+), 1 deletion(-) delete mode 100644 application/src/main/java/org/togetherjava/tjbot/commands/Commands.java diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/Commands.java b/application/src/main/java/org/togetherjava/tjbot/commands/Commands.java deleted file mode 100644 index e69de29bb2..0000000000 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 393172ed99..eea66b3444 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/Features.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/Features.java @@ -3,6 +3,7 @@ import net.dv8tion.jda.api.JDA; import org.jetbrains.annotations.NotNull; import org.togetherjava.tjbot.commands.basic.PingCommand; +import org.togetherjava.tjbot.commands.basic.RoleSelectCommand; import org.togetherjava.tjbot.commands.basic.VcActivityCommand; import org.togetherjava.tjbot.commands.free.FreeCommand; import org.togetherjava.tjbot.commands.mathcommands.TeXCommand; @@ -78,6 +79,7 @@ public enum Features { features.add(new AuditCommand(actionsStore)); features.add(new MuteCommand(actionsStore)); features.add(new UnmuteCommand(actionsStore)); + features.add(new RoleSelectCommand()); features.add(new TopHelpersCommand(database)); // Mixtures @@ -85,4 +87,4 @@ public enum Features { return features; } -} +} \ No newline at end of file From 958f9cc473829cd944741895aaf6716558fdbd19 Mon Sep 17 00:00:00 2001 From: Tijs Date: Thu, 27 Jan 2022 13:50:10 +0100 Subject: [PATCH 8/9] SplotlessApply - apply + cry = accurate --- .../src/main/java/org/togetherjava/tjbot/commands/Features.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 eea66b3444..94c8e74a90 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/Features.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/Features.java @@ -87,4 +87,4 @@ public enum Features { return features; } -} \ No newline at end of file +} From 456eb980481f9245390d8035c6f5cfad94e8f3c0 Mon Sep 17 00:00:00 2001 From: Tais993 Date: Mon, 7 Feb 2022 17:43:05 +0100 Subject: [PATCH 9/9] Applied review items --- .../commands/basic/RoleSelectCommand.java | 91 ++++++++++--------- 1 file changed, 50 insertions(+), 41 deletions(-) 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 3b0e23ee03..c7931e86ef 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 @@ -22,12 +22,14 @@ import org.slf4j.LoggerFactory; import org.togetherjava.tjbot.commands.SlashCommandAdapter; import org.togetherjava.tjbot.commands.SlashCommandVisibility; +import org.togetherjava.tjbot.commands.componentids.Lifespan; import java.awt.*; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Objects; +import java.util.function.Function; /** @@ -38,7 +40,7 @@ * you can select multiple roles.
* Note: the bot can only use roles with a position below its highest one */ -public class RoleSelectCommand extends SlashCommandAdapter { +public final class RoleSelectCommand extends SlashCommandAdapter { private static final Logger logger = LoggerFactory.getLogger(RoleSelectCommand.class); @@ -57,7 +59,7 @@ public class RoleSelectCommand extends SlashCommandAdapter { /** - * Construct an instance + * Construct an instance. */ public RoleSelectCommand() { super("role-select", "Sends a message where users can select their roles", @@ -74,6 +76,18 @@ public RoleSelectCommand() { getData().addSubcommands(allRoles, selectRoles); } + @NotNull + private static SelectOption mapToSelectOption(@NotNull Role role) { + RoleIcon roleIcon = role.getIcon(); + + if (null == roleIcon || !roleIcon.isEmoji()) { + return SelectOption.of(role.getName(), role.getId()); + } else { + return SelectOption.of(role.getName(), role.getId()) + .withEmoji((Emoji.fromUnicode(roleIcon.getEmoji()))); + } + } + @Override public void onSlashCommand(@NotNull final SlashCommandEvent event) { Member member = Objects.requireNonNull(event.getMember(), "Member is null"); @@ -87,16 +101,17 @@ public void onSlashCommand(@NotNull final SlashCommandEvent event) { Member selfMember = Objects.requireNonNull(event.getGuild()).getSelfMember(); if (!selfMember.hasPermission(Permission.MANAGE_ROLES)) { event.reply("The bot needs the manage role permissions").setEphemeral(true).queue(); - logger.warn("The bot needs the manage role permissions"); + logger.error("The bot needs the manage role permissions"); return; } - SelectionMenu.Builder menu = SelectionMenu.create(generateComponentId(member.getId())); - boolean ephemeral = false; + SelectionMenu.Builder menu = + SelectionMenu.create(generateComponentId(Lifespan.PERMANENT, member.getId())); + boolean isEphemeral = false; - if (Objects.equals(event.getSubcommandName(), CHOOSE_OPTION)) { + if (CHOOSE_OPTION.equals(event.getSubcommandName())) { addMenuOptions(event, menu, "Select the roles to display", 1); - ephemeral = true; + isEphemeral = true; } else { addMenuOptions(event, menu, "Select your roles", 0); } @@ -111,7 +126,7 @@ public void onSlashCommand(@NotNull final SlashCommandEvent event) { MessageBuilder messageBuilder = new MessageBuilder(makeEmbed(title, description)) .setActionRows(ActionRow.of(menu.build())); - if (ephemeral) { + if (isEphemeral) { event.reply(messageBuilder.build()).setEphemeral(true).queue(); } else { event.getChannel().sendMessage(messageBuilder.build()).queue(); @@ -121,7 +136,7 @@ public void onSlashCommand(@NotNull final SlashCommandEvent event) { } /** - * Adds role options to a selection menu + * Adds role options to a selection menu. *

* * @param event the {@link SlashCommandEvent} @@ -133,7 +148,6 @@ private static void addMenuOptions(@NotNull final Interaction event, @NotNull final SelectionMenu.Builder menu, @NotNull final String placeHolder, @Nullable final Integer minValues) { - Guild guild = Objects.requireNonNull(event.getGuild(), "The given guild cannot be null"); Role highestBotRole = guild.getSelfMember().getRoles().get(0); @@ -151,24 +165,12 @@ private static void addMenuOptions(@NotNull final Interaction event, .addOptions(roles.stream() .filter(role -> !role.isPublicRole()) .filter(role -> !role.getTags().isBot()) - .map(role -> { - RoleIcon roleIcon = role.getIcon(); - - if (null == roleIcon || !roleIcon.isEmoji()) { - return SelectOption.of(role.getName(), role.getId()); - } else { - return SelectOption.of(role.getName(), role.getId()) - .withEmoji((Emoji.fromUnicode(roleIcon.getEmoji()))); - } - }) + .map(RoleSelectCommand::mapToSelectOption) .toList()); } /** - * Creates an embedded message to send with the selection menu - * - *

- *

+ * Creates an embedded message to send with the selection menu. * * @param title for the embedded message. nullable {@link String} * @param description for the embedded message. nullable {@link String} @@ -193,7 +195,6 @@ public void onSelectionMenu(@NotNull final SelectionMenuEvent event, List selectedOptions = Objects.requireNonNull(event.getSelectedOptions(), "The given selectedOptions cannot be null"); - List selectedRoles = selectedOptions.stream() .map(SelectOption::getValue) .map(guild::getRoleById) @@ -210,7 +211,7 @@ public void onSelectionMenu(@NotNull final SelectionMenuEvent event, } /** - * Handles selection of a {@link SelectionMenuEvent} + * Handles selection of a {@link SelectionMenuEvent}. * * @param event the unacknowledged {@link SelectionMenuEvent} * @param selectedRoles the {@link Role roles} selected @@ -221,7 +222,26 @@ private static void handleRoleSelection(final @NotNull SelectionMenuEvent event, Collection rolesToAdd = new ArrayList<>(selectedRoles.size()); Collection rolesToRemove = new ArrayList<>(selectedRoles.size()); - event.getInteraction().getComponent().getOptions().stream().map(selectedOption -> { + event.getInteraction() + .getComponent() + .getOptions() + .stream() + .map(roleFromSelectOptionFunction(guild)) + .filter(Objects::nonNull) + .forEach(role -> { + if (selectedRoles.contains(role)) { + rolesToAdd.add(role); + } else { + rolesToRemove.add(role); + } + }); + + handleRoleModifications(event, event.getMember(), guild, rolesToAdd, rolesToRemove); + } + + @NotNull + private static Function roleFromSelectOptionFunction(Guild guild) { + return selectedOption -> { Role role = guild.getRoleById(selectedOption.getValue()); if (null == role) { @@ -229,15 +249,7 @@ private static void handleRoleSelection(final @NotNull SelectionMenuEvent event, } return role; - }).filter(Objects::nonNull).forEach(role -> { - if (selectedRoles.contains(role)) { - rolesToAdd.add(role); - } else { - rolesToRemove.add(role); - } - }); - - handleRoleModifications(event, event.getMember(), guild, rolesToAdd, rolesToRemove); + }; } /** @@ -277,7 +289,7 @@ private static void handleNullRole(final @NotNull SelectOption selectedOption) { } /** - * Updates the roles of the given member + * Updates the roles of the given member. * * @param event an unacknowledged {@link Interaction} event * @param member the member to update the roles of @@ -294,10 +306,7 @@ private static void handleRoleModifications(@NotNull final Interaction event, } /** - * This gets the OptionMapping and returns the value as a string if there is one - * - *

- *

+ * This gets the OptionMapping and returns the value as a string if there is one. * * @param option the {@link OptionMapping} * @return the value. nullable {@link String}