Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
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 @@ -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 =
"""
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);
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 from the server.
If you this 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.commands.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 this 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() {
*/
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 showModmail to show mod contact details or not
* @return the embed
*/
record TemporaryData(Instant expiresAt, String duration) {
static RestAction<EmbedBuilder> getModActionEmbed(Guild guild, String actionTitle,
String description, String reason, boolean showModmail) {
EmbedBuilder modActionEmbed =
new EmbedBuilder().setAuthor(guild.getName(), null, guild.getIconUrl())
.setTitle(actionTitle)
.setDescription(description)
.addField("Reason", reason, false)
.setColor(ModerationUtils.AMBIENT_COLOR);

if (!showModmail) {
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 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 showModmail to show mod contact details or not
* @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 showModmail) {
return getModActionEmbed(guild, actionTitle, description, reason, showModmail)
.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.commands.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,17 @@ 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 +117,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