Skip to content

Commit 9e40327

Browse files
Report Command (#653)
* Report Command * making fields private * removing Constant, naming convention since its not a constant * Instance methods writing to static fields fix * Adding comments for constructor * Little spelling improvements, comment improvements and capitalization * Making improvements, requested * pushing before it breaks Part 2 * Pretty sure the last one Part 3 * Little small improvements part 3 * Making some improvements part 4 * missed you * Part 6 * Part 7 * cmon finish thissss * Spooky month might be over, but you still have to review my code * boo * I am ready * improved the positioning of methods and naming of variables * The deed is done. * Empty lines are erased. * Additional adjustments. * Slight changes * the id capitalization * Done for good * yes
1 parent b424c2f commit 9e40327

File tree

3 files changed

+228
-1
lines changed

3 files changed

+228
-1
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import org.togetherjava.tjbot.commands.mathcommands.wolframalpha.WolframAlphaCommand;
1616
import org.togetherjava.tjbot.commands.mediaonly.MediaOnlyChannelListener;
1717
import org.togetherjava.tjbot.commands.moderation.*;
18+
import org.togetherjava.tjbot.commands.moderation.ReportCommand;
1819
import org.togetherjava.tjbot.commands.moderation.attachment.BlacklistedAttachmentListener;
1920
import org.togetherjava.tjbot.commands.moderation.modmail.ModMailCommand;
2021
import org.togetherjava.tjbot.commands.moderation.scam.ScamBlocker;
@@ -137,6 +138,7 @@ public static Collection<Feature> createFeatures(JDA jda, Database database, Con
137138
features.add(new AskCommand(config, helpSystemHelper, database));
138139
features.add(new ModMailCommand(jda, config));
139140
features.add(new HelpThreadCommand(config, helpSystemHelper));
141+
features.add(new ReportCommand(config));
140142

141143
// Mixtures
142144
features.add(new HelpThreadOverviewUpdater(config, helpSystemHelper));
Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
package org.togetherjava.tjbot.commands.moderation;
2+
3+
import com.github.benmanes.caffeine.cache.Cache;
4+
import com.github.benmanes.caffeine.cache.Caffeine;
5+
import net.dv8tion.jda.api.EmbedBuilder;
6+
import net.dv8tion.jda.api.entities.Guild;
7+
import net.dv8tion.jda.api.entities.Message;
8+
import net.dv8tion.jda.api.entities.MessageEmbed;
9+
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
10+
import net.dv8tion.jda.api.events.interaction.ModalInteractionEvent;
11+
import net.dv8tion.jda.api.events.interaction.command.MessageContextInteractionEvent;
12+
import net.dv8tion.jda.api.interactions.InteractionHook;
13+
import net.dv8tion.jda.api.interactions.commands.build.Commands;
14+
import net.dv8tion.jda.api.interactions.components.Modal;
15+
import net.dv8tion.jda.api.interactions.components.text.TextInput;
16+
import net.dv8tion.jda.api.interactions.components.text.TextInputStyle;
17+
import net.dv8tion.jda.api.requests.restaction.MessageCreateAction;
18+
import net.dv8tion.jda.api.utils.Result;
19+
import org.slf4j.Logger;
20+
import org.slf4j.LoggerFactory;
21+
22+
import org.togetherjava.tjbot.commands.BotCommandAdapter;
23+
import org.togetherjava.tjbot.commands.CommandVisibility;
24+
import org.togetherjava.tjbot.commands.MessageContextCommand;
25+
import org.togetherjava.tjbot.commands.utils.DiscordClientAction;
26+
import org.togetherjava.tjbot.commands.utils.MessageUtils;
27+
import org.togetherjava.tjbot.config.Config;
28+
29+
import java.awt.*;
30+
import java.time.Instant;
31+
import java.time.temporal.ChronoUnit;
32+
import java.util.List;
33+
import java.util.Objects;
34+
import java.util.Optional;
35+
import java.util.concurrent.TimeUnit;
36+
import java.util.function.Predicate;
37+
import java.util.regex.Pattern;
38+
39+
40+
/**
41+
* Implements the /report command, which allows users to report a selected offensive message from
42+
* another user. The message is then forwarded to moderators in a dedicated channel given by
43+
* {@link Config#getModMailChannelPattern()}.
44+
*/
45+
public final class ReportCommand extends BotCommandAdapter implements MessageContextCommand {
46+
47+
private static final Logger logger = LoggerFactory.getLogger(ReportCommand.class);
48+
private static final String COMMAND_NAME = "report";
49+
private static final String REPORT_REASON_INPUT_ID = "reportReason";
50+
private static final int COOLDOWN_DURATION_VALUE = 3;
51+
private static final ChronoUnit COOLDOWN_DURATION_UNIT = ChronoUnit.MINUTES;
52+
private static final Color AMBIENT_COLOR = Color.BLACK;
53+
private final Cache<Long, Instant> authorToLastReportInvocation = createCooldownCache();
54+
private final Predicate<String> modMailChannelNamePredicate;
55+
private final String configModMailChannelPattern;
56+
57+
/**
58+
* Creates a new instance.
59+
*
60+
* @param config to get the channel to forward reports to
61+
*/
62+
public ReportCommand(Config config) {
63+
super(Commands.message(COMMAND_NAME), CommandVisibility.GUILD);
64+
65+
modMailChannelNamePredicate =
66+
Pattern.compile(config.getModMailChannelPattern()).asMatchPredicate();
67+
68+
configModMailChannelPattern = config.getModMailChannelPattern();
69+
}
70+
71+
private Cache<Long, Instant> createCooldownCache() {
72+
return Caffeine.newBuilder()
73+
.maximumSize(1_000)
74+
.expireAfterAccess(COOLDOWN_DURATION_VALUE, TimeUnit.of(COOLDOWN_DURATION_UNIT))
75+
.build();
76+
}
77+
78+
@Override
79+
public void onMessageContext(MessageContextInteractionEvent event) {
80+
long userID = event.getUser().getIdLong();
81+
String reportedMessageTimestamp = event.getTarget().getTimeCreated().toInstant().toString();
82+
83+
if (handleIsOnCooldown(event)) {
84+
return;
85+
}
86+
authorToLastReportInvocation.put(userID, Instant.now());
87+
88+
String reportedMessage = event.getTarget().getContentRaw();
89+
String reportedMessageID = event.getTarget().getId();
90+
String reportedMessageChannel = event.getTarget().getChannel().getId();
91+
String reportedAuthorName = event.getTarget().getAuthor().getName();
92+
String reportedAuthorAvatarURL = event.getTarget().getAuthor().getAvatarUrl();
93+
String reportedAuthorID = event.getTarget().getAuthor().getId();
94+
95+
TextInput modalTextInput = TextInput
96+
.create(REPORT_REASON_INPUT_ID, "Anonymous report to the moderators",
97+
TextInputStyle.PARAGRAPH)
98+
.setPlaceholder("Why do you want to report this message?")
99+
.setRequiredRange(3, 200)
100+
.build();
101+
102+
String reportModalComponentID = generateComponentId(reportedMessage, reportedMessageID,
103+
reportedMessageChannel, reportedMessageTimestamp, reportedAuthorName,
104+
reportedAuthorAvatarURL, reportedAuthorID);
105+
Modal reportModal = Modal.create(reportModalComponentID, "Report this to a moderator")
106+
.addActionRow(modalTextInput)
107+
.build();
108+
109+
event.replyModal(reportModal).queue();
110+
}
111+
112+
private boolean handleIsOnCooldown(MessageContextInteractionEvent event) {
113+
if (!isAuthorOnCooldown(event.getUser().getIdLong())) {
114+
return false;
115+
}
116+
event
117+
.reply("You can only report a message once per %s minutes."
118+
.formatted(COOLDOWN_DURATION_VALUE))
119+
.setEphemeral(true)
120+
.queue();
121+
return true;
122+
}
123+
124+
private boolean isAuthorOnCooldown(long userId) {
125+
return Optional.ofNullable(authorToLastReportInvocation.getIfPresent(userId))
126+
.map(sinceCommandInvoked -> sinceCommandInvoked.plus(COOLDOWN_DURATION_VALUE,
127+
COOLDOWN_DURATION_UNIT))
128+
.filter(Instant.now()::isBefore)
129+
.isPresent();
130+
}
131+
132+
@Override
133+
public void onModalSubmitted(ModalInteractionEvent event, List<String> args) {
134+
Optional<TextChannel> modMailAuditLog = handleRequireModMailChannel(event);
135+
136+
if (modMailAuditLog.isEmpty()) {
137+
return;
138+
}
139+
140+
sendModMessage(event, args, modMailAuditLog.orElseThrow());
141+
}
142+
143+
private Optional<TextChannel> handleRequireModMailChannel(ModalInteractionEvent event) {
144+
long guildID = Objects
145+
.requireNonNull(event.getGuild(),
146+
"Guild is null for ModalInteractionEvent in ReportCommand.")
147+
.getIdLong();
148+
Optional<TextChannel> modMailAuditLog = event.getJDA()
149+
.getTextChannelCache()
150+
.stream()
151+
.filter(channel -> modMailChannelNamePredicate.test(channel.getName()))
152+
.findAny();
153+
if (modMailAuditLog.isEmpty()) {
154+
event.reply(
155+
"Sorry, there was an issue sending your report to the moderators. We are investigating.")
156+
.setEphemeral(true)
157+
.queue();
158+
logger.warn(
159+
"Cannot find the designated modmail channel in server by id {} with the pattern {}",
160+
guildID, configModMailChannelPattern);
161+
}
162+
return modMailAuditLog;
163+
}
164+
165+
private MessageCreateAction createModMessage(String reportReason,
166+
ReportedMessage reportedMessage, Guild guild, TextChannel modMailAuditLog) {
167+
168+
MessageEmbed reportedMessageEmbed = new EmbedBuilder().setTitle("Report")
169+
.setDescription(MessageUtils.abbreviate(reportedMessage.content,
170+
MessageEmbed.DESCRIPTION_MAX_LENGTH))
171+
.setAuthor(reportedMessage.authorName, null, reportedMessage.authorAvatarUrl)
172+
.setTimestamp(reportedMessage.timestamp)
173+
.setColor(AMBIENT_COLOR)
174+
.build();
175+
176+
MessageEmbed reportReasonEmbed = new EmbedBuilder().setTitle("Reason")
177+
.setDescription(reportReason)
178+
.setColor(AMBIENT_COLOR)
179+
.build();
180+
return modMailAuditLog.sendMessageEmbeds(reportedMessageEmbed, reportReasonEmbed)
181+
.addActionRow(DiscordClientAction.Channels.GUILD_CHANNEL_MESSAGE.asLinkButton(
182+
"Go to Message", guild.getId(), reportedMessage.channelID, reportedMessage.id));
183+
}
184+
185+
private void sendModMessage(ModalInteractionEvent event, List<String> args,
186+
TextChannel modMailAuditLog) {
187+
Guild guild = event.getGuild();
188+
event.deferReply().setEphemeral(true).queue();
189+
190+
InteractionHook hook = event.getHook();
191+
String reportReason = event.getValue(REPORT_REASON_INPUT_ID).getAsString();
192+
193+
ReportedMessage reportedMessage = ReportedMessage.ofArgs(args);
194+
195+
createModMessage(reportReason, reportedMessage, guild, modMailAuditLog).mapToResult()
196+
.map(this::createUserReply)
197+
.flatMap(hook::editOriginal)
198+
.queue();
199+
}
200+
201+
private String createUserReply(Result<Message> result) {
202+
if (result.isFailure()) {
203+
logger.warn("Unable to forward a message report to modmail channel.",
204+
result.getFailure());
205+
return "Sorry, there was an issue sending your report to the moderators. We are investigating.";
206+
}
207+
return "Thank you for reporting this message. A moderator will take care of the matter as soon as possible.";
208+
}
209+
210+
private record ReportedMessage(String content, String id, String channelID, Instant timestamp,
211+
String authorName, String authorAvatarUrl) {
212+
static ReportedMessage ofArgs(List<String> args) {
213+
String content = args.get(0);
214+
String id = args.get(1);
215+
String channelID = args.get(2);
216+
Instant timestamp = Instant.parse(args.get(3));
217+
String authorName = args.get(4);
218+
String authorAvatarUrl = args.get(5);
219+
return new ReportedMessage(content, id, channelID, timestamp, authorName,
220+
authorAvatarUrl);
221+
}
222+
223+
}
224+
225+
}

application/src/main/java/org/togetherjava/tjbot/commands/moderation/modmail/ModMailCommand.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
/**
3636
* Implements the /modmail command, which allows users to contact a moderator within the server
3737
* which forwards messages to moderators in a dedicated channel given by
38-
* {@link Config#getModAuditLogChannelPattern()}.
38+
* {@link Config#getModMailChannelPattern()}.
3939
*/
4040

4141
public final class ModMailCommand extends SlashCommandAdapter {

0 commit comments

Comments
 (0)