Skip to content

Commit bc80839

Browse files
authored
Merge pull request #474 from danthe1st/close-restrictions
restrict closing of posts by non-original posters
2 parents 164957b + 94252f3 commit bc80839

File tree

4 files changed

+104
-28
lines changed

4 files changed

+104
-28
lines changed

src/main/java/net/discordjug/javabot/Bot.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -112,10 +112,12 @@ private void registerComponentHandlers(@NotNull ApplicationContext ctx) {
112112
List<IdMapping<StringSelectMenuHandler>> stringSelectMappings = new ArrayList<>();
113113
for (Object handler : interactionHandlers.values()) {
114114
AutoDetectableComponentHandler annotation = handler.getClass().getAnnotation(AutoDetectableComponentHandler.class);
115-
String[] keys = annotation.value();
116-
addComponentHandler(buttonMappings, keys, handler, ButtonHandler.class);
117-
addComponentHandler(modalMappings, keys, handler, ModalHandler.class);
118-
addComponentHandler(stringSelectMappings, keys, handler, StringSelectMenuHandler.class);
115+
if (annotation != null) {//superclasses are annotated, ignore
116+
String[] keys = annotation.value();
117+
addComponentHandler(buttonMappings, keys, handler, ButtonHandler.class);
118+
addComponentHandler(modalMappings, keys, handler, ModalHandler.class);
119+
addComponentHandler(stringSelectMappings, keys, handler, StringSelectMenuHandler.class);
120+
}
119121
}
120122
dih4jda.addButtonMappings(buttonMappings.toArray(IdMapping[]::new));
121123
dih4jda.addModalMappings(modalMappings.toArray(IdMapping[]::new));

src/main/java/net/discordjug/javabot/systems/help/HelpListener.java

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,6 @@ private boolean isInvalidHelpForumChannel(@NotNull ForumChannel forum) {
245245

246246
private void handleHelpThanksInteraction(@NotNull ButtonInteractionEvent event, @NotNull HelpManager manager, String @NotNull [] id) {
247247
ThreadChannel post = manager.getPostThread();
248-
HelpConfig config = botConfig.get(event.getGuild()).getHelpConfig();
249248
if (event.getUser().getIdLong() != post.getOwnerIdLong()) {
250249
Responses.warning(
251250
event,
@@ -301,16 +300,14 @@ private void handleReplyGuidelines(@NotNull IReplyCallback callback, @NotNull Fo
301300
}
302301

303302
private void handlePostClose(ButtonInteractionEvent event, @NotNull HelpManager manager) {
304-
if (manager.isForumEligibleToBeUnreserved(event)) {
305-
manager.close(event, event.getUser().getIdLong() == manager.getPostThread()
306-
.getOwnerIdLong(), null);
307-
} else {
303+
if (event.getUser().getIdLong() != manager.getPostThread().getOwnerIdLong()) {
308304
Responses.warning(
309305
event,
310-
"Could not close this post",
311-
"You're not allowed to close this post."
306+
"Sorry, but only the original poster can close this post using these buttons."
312307
)
313308
.queue();
309+
return;
314310
}
311+
manager.close(event, true, null);
315312
}
316313
}

src/main/java/net/discordjug/javabot/systems/help/HelpManager.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,9 +117,23 @@ public void close(IReplyCallback callback, boolean withHelpers, @Nullable String
117117
.queue(s -> postThread.getManager().setLocked(true).setArchived(true).queue());
118118
if (callback.getMember().getIdLong() != postThread.getOwnerIdLong() &&
119119
Boolean.parseBoolean(preferenceService.getOrCreate(postThread.getOwnerIdLong(), Preference.PRIVATE_CLOSE_NOTIFICATIONS).getState())) {
120+
120121
postThread.getOwner().getUser().openPrivateChannel()
121122
.flatMap(c -> createDMCloseInfoEmbed(callback.getMember(), postThread, reason, c))
122123
.queue(success -> {}, failure -> {});
124+
125+
botConfig.get(callback.getGuild())
126+
.getModerationConfig()
127+
.getLogChannel()
128+
.sendMessageEmbeds(new EmbedBuilder()
129+
.setTitle("Post closed by non-original poster")
130+
.setDescription("The post " + postThread.getAsMention() +
131+
" has been closed by " + callback.getMember().getAsMention() + ".\n\n" +
132+
"[Post link](" + postThread.getJumpUrl() + ")")
133+
.addField("Reason", reason, false)
134+
.setAuthor(callback.getMember().getEffectiveName(), null, callback.getMember().getEffectiveAvatarUrl())
135+
.build())
136+
.queue();
123137
}
124138
}
125139

src/main/java/net/discordjug/javabot/systems/help/commands/UnreserveCommand.java

Lines changed: 80 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,46 @@
11
package net.discordjug.javabot.systems.help.commands;
22

3+
import java.util.List;
4+
5+
import org.jetbrains.annotations.NotNull;
6+
7+
import net.discordjug.javabot.annotations.AutoDetectableComponentHandler;
38
import net.discordjug.javabot.data.config.BotConfig;
49
import net.discordjug.javabot.data.h2db.DbActions;
510
import net.discordjug.javabot.systems.help.HelpManager;
611
import net.discordjug.javabot.systems.help.dao.HelpAccountRepository;
712
import net.discordjug.javabot.systems.help.dao.HelpTransactionRepository;
813
import net.discordjug.javabot.systems.user_preferences.UserPreferenceService;
14+
import net.discordjug.javabot.util.ExceptionLogger;
915
import net.discordjug.javabot.util.Responses;
1016
import net.dv8tion.jda.api.entities.channel.ChannelType;
1117
import net.dv8tion.jda.api.entities.channel.concrete.ThreadChannel;
18+
import net.dv8tion.jda.api.entities.channel.unions.MessageChannelUnion;
19+
import net.dv8tion.jda.api.events.interaction.ModalInteractionEvent;
1220
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
13-
import net.dv8tion.jda.api.interactions.commands.CommandInteraction;
21+
import net.dv8tion.jda.api.interactions.Interaction;
22+
import net.dv8tion.jda.api.interactions.callbacks.IReplyCallback;
1423
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
1524
import net.dv8tion.jda.api.interactions.commands.OptionType;
1625
import net.dv8tion.jda.api.interactions.commands.build.Commands;
17-
18-
import org.jetbrains.annotations.NotNull;
26+
import net.dv8tion.jda.api.interactions.components.ActionRow;
27+
import net.dv8tion.jda.api.interactions.components.text.TextInput;
28+
import net.dv8tion.jda.api.interactions.components.text.TextInputStyle;
29+
import net.dv8tion.jda.api.interactions.modals.Modal;
30+
import net.dv8tion.jda.api.interactions.modals.ModalMapping;
1931
import xyz.dynxsty.dih4jda.interactions.commands.application.SlashCommand;
32+
import xyz.dynxsty.dih4jda.interactions.components.ModalHandler;
33+
import xyz.dynxsty.dih4jda.util.ComponentIdBuilder;
2034

2135
/**
2236
* A simple command that can be used inside reserved help channels to
2337
* immediately unreserve them, instead of waiting for a timeout.
2438
*/
25-
public class UnreserveCommand extends SlashCommand {
39+
@AutoDetectableComponentHandler(UnreserveCommand.UNRESERVE_ID)
40+
public class UnreserveCommand extends SlashCommand implements ModalHandler {
41+
static final String UNRESERVE_ID = "unreserve";
42+
private static final int MINIMUM_REASON_LENGTH = 11;
43+
private static final String REASON_ID = "reason";
2644
private final BotConfig botConfig;
2745
private final DbActions dbActions;
2846
private final HelpAccountRepository helpAccountRepository;
@@ -43,35 +61,80 @@ public UnreserveCommand(BotConfig botConfig, DbActions dbActions, HelpTransactio
4361
this.helpAccountRepository = helpAccountRepository;
4462
this.helpTransactionRepository = helpTransactionRepository;
4563
this.preferenceService = preferenceService;
46-
setCommandData(Commands.slash("unreserve", "Unreserves this post marking your question/issue as resolved.")
64+
setCommandData(Commands.slash(UNRESERVE_ID, "Unreserves this post marking your question/issue as resolved.")
4765
.setGuildOnly(true)
48-
.addOption(OptionType.STRING, "reason", "The reason why you're unreserving this channel", false)
66+
.addOption(OptionType.STRING, REASON_ID, "The reason why you're unreserving this channel", false)
4967
);
5068
}
5169

5270
@Override
5371
public void execute(@NotNull SlashCommandInteractionEvent event) {
72+
String reason = event.getOption(REASON_ID, null, OptionMapping::getAsString);
73+
onCloseRequest(event, event, event.getChannel(), reason, ()->{
74+
TextInput reasonInput = TextInput
75+
.create(REASON_ID, "Reason", TextInputStyle.SHORT)
76+
.setRequiredRange(MINIMUM_REASON_LENGTH, 100)
77+
.setRequired(true)
78+
.setPlaceholder(reason == null ? "Please enter the reason you are closing this post here" : reason)
79+
.build();
80+
Modal modal = Modal
81+
.create(ComponentIdBuilder.build(UNRESERVE_ID), "Close post")
82+
.addComponents(ActionRow.of(
83+
reasonInput))
84+
.build();
85+
event.replyModal(modal).queue();
86+
});
87+
}
88+
89+
@Override
90+
public void handleModal(ModalInteractionEvent event, List<ModalMapping> values) {
91+
values
92+
.stream()
93+
.filter(mapping -> REASON_ID.equals(mapping.getId()))
94+
.map(mapping -> mapping.getAsString())
95+
.filter(reason -> !isReasonInvalid(reason))
96+
.findAny()
97+
.ifPresentOrElse(reason -> {
98+
onCloseRequest(event, event, event.getChannel(), reason, ()->{
99+
Responses.error(event, "The provided reason is missing or not valid").queue();
100+
ExceptionLogger.capture(new IllegalStateException("A reason was expected but not present"), getClass().getName());
101+
});
102+
}, () -> Responses.warning(event, "A valid reason must be provided").queue());
103+
104+
}
105+
106+
private void onCloseRequest(Interaction interaction, IReplyCallback replyCallback, MessageChannelUnion channel, String reason, Runnable noReasonHandler) {
107+
ChannelType channelType = channel.getType();
54108
// check whether the channel type is either text or thread (possible forum post?)
55-
if (event.getChannelType() != ChannelType.TEXT && event.getChannelType() != ChannelType.GUILD_PUBLIC_THREAD) {
56-
replyInvalidChannel(event);
109+
if (channelType != ChannelType.TEXT && channelType != ChannelType.GUILD_PUBLIC_THREAD) {
110+
replyInvalidChannel(replyCallback);
57111
return;
58112
}
59-
ThreadChannel postThread = event.getChannel().asThreadChannel();
113+
ThreadChannel postThread = channel.asThreadChannel();
60114
if (postThread.getParentChannel().getType() != ChannelType.FORUM) {
61-
replyInvalidChannel(event);
115+
replyInvalidChannel(replyCallback);
116+
return;
62117
}
63118
HelpManager manager = new HelpManager(postThread, dbActions, botConfig, helpAccountRepository, helpTransactionRepository, preferenceService);
64-
if (manager.isForumEligibleToBeUnreserved(event.getInteraction())) {
65-
manager.close(event,
66-
event.getUser().getIdLong() == manager.getPostThread().getOwnerIdLong(),
67-
event.getOption("reason", null, OptionMapping::getAsString));
119+
if (manager.isForumEligibleToBeUnreserved(interaction)) {
120+
if (replyCallback.getUser().getIdLong() != postThread.getOwnerIdLong() && isReasonInvalid(reason)) {
121+
noReasonHandler.run();
122+
return;
123+
}
124+
manager.close(replyCallback,
125+
replyCallback.getUser().getIdLong() == manager.getPostThread().getOwnerIdLong(),
126+
reason);
68127
} else {
69-
Responses.warning(event, "Could not close this post", "You're not allowed to close this post.").queue();
128+
Responses.warning(replyCallback, "Could not close this post", "You're not allowed to close this post.").queue();
70129
}
71130
}
72131

73-
private void replyInvalidChannel(CommandInteraction interaction) {
74-
Responses.warning(interaction, "Invalid Channel",
132+
private boolean isReasonInvalid(String reason) {
133+
return reason == null || reason.length() < MINIMUM_REASON_LENGTH;
134+
}
135+
136+
private void replyInvalidChannel(IReplyCallback replyCallback) {
137+
Responses.warning(replyCallback, "Invalid Channel",
75138
"This command may only be used in either the text-channel-based help system, or in our new forum help system.")
76139
.queue();
77140
}

0 commit comments

Comments
 (0)