Skip to content

Commit 744c29d

Browse files
Added the /warn command
1 parent 16e7060 commit 744c29d

File tree

3 files changed

+134
-0
lines changed

3 files changed

+134
-0
lines changed

application/src/main/java/org/togetherjava/tjbot/commands/Commands.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ public enum Commands {
6262
commands.add(new TagManageCommand(tagSystem));
6363
commands.add(new TagsCommand(tagSystem));
6464
commands.add(new VcActivityCommand());
65+
commands.add(new WarnCommand(actionsStore));
6566
commands.add(new KickCommand(actionsStore));
6667
commands.add(new BanCommand(actionsStore));
6768
commands.add(new UnbanCommand(actionsStore));

application/src/main/java/org/togetherjava/tjbot/commands/moderation/ModerationUtils.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,10 @@ enum Action {
341341
* When a user kicks another user.
342342
*/
343343
KICK("kicked"),
344+
/**
345+
* When a user warns another user.
346+
*/
347+
WARN("warned"),
344348
/**
345349
* When a user mutes another user.
346350
*/
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
package org.togetherjava.tjbot.commands.moderation;
2+
3+
import net.dv8tion.jda.api.entities.*;
4+
import net.dv8tion.jda.api.events.interaction.SlashCommandEvent;
5+
import net.dv8tion.jda.api.interactions.InteractionHook;
6+
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
7+
import net.dv8tion.jda.api.interactions.commands.OptionType;
8+
import net.dv8tion.jda.api.requests.RestAction;
9+
import net.dv8tion.jda.api.utils.Result;
10+
import org.jetbrains.annotations.NotNull;
11+
import org.jetbrains.annotations.Nullable;
12+
import org.slf4j.Logger;
13+
import org.slf4j.LoggerFactory;
14+
import org.togetherjava.tjbot.commands.SlashCommandAdapter;
15+
import org.togetherjava.tjbot.commands.SlashCommandVisibility;
16+
import org.togetherjava.tjbot.config.Config;
17+
18+
import java.util.Objects;
19+
import java.util.function.Predicate;
20+
import java.util.regex.Pattern;
21+
22+
23+
/**
24+
* This command can warn users. The command will also try to DM the user to inform them about the
25+
* action and the reason.
26+
* <p>
27+
* The command fails if the user triggering it is lacking permissions to either warn other users or
28+
* to warn the specific given user (for example a moderator attempting to warn an admin).
29+
*/
30+
public final class WarnCommand extends SlashCommandAdapter {
31+
private static final Logger logger = LoggerFactory.getLogger(WarnCommand.class);
32+
private static final String USER_OPTION = "user";
33+
private static final String REASON_OPTION = "reason";
34+
private static final String ACTION_VERB = "warn";
35+
private final ModerationActionsStore actionsStore;
36+
private final Predicate<String> hasRequiredRole;
37+
38+
/**
39+
* Creates a new instance.
40+
*
41+
* @param actionsStore used to store actions issued by this command
42+
*/
43+
public WarnCommand(@NotNull ModerationActionsStore actionsStore) {
44+
super("warn", "Warns the given user", SlashCommandVisibility.GUILD);
45+
46+
getData().addOption(OptionType.USER, USER_OPTION, "The user who you want to warn", true)
47+
.addOption(OptionType.STRING, REASON_OPTION, "Why you want to warn the user", true);
48+
49+
hasRequiredRole = Pattern.compile(Config.getInstance().getHeavyModerationRolePattern())
50+
.asMatchPredicate();
51+
this.actionsStore = Objects.requireNonNull(actionsStore);
52+
}
53+
54+
private @NotNull RestAction<InteractionHook> warnUserFlow(@NotNull User target,
55+
@NotNull Member author, @NotNull String reason, @NotNull Guild guild,
56+
@NotNull SlashCommandEvent event) {
57+
return dmUser(target, reason, guild, event).map(hasSentDm -> {
58+
warnUser(target, author, reason, guild);
59+
return hasSentDm;
60+
})
61+
.map(hasSentDm -> sendFeedback(hasSentDm, target, author, reason))
62+
.flatMap(event::replyEmbeds);
63+
}
64+
65+
private static @NotNull RestAction<Boolean> dmUser(@NotNull ISnowflake target,
66+
@NotNull String reason, @NotNull Guild guild, @NotNull SlashCommandEvent event) {
67+
return event.getJDA()
68+
.openPrivateChannelById(target.getId())
69+
.flatMap(channel -> channel.sendMessage(
70+
"""
71+
Hey there, sorry to tell you but unfortunately you have been warned in the server %s.
72+
If you think this was a mistake, please contact a moderator or admin of the server.
73+
The reason for the warning is: %s
74+
"""
75+
.formatted(guild.getName(), reason)))
76+
.mapToResult()
77+
.map(Result::isSuccess);
78+
}
79+
80+
private void warnUser(@NotNull User target, @NotNull Member author, @NotNull String reason,
81+
@NotNull Guild guild) {
82+
logger.info("'{}' ({}) warned the user '{}' ({}) for reason '{}'.",
83+
author.getUser().getAsTag(), author.getId(), target.getAsTag(), target.getId(),
84+
reason);
85+
86+
actionsStore.addAction(guild.getIdLong(), author.getIdLong(), target.getIdLong(),
87+
ModerationUtils.Action.WARN, null, reason);
88+
}
89+
90+
private static @NotNull MessageEmbed sendFeedback(boolean hasSentDm, @NotNull User target,
91+
@NotNull Member author, @NotNull String reason) {
92+
String dmNoticeText = "";
93+
if (!hasSentDm) {
94+
dmNoticeText = "(Unable to send them a DM.)";
95+
}
96+
return ModerationUtils.createActionResponse(author.getUser(), ModerationUtils.Action.WARN,
97+
target, dmNoticeText, reason);
98+
}
99+
100+
@Override
101+
public void onSlashCommand(@NotNull SlashCommandEvent event) {
102+
OptionMapping targetOption =
103+
Objects.requireNonNull(event.getOption(USER_OPTION), "The target is null");
104+
Member author = Objects.requireNonNull(event.getMember(), "The author is null");
105+
Guild guild = Objects.requireNonNull(event.getGuild(), "The guild is null");
106+
String reason = Objects.requireNonNull(event.getOption(REASON_OPTION), "The reason is null")
107+
.getAsString();
108+
109+
if (!handleChecks(guild.getSelfMember(), author, targetOption.getAsMember(), reason,
110+
event)) {
111+
return;
112+
}
113+
114+
warnUserFlow(targetOption.getAsUser(), author, reason, guild, event).queue();
115+
}
116+
117+
@SuppressWarnings("BooleanMethodNameMustStartWithQuestion")
118+
private boolean handleChecks(@NotNull Member bot, @NotNull Member author,
119+
@Nullable Member target, String reason, @NotNull SlashCommandEvent event) {
120+
if (target != null && !ModerationUtils.handleCanInteractWithTarget(ACTION_VERB, bot, author,
121+
target, event)) {
122+
return false;
123+
}
124+
if (!ModerationUtils.handleHasAuthorRole(ACTION_VERB, hasRequiredRole, author, event)) {
125+
return false;
126+
}
127+
return ModerationUtils.handleReason(reason, event);
128+
}
129+
}

0 commit comments

Comments
 (0)