-
-
Notifications
You must be signed in to change notification settings - Fork 89
/modmail slash command #329
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
Closed
talentedasian
wants to merge
7
commits into
Together-Java:develop
from
talentedasian:feature/modmail
Closed
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
2cb5974
Draft commit for /modmail slash command.
talentedasian 52744e5
Instead of getting the mods with an expensive query, query it just once
talentedasian 806bee3
Send DM to selected mod. If the selected mod wasn't found, which is
talentedasian b563a4c
Reply an ephemeral message to user after sending the message to a mod…
talentedasian 7ec6b33
Miscellaneous refactors
talentedasian 1bfbf42
* Add /reloadmod command for moderators to reload the list of choices in
talentedasian e154d7f
* Completely redo the way how select options are being generated.
talentedasian File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
169 changes: 169 additions & 0 deletions
169
application/src/main/java/org/togetherjava/tjbot/commands/modmail/ModmailCommand.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
package org.togetherjava.tjbot.commands.modmail; | ||
|
||
import net.dv8tion.jda.api.entities.Guild; | ||
import net.dv8tion.jda.api.events.ReadyEvent; | ||
import net.dv8tion.jda.api.events.interaction.SelectionMenuEvent; | ||
import net.dv8tion.jda.api.events.interaction.SlashCommandEvent; | ||
import net.dv8tion.jda.api.interactions.commands.OptionType; | ||
import net.dv8tion.jda.api.interactions.components.ActionRow; | ||
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.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
import org.togetherjava.tjbot.commands.SlashCommandAdapter; | ||
import org.togetherjava.tjbot.commands.SlashCommandVisibility; | ||
import org.togetherjava.tjbot.config.Config; | ||
|
||
import java.util.ArrayList; | ||
import java.util.List; | ||
import java.util.Objects; | ||
|
||
/** | ||
* Command that either sends a direct message to a moderator or sends the message to the dedicated | ||
* mod audit channel by the moderators. | ||
*/ | ||
public class ModmailCommand extends SlashCommandAdapter { | ||
private static final Logger logger = LoggerFactory.getLogger(ModmailCommand.class); | ||
|
||
private static final String COMMAND_NAME = "modmail"; | ||
private static final String TARGET_OPTION = "message"; | ||
private static final Config config = Config.getInstance(); | ||
|
||
final List<SelectOption> mods = new ArrayList<>(); | ||
|
||
/** | ||
* Creates an instance of the ModMail command. | ||
*/ | ||
public ModmailCommand() { | ||
super(COMMAND_NAME, | ||
"sends a message to either a single moderator or on the mod_audit_log channel", | ||
SlashCommandVisibility.GLOBAL); | ||
|
||
getData().addOption(OptionType.STRING, TARGET_OPTION, "The message to send", true); | ||
|
||
mods.add(SelectOption.of("All Moderators", "all")); | ||
} | ||
|
||
@Override | ||
public void onSlashCommand(@NotNull SlashCommandEvent event) { | ||
String memberId = event.getUser().getId(); | ||
|
||
event.reply(""" | ||
Select the moderator to send message to, or select "All Moderators" to send to | ||
the guild's mod audit channel. | ||
""") | ||
.addActionRow(SelectionMenu | ||
.create(generateComponentId(memberId, event.getOption(TARGET_OPTION).getAsString())) | ||
.addOptions(mods) | ||
.build()) | ||
.setEphemeral(false) | ||
.queue(); | ||
} | ||
|
||
@Override | ||
public void onSelectionMenu(@NotNull SelectionMenuEvent event, @NotNull List<String> args) { | ||
String message = args.get(1); | ||
// Ignore if another user clicked the button. | ||
String userId = args.get(0); | ||
if (!userId.equals(event.getUser().getId())) { | ||
event.reply( | ||
"Sorry, but only the user who triggered the command can interact with the menu.") | ||
.setEphemeral(true) | ||
.queue(); | ||
return; | ||
} | ||
|
||
SelectionMenu disabledMenu = event.getSelectionMenu().asDisabled(); | ||
|
||
// did user select to send message to all mods | ||
String modId = event.getValues().get(0); | ||
if (modId.equals("all")) { | ||
// currently blocked by #296 | ||
event.reply("Message now sent to all mods").setEphemeral(true).queue(); | ||
return; | ||
} | ||
|
||
// disable selection menu | ||
event.getMessage().editMessageComponents(ActionRow.of(disabledMenu)).queue(); | ||
|
||
boolean wasSent = sendToMod(modId, message, event); | ||
if (!wasSent) { | ||
event.reply("The moderator you chose was not found on the guild.") | ||
.setEphemeral(true) | ||
.queue(); | ||
|
||
String modSelectedByUser = event.getSelectedOptions().get(0).getLabel(); | ||
logger.warn(""" | ||
Moderator '{}' chosen by user is not on the guild. Use the /reloadmod command | ||
to update the list of moderators. | ||
""", modSelectedByUser); | ||
|
||
return; | ||
} | ||
|
||
event.reply("Message now sent to selected moderator").setEphemeral(true).queue(); | ||
} | ||
|
||
/** | ||
* Populates the list of moderators and stores it into a list to avoid querying an expensive | ||
* call to discord everytime the command is used. | ||
* | ||
* @param event the event that triggered this method | ||
*/ | ||
@Override | ||
public void onReady(@NotNull ReadyEvent event) { | ||
Guild guild = event.getJDA().getGuildById(config.getGuildId()); | ||
ModmailUtil.listOfMod(guild, mods); | ||
} | ||
|
||
private boolean sendToMod(@NotNull String modId, @NotNull String message, | ||
@NotNull SelectionMenuEvent event) { | ||
// the else is when the user invoked the command not on the context of a guild. | ||
Guild guild = Objects.requireNonNullElse(event.getGuild(), | ||
event.getJDA().getGuildById(config.getGuildId())); | ||
|
||
return !guild.retrieveMemberById(modId) | ||
.submit() | ||
.thenCompose(user -> user.getUser().openPrivateChannel().submit()) | ||
.thenAccept(channel -> channel | ||
.sendMessageEmbeds(ModmailUtil.messageEmbed(event.getUser().getName(), message)) | ||
.queue()) | ||
.whenComplete((v, err) -> { | ||
if (err != null) | ||
err.printStackTrace(); | ||
}) | ||
.isCompletedExceptionally(); | ||
} | ||
|
||
/** | ||
* Reloads the list of moderators to choose from from the {@link ModmailCommand}. | ||
* <p> | ||
* Only members who have the Moderator role can use this command. | ||
*/ | ||
public class ReloadModmailCommand extends SlashCommandAdapter { | ||
|
||
private static final String COMMAND_NAME = "reloadmod"; | ||
|
||
public ReloadModmailCommand() { | ||
super(COMMAND_NAME, "reloads the moderators in the modmail command", | ||
SlashCommandVisibility.GUILD); | ||
} | ||
|
||
@Override | ||
public void onSlashCommand(@NotNull SlashCommandEvent event) { | ||
mods.clear(); | ||
ModmailUtil.listOfMod(event.getGuild(), mods); | ||
|
||
if (ModmailUtil.doesUserHaveModRole(event.getMember(), event.getGuild())) { | ||
event.reply("List of moderators has now been updated.").setEphemeral(true).queue(); | ||
return; | ||
} | ||
|
||
event.reply("Only moderators can use this command.").setEphemeral(true).queue(); | ||
} | ||
|
||
} | ||
|
||
|
||
} |
89 changes: 89 additions & 0 deletions
89
application/src/main/java/org/togetherjava/tjbot/commands/modmail/ModmailUtil.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
package org.togetherjava.tjbot.commands.modmail; | ||
|
||
import net.dv8tion.jda.api.EmbedBuilder; | ||
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.interactions.components.selections.SelectOption; | ||
import org.jetbrains.annotations.NotNull; | ||
import org.togetherjava.tjbot.config.Config; | ||
|
||
import java.awt.*; | ||
import java.util.List; | ||
import java.util.Optional; | ||
import java.util.function.Predicate; | ||
import java.util.regex.Pattern; | ||
|
||
/** | ||
* Utility methods that interact either directly or indirectly with the {@link ModmailCommand}. | ||
*/ | ||
public class ModmailUtil { | ||
|
||
public static final Predicate<String> isModRole = | ||
Pattern.compile(Config.getInstance().getHeavyModerationRolePattern()) | ||
.asMatchPredicate(); | ||
|
||
private ModmailUtil() { | ||
throw new UnsupportedOperationException(); | ||
} | ||
|
||
/** | ||
* Finds the moderators on the given guild and stores it for later use. | ||
* <p> | ||
* This call is expensive, thus, it should only be used preferably once by storing the result | ||
* from the given list or seldomly when moderators want to reload the list of moderators to | ||
* choose from from the {@link ModmailCommand}. | ||
* <p/> | ||
* Since the elements in the given list will not be overwritten, the caller is responsible in | ||
* doing such actions. | ||
*/ | ||
public static void listOfMod(@NotNull Guild guild, List<SelectOption> modsOptions) { | ||
Role modRole = getModRole(guild) | ||
.orElseThrow(() -> new IllegalStateException("No moderator role found")); | ||
|
||
guild.findMembersWithRoles(modRole) | ||
.onSuccess(mods -> mods.forEach( | ||
mod -> modsOptions.add(SelectOption.of(mod.getEffectiveName(), mod.getId())))); | ||
} | ||
|
||
/** | ||
* Gets the moderator role. | ||
* | ||
* @param guild the guild to get the moderator role from | ||
* @return the moderator role, if found | ||
*/ | ||
public static @NotNull Optional<Role> getModRole(@NotNull Guild guild) { | ||
return guild.getRoles().stream().filter(role -> isModRole.test(role.getName())).findAny(); | ||
} | ||
|
||
/** | ||
* Checks whether the given member is a moderator on the given guild. | ||
* <p> | ||
* See {@link Config#getHeavyModerationRolePattern()}. | ||
* | ||
* @param member the member to check for moderator role. | ||
* @param guild the guild to get the moderator role from. | ||
* @return true if the member has the role Moderator | ||
*/ | ||
public static boolean doesUserHaveModRole(@NotNull Member member, @NotNull Guild guild) { | ||
return member.canInteract(getModRole(guild) | ||
.orElseThrow(() -> new IllegalStateException("No moderator role found"))); | ||
} | ||
|
||
/** | ||
* Creates a color black {@link MessageEmbed} with a non-inline field of the supplied message. | ||
* | ||
* @param user the user who invoked the command. | ||
* @param message the message the user wants to send to to a moderator or the moderators. | ||
* @return returns a {@link MessageEmbed} to send to the moderator. | ||
*/ | ||
public static MessageEmbed messageEmbed(@NotNull String user, @NotNull String message) { | ||
return new EmbedBuilder().setAuthor("Modmail Command invoked") | ||
.setColor(Color.BLACK) | ||
.setTitle("Message from user '%s' who used /modmail command".formatted(user)) | ||
.addField("Message", message, false) | ||
.build(); | ||
} | ||
|
||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will not compile.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Really? I'm sure I've tested and ran it on my machine numerous times.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also ran gradle
build
so I'm really sure it works. Do you mind checking it again?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It doesn't compile no, I'm not sure what you're doing there
modmailCommand.new ReloadModmailCommand()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
ReloadModmailCommand
is an inner class since it's the only solution I could think of to reload the mods in the/modmail
command. Thus, I am instantiating theReloadModmailCommand
from theModmail
object.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah that explains
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am genuinely confused why it doesn't compile. Do you mind helping me out on this one?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, it compiles like you said, it's just done in a weird way.
To my knowledge, you can remove
modmailCommand.
frommodmailCommand.new ReloadModmailCommand()
So please do that, if you can't, refer to the class instead of an instance?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can't. That's the required syntax for instantiating a non-static inner class. It's really weird.