diff --git a/src/main/java/net/discordjug/javabot/Bot.java b/src/main/java/net/discordjug/javabot/Bot.java index 8b75b1c85..29f478169 100644 --- a/src/main/java/net/discordjug/javabot/Bot.java +++ b/src/main/java/net/discordjug/javabot/Bot.java @@ -112,10 +112,12 @@ private void registerComponentHandlers(@NotNull ApplicationContext ctx) { List> stringSelectMappings = new ArrayList<>(); for (Object handler : interactionHandlers.values()) { AutoDetectableComponentHandler annotation = handler.getClass().getAnnotation(AutoDetectableComponentHandler.class); - String[] keys = annotation.value(); - addComponentHandler(buttonMappings, keys, handler, ButtonHandler.class); - addComponentHandler(modalMappings, keys, handler, ModalHandler.class); - addComponentHandler(stringSelectMappings, keys, handler, StringSelectMenuHandler.class); + if (annotation != null) {//superclasses are annotated, ignore + String[] keys = annotation.value(); + addComponentHandler(buttonMappings, keys, handler, ButtonHandler.class); + addComponentHandler(modalMappings, keys, handler, ModalHandler.class); + addComponentHandler(stringSelectMappings, keys, handler, StringSelectMenuHandler.class); + } } dih4jda.addButtonMappings(buttonMappings.toArray(IdMapping[]::new)); dih4jda.addModalMappings(modalMappings.toArray(IdMapping[]::new)); diff --git a/src/main/java/net/discordjug/javabot/systems/help/HelpListener.java b/src/main/java/net/discordjug/javabot/systems/help/HelpListener.java index d59aa949c..bef125a9c 100644 --- a/src/main/java/net/discordjug/javabot/systems/help/HelpListener.java +++ b/src/main/java/net/discordjug/javabot/systems/help/HelpListener.java @@ -245,7 +245,6 @@ private boolean isInvalidHelpForumChannel(@NotNull ForumChannel forum) { private void handleHelpThanksInteraction(@NotNull ButtonInteractionEvent event, @NotNull HelpManager manager, String @NotNull [] id) { ThreadChannel post = manager.getPostThread(); - HelpConfig config = botConfig.get(event.getGuild()).getHelpConfig(); if (event.getUser().getIdLong() != post.getOwnerIdLong()) { Responses.warning( event, @@ -301,16 +300,14 @@ private void handleReplyGuidelines(@NotNull IReplyCallback callback, @NotNull Fo } private void handlePostClose(ButtonInteractionEvent event, @NotNull HelpManager manager) { - if (manager.isForumEligibleToBeUnreserved(event)) { - manager.close(event, event.getUser().getIdLong() == manager.getPostThread() - .getOwnerIdLong(), null); - } else { + if (event.getUser().getIdLong() != manager.getPostThread().getOwnerIdLong()) { Responses.warning( event, - "Could not close this post", - "You're not allowed to close this post." + "Sorry, but only the original poster can close this post using these buttons." ) .queue(); + return; } + manager.close(event, true, null); } } \ No newline at end of file diff --git a/src/main/java/net/discordjug/javabot/systems/help/HelpManager.java b/src/main/java/net/discordjug/javabot/systems/help/HelpManager.java index ca21a5abe..217f4406a 100644 --- a/src/main/java/net/discordjug/javabot/systems/help/HelpManager.java +++ b/src/main/java/net/discordjug/javabot/systems/help/HelpManager.java @@ -117,9 +117,23 @@ public void close(IReplyCallback callback, boolean withHelpers, @Nullable String .queue(s -> postThread.getManager().setLocked(true).setArchived(true).queue()); if (callback.getMember().getIdLong() != postThread.getOwnerIdLong() && Boolean.parseBoolean(preferenceService.getOrCreate(postThread.getOwnerIdLong(), Preference.PRIVATE_CLOSE_NOTIFICATIONS).getState())) { + postThread.getOwner().getUser().openPrivateChannel() .flatMap(c -> createDMCloseInfoEmbed(callback.getMember(), postThread, reason, c)) .queue(success -> {}, failure -> {}); + + botConfig.get(callback.getGuild()) + .getModerationConfig() + .getLogChannel() + .sendMessageEmbeds(new EmbedBuilder() + .setTitle("Post closed by non-original poster") + .setDescription("The post " + postThread.getAsMention() + + " has been closed by " + callback.getMember().getAsMention() + ".\n\n" + + "[Post link](" + postThread.getJumpUrl() + ")") + .addField("Reason", reason, false) + .setAuthor(callback.getMember().getEffectiveName(), null, callback.getMember().getEffectiveAvatarUrl()) + .build()) + .queue(); } } diff --git a/src/main/java/net/discordjug/javabot/systems/help/commands/UnreserveCommand.java b/src/main/java/net/discordjug/javabot/systems/help/commands/UnreserveCommand.java index 95b7225a6..19c522df1 100644 --- a/src/main/java/net/discordjug/javabot/systems/help/commands/UnreserveCommand.java +++ b/src/main/java/net/discordjug/javabot/systems/help/commands/UnreserveCommand.java @@ -1,28 +1,46 @@ package net.discordjug.javabot.systems.help.commands; +import java.util.List; + +import org.jetbrains.annotations.NotNull; + +import net.discordjug.javabot.annotations.AutoDetectableComponentHandler; import net.discordjug.javabot.data.config.BotConfig; import net.discordjug.javabot.data.h2db.DbActions; import net.discordjug.javabot.systems.help.HelpManager; import net.discordjug.javabot.systems.help.dao.HelpAccountRepository; import net.discordjug.javabot.systems.help.dao.HelpTransactionRepository; import net.discordjug.javabot.systems.user_preferences.UserPreferenceService; +import net.discordjug.javabot.util.ExceptionLogger; import net.discordjug.javabot.util.Responses; import net.dv8tion.jda.api.entities.channel.ChannelType; import net.dv8tion.jda.api.entities.channel.concrete.ThreadChannel; +import net.dv8tion.jda.api.entities.channel.unions.MessageChannelUnion; +import net.dv8tion.jda.api.events.interaction.ModalInteractionEvent; import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; -import net.dv8tion.jda.api.interactions.commands.CommandInteraction; +import net.dv8tion.jda.api.interactions.Interaction; +import net.dv8tion.jda.api.interactions.callbacks.IReplyCallback; import net.dv8tion.jda.api.interactions.commands.OptionMapping; import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.interactions.commands.build.Commands; - -import org.jetbrains.annotations.NotNull; +import net.dv8tion.jda.api.interactions.components.ActionRow; +import net.dv8tion.jda.api.interactions.components.text.TextInput; +import net.dv8tion.jda.api.interactions.components.text.TextInputStyle; +import net.dv8tion.jda.api.interactions.modals.Modal; +import net.dv8tion.jda.api.interactions.modals.ModalMapping; import xyz.dynxsty.dih4jda.interactions.commands.application.SlashCommand; +import xyz.dynxsty.dih4jda.interactions.components.ModalHandler; +import xyz.dynxsty.dih4jda.util.ComponentIdBuilder; /** * A simple command that can be used inside reserved help channels to * immediately unreserve them, instead of waiting for a timeout. */ -public class UnreserveCommand extends SlashCommand { +@AutoDetectableComponentHandler(UnreserveCommand.UNRESERVE_ID) +public class UnreserveCommand extends SlashCommand implements ModalHandler { + static final String UNRESERVE_ID = "unreserve"; + private static final int MINIMUM_REASON_LENGTH = 11; + private static final String REASON_ID = "reason"; private final BotConfig botConfig; private final DbActions dbActions; private final HelpAccountRepository helpAccountRepository; @@ -43,35 +61,80 @@ public UnreserveCommand(BotConfig botConfig, DbActions dbActions, HelpTransactio this.helpAccountRepository = helpAccountRepository; this.helpTransactionRepository = helpTransactionRepository; this.preferenceService = preferenceService; - setCommandData(Commands.slash("unreserve", "Unreserves this post marking your question/issue as resolved.") + setCommandData(Commands.slash(UNRESERVE_ID, "Unreserves this post marking your question/issue as resolved.") .setGuildOnly(true) - .addOption(OptionType.STRING, "reason", "The reason why you're unreserving this channel", false) + .addOption(OptionType.STRING, REASON_ID, "The reason why you're unreserving this channel", false) ); } @Override public void execute(@NotNull SlashCommandInteractionEvent event) { + String reason = event.getOption(REASON_ID, null, OptionMapping::getAsString); + onCloseRequest(event, event, event.getChannel(), reason, ()->{ + TextInput reasonInput = TextInput + .create(REASON_ID, "Reason", TextInputStyle.SHORT) + .setRequiredRange(MINIMUM_REASON_LENGTH, 100) + .setRequired(true) + .setPlaceholder(reason == null ? "Please enter the reason you are closing this post here" : reason) + .build(); + Modal modal = Modal + .create(ComponentIdBuilder.build(UNRESERVE_ID), "Close post") + .addComponents(ActionRow.of( + reasonInput)) + .build(); + event.replyModal(modal).queue(); + }); + } + + @Override + public void handleModal(ModalInteractionEvent event, List values) { + values + .stream() + .filter(mapping -> REASON_ID.equals(mapping.getId())) + .map(mapping -> mapping.getAsString()) + .filter(reason -> !isReasonInvalid(reason)) + .findAny() + .ifPresentOrElse(reason -> { + onCloseRequest(event, event, event.getChannel(), reason, ()->{ + Responses.error(event, "The provided reason is missing or not valid").queue(); + ExceptionLogger.capture(new IllegalStateException("A reason was expected but not present"), getClass().getName()); + }); + }, () -> Responses.warning(event, "A valid reason must be provided").queue()); + + } + + private void onCloseRequest(Interaction interaction, IReplyCallback replyCallback, MessageChannelUnion channel, String reason, Runnable noReasonHandler) { + ChannelType channelType = channel.getType(); // check whether the channel type is either text or thread (possible forum post?) - if (event.getChannelType() != ChannelType.TEXT && event.getChannelType() != ChannelType.GUILD_PUBLIC_THREAD) { - replyInvalidChannel(event); + if (channelType != ChannelType.TEXT && channelType != ChannelType.GUILD_PUBLIC_THREAD) { + replyInvalidChannel(replyCallback); return; } - ThreadChannel postThread = event.getChannel().asThreadChannel(); + ThreadChannel postThread = channel.asThreadChannel(); if (postThread.getParentChannel().getType() != ChannelType.FORUM) { - replyInvalidChannel(event); + replyInvalidChannel(replyCallback); + return; } HelpManager manager = new HelpManager(postThread, dbActions, botConfig, helpAccountRepository, helpTransactionRepository, preferenceService); - if (manager.isForumEligibleToBeUnreserved(event.getInteraction())) { - manager.close(event, - event.getUser().getIdLong() == manager.getPostThread().getOwnerIdLong(), - event.getOption("reason", null, OptionMapping::getAsString)); + if (manager.isForumEligibleToBeUnreserved(interaction)) { + if (replyCallback.getUser().getIdLong() != postThread.getOwnerIdLong() && isReasonInvalid(reason)) { + noReasonHandler.run(); + return; + } + manager.close(replyCallback, + replyCallback.getUser().getIdLong() == manager.getPostThread().getOwnerIdLong(), + reason); } else { - Responses.warning(event, "Could not close this post", "You're not allowed to close this post.").queue(); + Responses.warning(replyCallback, "Could not close this post", "You're not allowed to close this post.").queue(); } } - private void replyInvalidChannel(CommandInteraction interaction) { - Responses.warning(interaction, "Invalid Channel", + private boolean isReasonInvalid(String reason) { + return reason == null || reason.length() < MINIMUM_REASON_LENGTH; + } + + private void replyInvalidChannel(IReplyCallback replyCallback) { + Responses.warning(replyCallback, "Invalid Channel", "This command may only be used in either the text-channel-based help system, or in our new forum help system.") .queue(); }