-
-
Notifications
You must be signed in to change notification settings - Fork 105
Migrated Role Reactions #284
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 17 commits
590fb6f
1c5384e
cdecca8
bb7e2eb
6f87f03
48b8988
307b62a
f7cac5b
31d8462
d017bc1
08ff0b0
11cc353
7fd1224
58e0c89
178c2c0
d212382
b76a15f
076d351
041e54c
bdd9799
214d079
4d16e2e
6d9fc10
71bc3b1
4d2f374
9e85bf8
afd08a8
fd58740
6894466
ebf7498
3ec7a99
e27451f
41ccb92
848eb43
fcff080
0f412a9
2b4c3ff
319d13f
f770046
f23c792
6ffd558
c9a7de9
f5c1e92
ac83a88
2a9dde9
0b48812
c81eca4
27c2d1d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,262 @@ | ||||||
| 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.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.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.List; | ||||||
| import java.util.Objects; | ||||||
|
|
||||||
|
|
||||||
| /** | ||||||
| * Implements the {@code roleSelect} command. | ||||||
| * | ||||||
| * <p> | ||||||
| * Allows users to select their roles without using reactions, instead it uses selection menus where | ||||||
| * you can select multiple roles. <br /> | ||||||
| * 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 List<OptionData> 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 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 | ||||||
| * <p> | ||||||
| * | ||||||
| * @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 SlashCommandEvent event, | ||||||
| @NotNull SelectionMenu.Builder menu, @NotNull String placeHolder, | ||||||
| @Nullable Integer minValues) { | ||||||
|
|
||||||
| Role highestBotRole = | ||||||
| Objects.requireNonNull(event.getGuild()).getSelfMember().getRoles().get(1); | ||||||
|
Comment on lines
+136
to
+137
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wouldn't the highest role be 0?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| List<Role> guildRoles = Objects.requireNonNull(event.getGuild()).getRoles(); | ||||||
|
|
||||||
| List<Role> roles = new ArrayList<>( | ||||||
| guildRoles.subList(guildRoles.indexOf(highestBotRole) + 1, guildRoles.size())); | ||||||
|
|
||||||
| if (minValues != null) { | ||||||
| menu.setMinValues(minValues); | ||||||
| } | ||||||
|
|
||||||
| menu.setPlaceholder(placeHolder).setMaxValues(roles.size()); | ||||||
|
|
||||||
| for (Role role : roles) { | ||||||
| if (role.getName().equals("@everyone") || role.getTags().getBotId() != null) { | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
You can easily create a role named Thankfully, the real And to use Role#getTags you need to enable this in the cache, which I highly doubt is enabled. |
||||||
| continue; | ||||||
| } | ||||||
| menu.addOption(role.getName(), role.getId()); | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| /** | ||||||
| * Creates an embedded message to send with the selection menu | ||||||
| * | ||||||
| * <p> | ||||||
| * </p> | ||||||
| * | ||||||
| * @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 String title, | ||||||
| @Nullable String description) { | ||||||
|
|
||||||
| if (title == null) { | ||||||
| title = "Select your roles:"; | ||||||
| } | ||||||
|
|
||||||
| return new EmbedBuilder().setTitle(title) | ||||||
| .setDescription(description) | ||||||
| .setColor(new Color(24, 221, 136)) | ||||||
| .build(); | ||||||
| } | ||||||
|
|
||||||
| @Override | ||||||
| public void onSelectionMenu(@NotNull SelectionMenuEvent event, @NotNull List<String> args) { | ||||||
| Member member = Objects.requireNonNull(event.getMember(), "Member is null"); | ||||||
|
|
||||||
| Guild guild = Objects.requireNonNull(event.getGuild()); | ||||||
|
|
||||||
| List<Role> selectedRoles = new ArrayList<>(); | ||||||
| for (SelectOption selectedOption : Objects.requireNonNull(event.getSelectedOptions())) { | ||||||
| Role selectedRole = guild.getRoleById(selectedOption.getValue()); | ||||||
| if (selectedRole != null && guild.getSelfMember().canInteract(selectedRole)) { | ||||||
| selectedRoles.add(selectedRole); | ||||||
| } | ||||||
| } | ||||||
|
Comment on lines
+186
to
+192
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thoughts on using the Stream API instead? event.getSelectedOptions()
.stream()
.map(selectedOption -> guild.getRoleById(selectedOption.getValue())
.filter(Objects::requireNonNull)
.filter(role -> guild.getSelfMember().canInteract(role))
.toList();Not sure how advanced you are with Java, so if you don't understand how this works, feel free to ask ^^ |
||||||
|
|
||||||
| // True if the event option was 'choose' | ||||||
| if (event.getMessage().isEphemeral()) { | ||||||
|
|
||||||
| SelectionMenu.Builder menu = SelectionMenu.create(generateComponentId(member.getId())); | ||||||
| menu.setPlaceholder("Select your roles") | ||||||
| .setMaxValues(selectedRoles.size()) | ||||||
| .setMinValues(0); | ||||||
|
|
||||||
| // Add selected options to the menu | ||||||
| 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<SelectOption> menuOptions = | ||||||
| Objects.requireNonNull(event.getInteraction().getComponent()).getOptions(); | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Why throw a NPE when you can throw a NPE? Exactly, if you remove the requireNonNull the code is a ton shorter, while it's behavior doesn't change. |
||||||
|
|
||||||
|
|
||||||
| for (SelectOption selectedOption : menuOptions) { | ||||||
| Role role = guild.getRoleById(selectedOption.getValue()); | ||||||
|
|
||||||
| if (role == null) { | ||||||
| logger.info( | ||||||
| "The {} ({}) role has been removed but is still an option in the selection menu", | ||||||
| selectedOption.getLabel(), selectedOption.getValue()); | ||||||
| continue; | ||||||
| } | ||||||
|
|
||||||
| if (selectedRoles.contains(role)) { | ||||||
| guild.addRoleToMember(member, role).queue(); | ||||||
| } else { | ||||||
| event.getGuild().removeRoleFromMember(member, role).queue(); | ||||||
| } | ||||||
|
Comment on lines
+229
to
+233
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead, use Guild#modifyMemberRoles and add/remove all roles together, if possible |
||||||
| } | ||||||
|
|
||||||
| event.reply("Updated your roles!").setEphemeral(true).queue(); | ||||||
| } | ||||||
|
|
||||||
| /** | ||||||
| * This gets the OptionMapping and returns the value as a string if there is one | ||||||
| * | ||||||
| * <p> | ||||||
| * </p> | ||||||
| * | ||||||
| * @param option the {@link OptionMapping} | ||||||
| * @return the value. nullable {@link String} | ||||||
| */ | ||||||
| private static @Nullable String handleOption(@Nullable OptionMapping option) { | ||||||
|
|
||||||
| if (option == null) { | ||||||
| return null; | ||||||
| } | ||||||
|
|
||||||
| if (option.getType() == OptionType.STRING) { | ||||||
| return option.getAsString(); | ||||||
| } else if (option.getType() == OptionType.BOOLEAN) { | ||||||
| return option.getAsBoolean() ? "true" : "false"; | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Not sure, but I'd assume toStringing would also result in |
||||||
| } else { | ||||||
| return null; | ||||||
| } | ||||||
| } | ||||||
| } | ||||||
Uh oh!
There was an error while loading. Please reload this page.