Skip to content

Commit eae9130

Browse files
committed
Fix the /role-select command
The previous design crashed in a guild with more than 25 roles. This commit fixes that, by adding a "roles" option to the command, which is a text option from which mention roles will be retrieved.
1 parent efdfef2 commit eae9130

File tree

3 files changed

+89
-105
lines changed

3 files changed

+89
-105
lines changed

src/commander/java/com/mcmoddev/mmdbot/commander/commands/RoleSelectCommand.java

Lines changed: 71 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@
3535
import net.dv8tion.jda.api.Permission;
3636
import net.dv8tion.jda.api.entities.Emoji;
3737
import net.dv8tion.jda.api.entities.Guild;
38+
import net.dv8tion.jda.api.entities.MessageEmbed;
3839
import net.dv8tion.jda.api.entities.Role;
39-
import net.dv8tion.jda.api.interactions.Interaction;
4040
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
4141
import net.dv8tion.jda.api.interactions.commands.OptionType;
4242
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
@@ -48,13 +48,13 @@
4848
import net.dv8tion.jda.api.interactions.components.text.TextInput;
4949
import net.dv8tion.jda.api.interactions.components.text.TextInputStyle;
5050

51-
import javax.annotation.Nullable;
5251
import java.awt.Color;
5352
import java.util.ArrayList;
5453
import java.util.Collection;
5554
import java.util.List;
5655
import java.util.Objects;
5756
import java.util.UUID;
57+
import java.util.stream.Collectors;
5858

5959
public class RoleSelectCommand extends SlashCommand {
6060

@@ -67,8 +67,6 @@ public class RoleSelectCommand extends SlashCommand {
6767
.onModalInteraction(COMMAND::onModalInteraction)
6868
.build();
6969

70-
private static final Color COLOUR = new Color(24, 221, 136, 255);
71-
7270
private RoleSelectCommand() {
7371
this.name = "role-select";
7472
this.guildOnly = true;
@@ -80,99 +78,97 @@ private RoleSelectCommand() {
8078
Permission.MANAGE_ROLES
8179
};
8280
options = List.of(
81+
new OptionData(OptionType.STRING, "roles", "Mention the roles to add to the panel.", true),
8382
new OptionData(OptionType.BOOLEAN, "dropdown", "If the role selection panel should be a dropdown rather than buttons.")
8483
);
8584
}
8685

8786
@Override
8887
protected void execute(final SlashCommandEvent event) {
88+
final var selfMember = Objects.requireNonNull(event.getGuild()).getSelfMember();
89+
Objects.requireNonNull(event.getMember());
90+
final var args = event.getOption("roles", List.<String>of(), m -> m.getMentionedRoles()
91+
.stream()
92+
.filter(r -> !r.isManaged() && selfMember.canInteract(r) && event.getMember().canInteract(r))
93+
.map(Role::getId)
94+
.collect(Collectors.toList()));
95+
96+
if (args.isEmpty()) {
97+
event
98+
.deferReply(true)
99+
.setContent("Please provide at least one role for the panel.")
100+
.queue();
101+
return;
102+
} else if (args.size() > 25) {
103+
event
104+
.deferReply(true)
105+
.setContent("Please provide less than 25 roles for the panel.")
106+
.queue();
107+
}
108+
args.add(0, String.valueOf(event.getOption("dropdown", false, OptionMapping::getAsBoolean)));
109+
89110
final var title = TextInput.create("title", "Title", TextInputStyle.SHORT)
90111
.setRequired(false)
112+
.setMaxLength(MessageEmbed.TITLE_MAX_LENGTH)
91113
.setPlaceholder("The title of the role panel to create.")
92114
.build();
93115

94116
final var description = TextInput.create("description", "Description", TextInputStyle.PARAGRAPH)
95117
.setRequired(false)
118+
.setMaxLength(Math.min(MessageEmbed.DESCRIPTION_MAX_LENGTH, TextInput.TEXT_INPUT_MAX_LENGTH))
96119
.setPlaceholder("The description role panel to create.")
97120
.build();
98121

99-
// TODO implement mappings for role icons and add colour option
122+
final var colour = TextInput.create("colour", "Colour", TextInputStyle.SHORT)
123+
.setRequired(false)
124+
.setPlaceholder("The colour the embed will have. Example: #ffffff")
125+
.build();
100126

101-
final var modal = COMPONENT_LISTENER.createModal("Role selection panel creation", Component.Lifespan.TEMPORARY,
102-
String.valueOf(event.getOption("dropdown", false, OptionMapping::getAsBoolean)))
127+
final var modal = COMPONENT_LISTENER.createModal("Role selection panel creation", Component.Lifespan.TEMPORARY, args)
103128
.addActionRows(
104129
ActionRow.of(title),
105-
ActionRow.of(description)
130+
ActionRow.of(description),
131+
ActionRow.of(colour)
106132
)
107133
.build();
108134

109135
event.replyModal(modal).queue();
110136
}
111137

112-
private static void addMenuOptions(@NonNull final Interaction interaction,
113-
@NonNull final SelectMenu.Builder menu,
114-
@NonNull final String placeHolder,
115-
@Nullable final Integer minValues) {
116-
117-
final var guild = Objects.requireNonNull(interaction.getGuild());
118-
final var highestBotRole = guild.getSelfMember().getRoles().get(0);
119-
final var guildRoles = guild.getRoles();
120-
121-
final var roles = guildRoles.subList(guildRoles.indexOf(highestBotRole) + 1, guildRoles.size());
122-
123-
if (minValues != null) {
124-
menu.setMinValues(minValues);
125-
}
126-
127-
menu.setPlaceholder(placeHolder)
128-
.setMaxValues(roles.size())
129-
.addOptions(roles.stream()
130-
.filter(role -> !role.isPublicRole())
131-
.filter(role -> !role.getTags().isBot())
132-
.map(RoleSelectCommand::roleToSelection)
133-
.toList());
134-
}
135-
136-
@NonNull
137-
private static SelectOption roleToSelection(@NonNull final Role role) {
138-
final var roleIcon = role.getIcon();
139-
140-
if (roleIcon == null || !roleIcon.isEmoji() || !roleIcon.isEmoji()) {
141-
return SelectOption.of(role.getName(), role.getId());
142-
} else {
143-
return SelectOption.of(role.getName(), role.getId())
144-
.withEmoji(Emoji.fromUnicode(Objects.requireNonNull(roleIcon.getEmoji())));
145-
}
146-
}
147-
148138
public void onModalInteraction(final ModalInteractionContext context) {
149139
final var event = context.getEvent();
150140
if (!event.isFromGuild()) return;
151-
final var menu = COMPONENT_LISTENER.createMenu(new String[]{MenuType.IN_CREATION.toString()},
152-
Component.Lifespan.TEMPORARY,
153-
// Parse the isDropdown
154-
context.getArguments().get(0));
155-
156-
addMenuOptions(event, menu, "Select the roles to display", 1);
141+
Objects.requireNonNull(context.getGuild());
157142

158143
final var titleOption = event.getValue("title");
159144
final var descriptionOption = event.getValue("description");
145+
final var colourOption = event.getValue("colour");
160146

161147
final var title = titleOption == null ? null : titleOption.getAsString();
162148
final var description = descriptionOption == null ? null : descriptionOption.getAsString();
149+
final var colour = colourOption == null ? null : Color.decode(colourOption.getAsString());
163150

164-
event.reply(new MessageBuilder()
165-
.setEmbeds(
166-
new EmbedBuilder()
167-
.setTitle(title)
168-
.setColor(COLOUR)
169-
.setDescription(description)
170-
.build()
171-
)
172-
.setActionRows(ActionRow.of(menu.build()))
173-
.build())
174-
.setEphemeral(true)
175-
.queue();
151+
final var isDropdown = Boolean.parseBoolean(context.getArguments().get(0));
152+
final var roles = context.getArguments().subList(1, context.getArguments().size())
153+
.stream()
154+
.map(context.getGuild()::getRoleById)
155+
.toList();
156+
157+
final var message = new MessageBuilder()
158+
.setEmbeds(
159+
new EmbedBuilder()
160+
.setTitle(title)
161+
.setColor(colour)
162+
.setDescription(description)
163+
.build()
164+
)
165+
.setActionRows();
166+
167+
if (isDropdown) {
168+
handleDropdownCreation(context, message, roles);
169+
} else {
170+
handleButtonOption(context, message, roles);
171+
}
176172
}
177173

178174
protected void onSelectMenu(final SelectMenuInteractionContext context) {
@@ -187,21 +183,10 @@ protected void onSelectMenu(final SelectMenuInteractionContext context) {
187183
.filter(selfMember::canInteract)
188184
.toList();
189185

190-
final boolean isDropdown = Boolean.parseBoolean(context.getArguments().get(0));
191-
192-
switch (MenuType.valueOf(context.getItemComponentArguments().get(0))) {
193-
case IN_CREATION -> {
194-
if (isDropdown) {
195-
handleDropdownCreation(context, selectedRoles);
196-
} else {
197-
handleButtonOption(context, selectedRoles);
198-
}
199-
}
200-
case ROLE_PANEL -> handleRoleSelection(context, selectedRoles, guild);
201-
}
186+
handleRoleSelection(context, selectedRoles, guild);
202187
}
203188

204-
private static void handleButtonOption(final @NonNull SelectMenuInteractionContext context, final List<Role> selectedRoles) {
189+
private static void handleButtonOption(final @NonNull ModalInteractionContext context, final MessageBuilder builder, final List<Role> selectedRoles) {
205190
final UUID id = UUID.randomUUID();
206191
final var idString = id.toString();
207192
final List<ActionRow> rows = new ArrayList<>();
@@ -222,47 +207,39 @@ private static void handleButtonOption(final @NonNull SelectMenuInteractionConte
222207
.toList()
223208
));
224209
}
225-
/* TODO what's the actual limit?
226-
if (rows.size() > 5) {
227-
context.getEvent().deferReply(true).setContent("Too many roles selected!");
228-
return;
229-
}
230-
*/
231210

232211
final var component = new Component(COMPONENT_LISTENER.getName(), id, List.of(), Component.Lifespan.PERMANENT);
233212
COMPONENT_LISTENER.insertComponent(component);
234213

235-
context.getEvent().getChannel()
236-
.sendMessageEmbeds(context.getEvent().getMessage().getEmbeds().get(0))
237-
.setActionRows(rows)
214+
context.getEvent().getMessageChannel()
215+
.sendMessage(builder.setActionRows(rows).build())
216+
.flatMap($ -> context.getEvent().reply("Message created and sent successfully!").setEphemeral(true))
238217
.queue();
239-
240-
context.getEvent().reply("Message created and sent successfully!").setEphemeral(true).queue();
241218
}
242219

243220
private static Button createButtonForRole(final String id, final Role role) {
244221
final var icon = role.getIcon();
245222
final var bId = Component.createIdWithArguments(id, role.getId());
246223
if (icon != null && icon.isEmoji()) {
247-
return Button.of(ButtonStyle.PRIMARY, bId, role.getName(), Emoji.fromUnicode(icon.getEmoji()));
224+
return Button.of(ButtonStyle.PRIMARY, bId, role.getName(), Emoji.fromUnicode(Objects.requireNonNull(icon.getEmoji())));
248225
}
249226
return Button.primary(bId, role.getName());
250227
}
251228

252-
private static void handleDropdownCreation(final @NonNull SelectMenuInteractionContext context, final List<Role> selectedRoles) {
253-
SelectMenu.Builder menu = COMPONENT_LISTENER.createMenu(new String[] {MenuType.ROLE_PANEL.toString()}, Component.Lifespan.PERMANENT, String.valueOf(true))
229+
private static void handleDropdownCreation(final @NonNull ModalInteractionContext context, final MessageBuilder message, final List<Role> selectedRoles) {
230+
SelectMenu.Builder menu = COMPONENT_LISTENER.createMenu(Component.Lifespan.PERMANENT)
254231
.setPlaceholder("Select your roles")
255232
.setMaxValues(selectedRoles.size())
256233
.setMinValues(0);
257234

258235
selectedRoles.forEach(role -> menu.addOption(role.getName(), role.getId()));
259236

260-
context.getEvent().getChannel()
261-
.sendMessageEmbeds(context.getEvent().getMessage().getEmbeds().get(0))
262-
.setActionRow(menu.build())
237+
context.getEvent().getMessageChannel()
238+
.sendMessage(message
239+
.setActionRows(ActionRow.of(menu.build()))
240+
.build())
241+
.flatMap($ -> context.getEvent().reply("Message created and sent successfully!").setEphemeral(true))
263242
.queue();
264-
265-
context.getEvent().reply("Message created and sent successfully!").setEphemeral(true).queue();
266243
}
267244

268245
private static void handleRoleSelection(final @NonNull SelectMenuInteractionContext context, final @NonNull Collection<Role> selectedRoles, final Guild guild) {
@@ -324,9 +301,4 @@ protected void onButtonInteraction(@NonNull final ButtonInteractionContext conte
324301
.queue();
325302
}
326303
}
327-
328-
enum MenuType {
329-
IN_CREATION,
330-
ROLE_PANEL
331-
}
332304
}

src/core/java/com/mcmoddev/mmdbot/core/commands/component/ComponentListener.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
*/
2121
package com.mcmoddev.mmdbot.core.commands.component;
2222

23+
import com.google.errorprone.annotations.CanIgnoreReturnValue;
2324
import com.mcmoddev.mmdbot.core.commands.component.context.ButtonInteractionContext;
2425
import com.mcmoddev.mmdbot.core.commands.component.context.ModalInteractionContext;
2526
import com.mcmoddev.mmdbot.core.commands.component.context.SelectMenuInteractionContext;
@@ -65,15 +66,13 @@ public SelectMenu.Builder createMenu(@NonNull Component.Lifespan lifespan, final
6566
}
6667

6768
@NonNull
68-
public SelectMenu.Builder createMenu(@NonNull final String[] componentArgs, @NonNull Component.Lifespan lifespan, final String... args) {
69-
final var comp = new Component(name, UUID.randomUUID(), Arrays.asList(args), lifespan);
70-
insertComponent(comp);
71-
return SelectMenu.create(Component.createIdWithArguments(comp.uuid().toString(), componentArgs));
69+
public Modal.Builder createModal(@NonNull final String label, @NonNull final Component.Lifespan lifespan, final String... args) {
70+
return createModal(label, lifespan, Arrays.asList(args));
7271
}
7372

7473
@NonNull
75-
public Modal.Builder createModal(@NonNull final String label, @NonNull final Component.Lifespan lifespan, final String... args) {
76-
final var comp = new Component(name, UUID.randomUUID(), Arrays.asList(args), lifespan);
74+
public Modal.Builder createModal(@NonNull final String label, @NonNull final Component.Lifespan lifespan, final List<String> args) {
75+
final var comp = new Component(name, UUID.randomUUID(), args, lifespan);
7776
insertComponent(comp);
7877
return Modal.create(comp.uuid().toString(), label);
7978
}
@@ -82,6 +81,7 @@ public void insertComponent(final Component component) {
8281
manager.getStorage().insertComponent(component);
8382
}
8483

84+
@CanIgnoreReturnValue
8585
public Component insertComponent(final UUID id, final Component.Lifespan lifespan, final String... args) {
8686
final var comp = new Component(getName(), id, Arrays.asList(args), lifespan);
8787
insertComponent(comp);

src/core/java/com/mcmoddev/mmdbot/core/commands/component/context/BaseItemComponentInteractionContext.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import com.mcmoddev.mmdbot.core.commands.component.Component;
2424
import com.mcmoddev.mmdbot.core.commands.component.ComponentManager;
2525
import lombok.NonNull;
26+
import net.dv8tion.jda.api.entities.Guild;
2627
import net.dv8tion.jda.api.entities.Member;
2728
import net.dv8tion.jda.api.entities.User;
2829
import net.dv8tion.jda.api.interactions.Interaction;
@@ -47,6 +48,17 @@ public interface BaseItemComponentInteractionContext<T extends Interaction> {
4748
@NonNull
4849
T getEvent();
4950

51+
/**
52+
* The {@link Guild} this interaction happened in.
53+
* <br>This is null in direct messages.
54+
*
55+
* @return The {@link Guild} or null
56+
*/
57+
@Nullable
58+
default Guild getGuild() {
59+
return getEvent().getGuild();
60+
}
61+
5062
/**
5163
* The {@link Member} who caused this interaction.
5264
* <br>This is null if the interaction is not from a guild.

0 commit comments

Comments
 (0)