Skip to content

Commit

Permalink
Enhanced mod action dm message (#647)
Browse files Browse the repository at this point in the history
* enhanced mod aciton dm message

* removed unused fields

* improvements

* improved docs

* code fixes

* code fixes

* fixes

* CR
  • Loading branch information
Taz03 authored Feb 7, 2023
1 parent d839687 commit 8dc3b07
Show file tree
Hide file tree
Showing 8 changed files with 134 additions and 172 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.*;
import net.dv8tion.jda.api.events.GenericEvent;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import net.dv8tion.jda.api.exceptions.ErrorResponseException;
import net.dv8tion.jda.api.interactions.InteractionHook;
Expand All @@ -13,7 +12,6 @@
import net.dv8tion.jda.api.requests.ErrorResponse;
import net.dv8tion.jda.api.requests.RestAction;
import net.dv8tion.jda.api.requests.restaction.AuditableRestAction;
import net.dv8tion.jda.api.utils.Result;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -45,6 +43,7 @@ public final class BanCommand extends SlashCommandAdapter {
private static final String REASON_OPTION = "reason";
private static final String COMMAND_NAME = "ban";
private static final String ACTION_VERB = "ban";
private static final String ACTION_TITLE = "Ban";
@SuppressWarnings("StaticCollection")
private static final List<String> DURATIONS = List.of(ModerationUtils.PERMANENT_DURATION,
"1 hour", "3 hours", "1 day", "2 days", "3 days", "7 days", "30 days");
Expand Down Expand Up @@ -82,24 +81,16 @@ private static RestAction<Message> handleAlreadyBanned(Guild.Ban ban, Interactio
return hook.sendMessage(message).setEphemeral(true);
}

private static RestAction<Boolean> sendDm(ISnowflake target,
@Nullable ModerationUtils.TemporaryData temporaryData, String reason, Guild guild,
GenericEvent event) {
String durationMessage =
temporaryData == null ? "permanently" : "for " + temporaryData.duration();
String dmMessage =
private static RestAction<Boolean> sendDm(User target,
@Nullable ModerationUtils.TemporaryData temporaryData, String reason, Guild guild) {
String durationMessage = temporaryData == null ? "Permanently" : temporaryData.duration();
String description =
"""
Hey there, sorry to tell you but unfortunately you have been banned %s from the server %s.
If you think this was a mistake, please contact a moderator or admin of the server.
The reason for the ban is: %s
"""
.formatted(durationMessage, guild.getName(), reason);

return event.getJDA()
.openPrivateChannelById(target.getId())
.flatMap(channel -> channel.sendMessage(dmMessage))
.mapToResult()
.map(Result::isSuccess);
Hey there, sorry to tell you but unfortunately you have been banned from the server.
If you think this was a mistake, please contact a moderator or admin of this server.""";

return ModerationUtils.sendModActionDm(ModerationUtils.getModActionEmbed(guild,
ACTION_TITLE, description, reason, durationMessage, false), target);
}

private static MessageEmbed sendFeedback(boolean hasSentDm, User target, Member author,
Expand Down Expand Up @@ -140,7 +131,7 @@ private static Optional<RestAction<Message>> handleNotAlreadyBannedResponse(
private RestAction<Message> banUserFlow(User target, Member author,
@Nullable ModerationUtils.TemporaryData temporaryData, String reason,
int deleteHistoryDays, Guild guild, SlashCommandInteractionEvent event) {
return sendDm(target, temporaryData, reason, guild, event)
return sendDm(target, temporaryData, reason, guild)
.flatMap(hasSentDm -> banUser(target, author, temporaryData, reason, deleteHistoryDays,
guild).map(banResult -> hasSentDm))
.map(hasSentDm -> sendFeedback(hasSentDm, target, author, temporaryData, reason))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,12 @@
package org.togetherjava.tjbot.features.moderation;

import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.ISnowflake;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.MessageEmbed;
import net.dv8tion.jda.api.events.GenericEvent;
import net.dv8tion.jda.api.entities.*;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import net.dv8tion.jda.api.interactions.callbacks.IReplyCallback;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.requests.RestAction;
import net.dv8tion.jda.api.requests.restaction.AuditableRestAction;
import net.dv8tion.jda.api.utils.Result;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand All @@ -36,6 +31,7 @@ public final class KickCommand extends SlashCommandAdapter {
private static final String REASON_OPTION = "reason";
private static final String COMMAND_NAME = "kick";
private static final String ACTION_VERB = "kick";
private static final String ACTION_TITLE = "Kick";
private final ModerationActionsStore actionsStore;

/**
Expand All @@ -62,27 +58,23 @@ private void kickUserFlow(Member target, Member author, String reason, Guild gui
SlashCommandInteractionEvent event) {
event.deferReply().queue();

sendDm(target, reason, guild, event)
sendDm(target.getUser(), reason, guild)
.flatMap(hasSentDm -> kickUser(target, author, reason, guild)
.map(kickResult -> hasSentDm))
.map(hasSentDm -> sendFeedback(hasSentDm, target, author, reason))
.flatMap(event.getHook()::sendMessageEmbeds)
.queue();
}

private static RestAction<Boolean> sendDm(ISnowflake target, String reason, Guild guild,
GenericEvent event) {
return event.getJDA()
.openPrivateChannelById(target.getId())
.flatMap(channel -> channel.sendMessage(
"""
Hey there, sorry to tell you but unfortunately you have been kicked from the server %s.
If you think this was a mistake, please contact a moderator or admin of the server.
The reason for the kick is: %s
"""
.formatted(guild.getName(), reason)))
.mapToResult()
.map(Result::isSuccess);
private static RestAction<Boolean> sendDm(User target, String reason, Guild guild) {
String description =
"""
Hey there, sorry to tell you but unfortunately you have been kicked from the server.
If you think this was a mistake, please contact a moderator or admin of this server.""";

return ModerationUtils.sendModActionDm(
ModerationUtils.getModActionEmbed(guild, ACTION_TITLE, description, reason, false),
target);
}

private AuditableRestAction<Void> kickUser(Member target, Member author, String reason,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.*;
import net.dv8tion.jda.api.entities.channel.concrete.PrivateChannel;
import net.dv8tion.jda.api.interactions.callbacks.IReplyCallback;
import net.dv8tion.jda.api.requests.RestAction;
import net.dv8tion.jda.api.requests.restaction.AuditableRestAction;
import net.dv8tion.jda.api.utils.Result;
import net.dv8tion.jda.internal.requests.CompletedRestAction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand All @@ -20,11 +21,8 @@
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalUnit;
import java.util.EnumSet;
import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;
import java.util.function.UnaryOperator;
import java.util.regex.Pattern;

/**
Expand Down Expand Up @@ -52,25 +50,6 @@ private ModerationUtils() {
*/
public static final Color AMBIENT_COLOR = Color.decode("#895FE8");

/**
* Actions with timely constraint, like being muted for 1 hour.
*/
private static final Set<ModerationAction> TEMPORARY_ACTIONS =
EnumSet.of(ModerationAction.MUTE);
/**
* Actions with revoking previously made actions on the user, like unmuting the user after it
* has been muted.
*/
private static final Set<ModerationAction> REVOKE_ACTIONS =
EnumSet.of(ModerationAction.UNMUTE, ModerationAction.UNQUARANTINE);
/**
* Soft violations were the user still remains member of the guild, such as a warning
*/
private static final Set<ModerationAction> SOFT_ACTIONS =
EnumSet.of(ModerationAction.WARN, ModerationAction.QUARANTINE);



/**
* Checks whether the given reason is valid. If not, it will handle the situation and respond to
* the user.
Expand Down Expand Up @@ -390,64 +369,71 @@ static Optional<TemporaryData> computeTemporaryData(String durationText) {
}

/**
* Wrapper to hold data relevant to temporary actions, for example the time it expires.
* Creates a nice looking embed for the mod action taken.
*
* @param expiresAt the time the temporary action expires
* @param duration a human-readable text representing the duration of the temporary action, such
* as {@code "1 day"}.
* @param guild the guild in which the action has been taken
* @param actionTitle the mod action as title e.g, Ban
* @param description a short description explaining the action
* @param reason reason for the action taken
* @param showModmailAdvice whether to advice on how to use the modmail command
* @return the embed
*/
record TemporaryData(Instant expiresAt, String duration) {
static RestAction<EmbedBuilder> getModActionEmbed(Guild guild, String actionTitle,
String description, String reason, boolean showModmailAdvice) {
EmbedBuilder modActionEmbed =
new EmbedBuilder().setAuthor(guild.getName(), null, guild.getIconUrl())
.setTitle(actionTitle)
.setDescription(description)
.addField("Reason", reason, false)
.setColor(ModerationUtils.AMBIENT_COLOR);

if (!showModmailAdvice) {
return new CompletedRestAction<>(guild.getJDA(), modActionEmbed);
}

return MessageUtils.mentionGlobalSlashCommand(guild.getJDA(), ModMailCommand.COMMAND_NAME)
.map(commandMention -> modActionEmbed.appendDescription(
"%n%nTo get in touch with a moderator, you can use the %s command here in this chat. Your message will then be forwarded and a moderator will get back to you soon 😊"
.formatted(commandMention)));
}

/**
* Gives out advice depending on the {@link ModerationAction} and the parameters passed into it.
* Creates a nice looking embed for the mod action taken with duration
*
* @param action the action that is being performed, such as banning a user.
* @param temporaryData if the action is a temporary action, such as a 1 hour mute.
* @param additionalDescription any extra description that should be part of the message, if
* desired
* @param guild for which the action was triggered.
* @param reason for the action.
* @param textChannel for which messages are being sent to.
*
* @return the appropriate advice.
* @param guild the guild in which the action has been taken
* @param actionTitle the mod action itself
* @param description a short description explaining the action
* @param reason reason for the action taken
* @param duration the duration of mod action
* @param showModmailAdvice whether to advice on how to use the modmail command
* @return the embed
*/
public static RestAction<Message> sendDmAdvice(ModerationAction action,
@Nullable TemporaryData temporaryData, @Nullable String additionalDescription,
Guild guild, String reason, PrivateChannel textChannel) {
String additionalDescriptionInfix =
additionalDescription == null ? "" : "\n" + additionalDescription;

if (REVOKE_ACTIONS.contains(action)) {
return textChannel.sendMessage("""
Hey there, you have been %s in the server %s.%s
The reason for being %s is: %s
""".formatted(action.getVerb(), guild.getName(), additionalDescriptionInfix,
action.getVerb(), reason));
}
String durationMessage;
if (SOFT_ACTIONS.contains(action)) {
durationMessage = "";
} else if (TEMPORARY_ACTIONS.contains(action)) {
durationMessage =
temporaryData == null ? " permanently" : " for " + temporaryData.duration();
} else {
throw new IllegalArgumentException(
"Action '%s' is not supported by this method".formatted(action));
}
static RestAction<EmbedBuilder> getModActionEmbed(Guild guild, String actionTitle,
String description, String reason, String duration, boolean showModmailAdvice) {
return getModActionEmbed(guild, actionTitle, description, reason, showModmailAdvice)
.map(embedBuilder -> embedBuilder.addField("Duration", duration, false));
}

UnaryOperator<String> createDmMessage =
commandMention -> """
Hey there, sorry to tell you but unfortunately you have been %s%s in the server %s.%s
To get in touch with a moderator, you can simply use the %s command here in this chat. \
Your message will then be forwarded and a moderator will get back to you soon 😊
The reason for being %s is: %s
"""
.formatted(action.getVerb(), durationMessage, guild.getName(),
additionalDescriptionInfix, commandMention, action.getVerb(), reason);
/**
* @param embedBuilder rest action to generate embed from
* @param target the user to send the generated embed
* @return boolean rest action, weather the dm is sent successfully
*/
static RestAction<Boolean> sendModActionDm(RestAction<EmbedBuilder> embedBuilder, User target) {
return embedBuilder.map(EmbedBuilder::build)
.flatMap(embed -> target.openPrivateChannel()
.flatMap(channel -> channel.sendMessageEmbeds(embed)))
.mapToResult()
.map(Result::isSuccess);
}

return MessageUtils.mentionGlobalSlashCommand(guild.getJDA(), ModMailCommand.COMMAND_NAME)
.map(createDmMessage)
.flatMap(textChannel::sendMessage);
/**
* Wrapper to hold data relevant to temporary actions, for example the time it expires.
*
* @param expiresAt the time the temporary action expires
* @param duration a human-readable text representing the duration of the temporary action, such
* as {@code "1 day"}.
*/
record TemporaryData(Instant expiresAt, String duration) {
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
package org.togetherjava.tjbot.features.moderation;

import net.dv8tion.jda.api.entities.*;
import net.dv8tion.jda.api.events.GenericEvent;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import net.dv8tion.jda.api.interactions.callbacks.IReplyCallback;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
import net.dv8tion.jda.api.requests.RestAction;
import net.dv8tion.jda.api.requests.restaction.AuditableRestAction;
import net.dv8tion.jda.api.utils.Result;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -38,6 +36,7 @@ public final class MuteCommand extends SlashCommandAdapter {
private static final String REASON_OPTION = "reason";
private static final String COMMAND_NAME = "mute";
private static final String ACTION_VERB = "mute";
private static final String ACTION_TITLE = "Mute";
@SuppressWarnings("StaticCollection")
private static final List<String> DURATIONS = List.of("10 minutes", "30 minutes", "1 hour",
"3 hours", "1 day", "3 days", "7 days", ModerationUtils.PERMANENT_DURATION);
Expand Down Expand Up @@ -70,16 +69,16 @@ private static void handleAlreadyMutedTarget(IReplyCallback event) {
event.reply("The user is already muted.").setEphemeral(true).queue();
}

private static RestAction<Boolean> sendDm(ISnowflake target,
@Nullable ModerationUtils.TemporaryData temporaryData, String reason, Guild guild,
GenericEvent event) {
return event.getJDA()
.openPrivateChannelById(target.getId())
.flatMap(channel -> ModerationUtils.sendDmAdvice(ModerationAction.MUTE, temporaryData,
"This means you can no longer send any messages in the server until you have been unmuted again.",
guild, reason, channel))
.mapToResult()
.map(Result::isSuccess);
private static RestAction<Boolean> sendDm(User target,
@Nullable ModerationUtils.TemporaryData temporaryData, String reason, Guild guild) {
String durationMessage = temporaryData == null ? "Permanent" : temporaryData.duration();
String description =
"""
Hey there, sorry to tell you but unfortunately you have been muted.
This means you can no longer send any messages in the server until you have been unmuted again.""";

return ModerationUtils.sendModActionDm(ModerationUtils.getModActionEmbed(guild,
ACTION_TITLE, description, reason, durationMessage, true), target);
}

private static MessageEmbed sendFeedback(boolean hasSentDm, Member target, Member author,
Expand Down Expand Up @@ -117,7 +116,7 @@ private void muteUserFlow(Member target, Member author,
SlashCommandInteractionEvent event) {
event.deferReply().queue();

sendDm(target, temporaryData, reason, guild, event)
sendDm(target.getUser(), temporaryData, reason, guild)
.flatMap(hasSentDm -> muteUser(target, author, temporaryData, reason, guild)
.map(result -> hasSentDm))
.map(hasSentDm -> sendFeedback(hasSentDm, target, author, temporaryData, reason))
Expand Down
Loading

0 comments on commit 8dc3b07

Please sign in to comment.