Skip to content
Closed
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
590fb6f
Initial commit of the RoleSelect command
JJeeff248 Nov 28, 2021
1c5384e
Applied spotless check
JJeeff248 Nov 28, 2021
cdecca8
Added check to make sure the user has the right permissions to change…
JJeeff248 Nov 28, 2021
bb7e2eb
Changed the way bot replies
JJeeff248 Nov 29, 2021
6f87f03
Users can deselect roles to remove them
JJeeff248 Nov 29, 2021
48b8988
Removed unnecessary Objects#requireNotNull
JJeeff248 Dec 2, 2021
307b62a
A bit of code clean up
JJeeff248 Dec 2, 2021
f7cac5b
A bit of code clean up
JJeeff248 Dec 2, 2021
31d8462
Removed unused import
JJeeff248 Dec 2, 2021
d017bc1
Renamed variable to make more sense.
JJeeff248 Dec 2, 2021
08ff0b0
Tidied up code a bit.
JJeeff248 Dec 8, 2021
11cc353
Compressed the makeEmbed method
JJeeff248 Dec 8, 2021
7fd1224
Changed log messages to meet Sonar string format standards
JJeeff248 Dec 8, 2021
58e0c89
Spotless Applied
JJeeff248 Dec 8, 2021
178c2c0
Now complies with SonarLint COGNITIVE COMPLEXITY
JJeeff248 Dec 8, 2021
d212382
Excludes bot roles from menus
JJeeff248 Dec 8, 2021
b76a15f
Added curley brackets to if statement
JJeeff248 Dec 13, 2021
076d351
Added type-to-count table to audit summary
Zabuzard Dec 13, 2021
041e54c
Adding mute, unmute commands
Zabuzard Dec 10, 2021
bdd9799
Bugfix with missing perms to interact with role
Zabuzard Nov 8, 2021
214d079
Getting rid of some code duplication
Zabuzard Nov 8, 2021
4d16e2e
Simplified mute/unmute flow using individual methods (CR Tais)
Zabuzard Nov 16, 2021
6d9fc10
Integrated ModerationActionsStore into Mute/Unmute
Zabuzard Dec 10, 2021
71bc3b1
Moved routines and listeners inside Commands
Zabuzard Dec 10, 2021
4d2f374
Moved isMutedRole logic into utility class to reduce duplication
Zabuzard Dec 10, 2021
9e85bf8
Added RejoinMuted listener to fix mute-bypass-exploit
Zabuzard Dec 10, 2021
afd08a8
Cleanup and bugfix with unmodifable list
Zabuzard Dec 10, 2021
fd58740
Removed DatabaseCommand
Zabuzard Dec 14, 2021
6894466
Spring Version Update (#324)
krankkkk Dec 15, 2021
ebf7498
Create Custom Filter for the AsyncAppender (#327)
krankkkk Dec 16, 2021
3ec7a99
Addition of stale-workflow
Zabuzard Dec 16, 2021
e27451f
Initial commit of the RoleSelect command
JJeeff248 Nov 28, 2021
41ccb92
Applied spotless check
JJeeff248 Nov 28, 2021
848eb43
Added check to make sure the user has the right permissions to change…
JJeeff248 Nov 28, 2021
fcff080
Changed the way bot replies
JJeeff248 Nov 29, 2021
0f412a9
Users can deselect roles to remove them
JJeeff248 Nov 29, 2021
2b4c3ff
Removed unnecessary Objects#requireNotNull
JJeeff248 Dec 2, 2021
319d13f
A bit of code clean up
JJeeff248 Dec 2, 2021
f770046
A bit of code clean up
JJeeff248 Dec 2, 2021
f23c792
Removed unused import
JJeeff248 Dec 2, 2021
6ffd558
Renamed variable to make more sense.
JJeeff248 Dec 2, 2021
c9a7de9
Tidied up code a bit.
JJeeff248 Dec 8, 2021
f5c1e92
Compressed the makeEmbed method
JJeeff248 Dec 8, 2021
ac83a88
Changed log messages to meet Sonar string format standards
JJeeff248 Dec 8, 2021
2a9dde9
Spotless Applied
JJeeff248 Dec 8, 2021
0b48812
Now complies with SonarLint COGNITIVE COMPLEXITY
JJeeff248 Dec 8, 2021
c81eca4
Excludes bot roles from menus
JJeeff248 Dec 8, 2021
27c2d1d
Added curley brackets to if statement
JJeeff248 Dec 13, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import org.jetbrains.annotations.NotNull;
import org.togetherjava.tjbot.commands.basic.DatabaseCommand;
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;
Expand Down Expand Up @@ -57,6 +58,7 @@ public enum Commands {
commands.add(new UnbanCommand(actionsStore));
commands.add(new FreeCommand());
commands.add(new AuditCommand(actionsStore));
commands.add(new RoleSelectCommand());

return commands;
}
Expand Down
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't the highest role be 0?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Objects.requireNonNull(event.getGuild()).getSelfMember().getRoles().get(1);
event.getGuild().getSelfMember().getRoles().get(1);

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) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (role.getName().equals("@everyone") || role.getTags().getBotId() != null) {
if (role.getIdLong() == event.getGuild().getIdLong() || role.getTags().getBotId() != null) {

You can easily create a role named @everyone
So instead, check or the role's ID is equal to the guild's ID.

Thankfully, the real @everyone role is always equal to the guild's ID

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
Copy link
Member

Choose a reason for hiding this comment

The 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();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Objects.requireNonNull(event.getInteraction().getComponent()).getOptions();
event.getInteraction().getComponent().getOptions();

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
Copy link
Member

Choose a reason for hiding this comment

The 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";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return option.getAsBoolean() ? "true" : "false";
return option.getAsBoolean().toString();

Not sure, but I'd assume toStringing would also result in true or false

} else {
return null;
}
}
}