From befef1f61d6e41bbca60f3f9a0b3b57c0ba5fa18 Mon Sep 17 00:00:00 2001 From: Sheldan <5037282+Sheldan@users.noreply.github.com> Date: Sun, 24 Dec 2023 23:25:03 +0100 Subject: [PATCH] [AB-xxx] refactoring modmail to offer a feature mode to use threads instead of channels adding various utilities for thread channels in core fixing enable feature not showing validation messages restructuring feature mode collection to be a startup listener, because postconstruct might not have the appropriate values available, and therefore not initialize the map correctly --- .../service/ModMailThreadServiceBean.java | 170 ++++++++++++------ .../ModMailFeatureValidatorBean.java | 47 ++++- .../main/resources/modmail-config.properties | 5 + .../modmail/config/ModMailFeatureConfig.java | 7 +- .../abstracto/modmail/config/ModMailMode.java | 2 +- .../modmail/config/ModMailPostTargets.java | 6 +- .../template/ModMailClosingHeaderModel.java | 1 + .../template/ModMailNotificationModel.java | 6 +- ...ilThreadContainerValidationErrorModel.java | 23 +++ .../config/features/EnableFeature.java | 18 +- .../core/service/ChannelServiceBean.java | 39 +++- .../service/FeatureConfigServiceBean.java | 9 +- .../core/service/FeatureModeServiceBean.java | 30 ++-- .../core/service/PostTargetServiceBean.java | 12 +- .../core/models/FeatureValidationResult.java | 3 +- .../core/service/ChannelService.java | 6 + 16 files changed, 283 insertions(+), 101 deletions(-) create mode 100644 abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/model/template/ModMailThreadContainerValidationErrorModel.java diff --git a/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/service/ModMailThreadServiceBean.java b/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/service/ModMailThreadServiceBean.java index 93dbde383..c705b08fd 100644 --- a/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/service/ModMailThreadServiceBean.java +++ b/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/service/ModMailThreadServiceBean.java @@ -40,6 +40,7 @@ import dev.sheldan.abstracto.core.templating.service.TemplateService; import lombok.extern.slf4j.Slf4j; import net.dv8tion.jda.api.entities.*; import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; +import net.dv8tion.jda.api.entities.channel.concrete.ThreadChannel; import net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.exceptions.InsufficientPermissionException; @@ -56,14 +57,12 @@ import java.util.concurrent.CompletableFuture; import java.util.function.Function; import java.util.stream.Collectors; +import static dev.sheldan.abstracto.modmail.config.ModMailFeatureConfig.MOD_MAIL_CLOSING_TEXT_SYSTEM_CONFIG_KEY; + @Component @Slf4j public class ModMailThreadServiceBean implements ModMailThreadService { - /** - * The config key to use for the closing text - */ - public static final String MODMAIL_CLOSING_MESSAGE_TEXT = "modMailClosingText"; /** * The config key to use for the ID of the category to create {@link GuildMessageChannel} in */ @@ -190,11 +189,9 @@ public class ModMailThreadServiceBean implements ModMailThreadService { @Override public CompletableFuture createModMailThreadForUser(Member member, Message initialMessage, boolean userInitiated, List undoActions) { Long serverId = member.getGuild().getIdLong(); - Long categoryId = configService.getLongValue(MODMAIL_CATEGORY, serverId); + User user = member.getUser(); AServer server = serverManagementService.loadServer(member.getGuild().getIdLong()); metricService.incrementCounter(MODMAIL_THREAD_CREATED_COUNTER); - User user = member.getUser(); - log.info("Creating modmail channel for user {} in category {} on server {}.", user.getId(), categoryId, serverId); ModMailChannelNameModel model = ModMailChannelNameModel .builder() .serverId(serverId) @@ -204,12 +201,36 @@ public class ModMailThreadServiceBean implements ModMailThreadService { .currentDate(Instant.now()) .build(); String channelName = templateService.renderTemplate(TEXT_CHANNEL_NAME_TEMPLATE_KEY, model, serverId); - CompletableFuture textChannelFuture = channelService.createTextChannel(channelName, server, categoryId); - return textChannelFuture.thenCompose(channel -> { - undoActions.add(UndoActionInstance.getChannelDeleteAction(serverId, channel.getIdLong())); - return self.performModMailThreadSetup(member, initialMessage, channel, userInitiated, undoActions) - .thenCompose(unused -> CompletableFuture.completedFuture(channel)); - }); + if (featureModeService.featureModeActive(ModMailFeatureDefinition.MOD_MAIL, serverId, ModMailMode.THREAD_CONTAINER)) { + MessageToSend notificationMessageToSend = getModmailNotificationMessageToSend(member, null, serverId, false); + Optional modmailContainerOptional = postTargetService.getPostTargetChannel(ModMailPostTargets.MOD_MAIL_CONTAINER, serverId); + if(modmailContainerOptional.isEmpty()) { + throw new AbstractoRunTimeException("Modmail thread container not setup."); + } + GuildMessageChannel modmailContainer = modmailContainerOptional.get(); + Optional textChannelOptional = channelService.getTextChannelFromServerOptional(serverId, modmailContainer.getIdLong()); + if(textChannelOptional.isEmpty()) { + throw new AbstractoRunTimeException("Modmail thread container text channel not found."); + } + TextChannel textChannel = textChannelOptional.get(); + List> notificationMessage = channelService.sendMessageToSendToChannel(notificationMessageToSend, modmailContainer); + return FutureUtils.toSingleFutureGeneric(notificationMessage) + .thenCompose(unused -> channelService.createThreadWithStarterMessage(textChannel, channelName, notificationMessage.get(0).join().getIdLong())) + .thenCompose(threadChannel -> { + undoActions.add(UndoActionInstance.getChannelDeleteAction(serverId, threadChannel.getIdLong())); + return self.performModMailThreadSetup(member, initialMessage, threadChannel, userInitiated, undoActions) + .thenCompose(unused -> CompletableFuture.completedFuture(threadChannel)); + }); + } else { + Long categoryId = configService.getLongValue(MODMAIL_CATEGORY, serverId); + log.info("Creating modmail channel for user {} in category {} on server {}.", user.getId(), categoryId, serverId); + CompletableFuture textChannelFuture = channelService.createTextChannel(channelName, server, categoryId); + return textChannelFuture.thenCompose(channel -> { + undoActions.add(UndoActionInstance.getChannelDeleteAction(serverId, channel.getIdLong())); + return self.performModMailThreadSetup(member, initialMessage, channel, userInitiated, undoActions) + .thenCompose(unused -> CompletableFuture.completedFuture(channel)); + }); + } } @Transactional @@ -239,12 +260,12 @@ public class ModMailThreadServiceBean implements ModMailThreadService { * @param member The {@link Member} for which a {@link ModMailThread} is being created * @param initialMessage The {@link Message} which was sent by the user to open a thread, this is null, if the thread was opened via a command * @param channel The created {@link TextChannel} in which the mod mail thread is dealt with - * @param userInitiated Whether or not the thread was initiated by a member + * @param userInitiated Whether the thread was initiated by a member * @param undoActions The list of actions to undo, in case an exception occurs * @return A {@link CompletableFuture future} which completes when the setup is done */ @Transactional - public CompletableFuture performModMailThreadSetup(Member member, Message initialMessage, TextChannel channel, boolean userInitiated, List undoActions) { + public CompletableFuture performModMailThreadSetup(Member member, Message initialMessage, GuildMessageChannel channel, boolean userInitiated, List undoActions) { log.info("Performing modmail thread setup for channel {} for user {} in server {}. It was initiated by a user: {}.", channel.getIdLong(), member.getId(), channel.getGuild().getId(), userInitiated); CompletableFuture headerFuture = sendModMailHeader(channel, member); CompletableFuture userReplyMessage; @@ -268,7 +289,7 @@ public class ModMailThreadServiceBean implements ModMailThreadService { } @Transactional - public void setupModMailThreadInDB(Message initialMessage, TextChannel channel, Member member, Message sendMessage) { + public void setupModMailThreadInDB(Message initialMessage, GuildMessageChannel channel, Member member, Message sendMessage) { log.info("Persisting info about modmail thread {} in database.", channel.getIdLong()); AUserInAServer aUserInAServer = userInServerManagementService.loadOrCreateUser(member); ModMailThread thread = createThreadObject(channel, aUserInAServer); @@ -281,15 +302,25 @@ public class ModMailThreadServiceBean implements ModMailThreadService { /** * Sends the message containing the pings to notify the staff members to handle the opened {@link ModMailThread} * @param member The {@link FullUserInServer} which opened the thread - * @param channel The created {@link TextChannel} in which the mod mail thread is dealt with - * @return A {@link CompletableFuture future} which complets when the notification has been sent + * @param channel The created {@link GuildMessageChannel} in which the mod mail thread is dealt with + * @return A {@link CompletableFuture future} which completes when the notification has been sent */ @Transactional - public CompletableFuture sendModMailNotification(Member member, TextChannel channel) { + public CompletableFuture sendModMailNotification(Member member, GuildMessageChannel channel) { Long serverId = member.getGuild().getIdLong(); + MessageToSend messageToSend = getModmailNotificationMessageToSend(member, channel, serverId, true); + return FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(messageToSend, ModMailPostTargets.MOD_MAIL_PING, serverId)); + } + + private MessageToSend getModmailNotificationMessageToSend(Member member, GuildMessageChannel channel, Long serverId, boolean pingRole) { log.info("Sending modmail notification for new modmail thread about user {} in server {}.", member.getId(), serverId); AServer server = serverManagementService.loadServer(serverId); - List rolesToPing = modMailRoleManagementService.getRolesForServer(server); + List rolesToPing; + if(pingRole) { + rolesToPing = modMailRoleManagementService.getRolesForServer(server); + } else { + rolesToPing = new ArrayList<>(); + } log.debug("Pinging {} roles to notify about modmail thread about user {} in server {}.", rolesToPing.size(), member.getId(), serverId); ModMailNotificationModel modMailNotificationModel = ModMailNotificationModel .builder() @@ -297,29 +328,31 @@ public class ModMailThreadServiceBean implements ModMailThreadService { .roles(rolesToPing) .channel(channel) .build(); - MessageToSend messageToSend = templateService.renderEmbedTemplate("modmail_notification_message", modMailNotificationModel, channel.getGuild().getIdLong()); - return FutureUtils.toSingleFutureGeneric(postTargetService.sendEmbedInPostTarget(messageToSend, ModMailPostTargets.MOD_MAIL_PING, serverId)); + return templateService.renderEmbedTemplate("modmail_notification_message", modMailNotificationModel, serverId); } /** * Creates the instance of the {@link ModMailThread} in the database. - * @param channel The {@link TextChannel} in which the {@link ModMailThread} is being done + * @param channel The {@link GuildMessageChannel} in which the {@link ModMailThread} is being done * @param user The {@link AUserInAServer} which the thread is about * @return The created instance of {@link ModMailThread} */ - public ModMailThread createThreadObject(TextChannel channel, AUserInAServer user) { + public ModMailThread createThreadObject(GuildMessageChannel channel, AUserInAServer user) { log.info("Creating database objects related to modmail thread in channel {} and about user {} in server {}.", channel.getIdLong(), user.getUserReference().getId(), channel.getGuild().getId()); - AChannel channel2 = channelManagementService.createChannel(channel.getIdLong(), AChannelType.TEXT, user.getServerReference()); - log.info("Creating mod mail thread in channel {} with db channel {}", channel.getIdLong(), channel2.getId()); - return modMailThreadManagementService.createModMailThread(user, channel2); + boolean useThreads = featureModeService.featureModeActive(ModMailFeatureDefinition.MOD_MAIL, channel.getGuild().getIdLong(), ModMailMode.THREAD_CONTAINER); + AChannel aChannel = channelManagementService.createChannel(channel.getIdLong(), useThreads ? AChannelType.PUBLIC_THREAD : AChannelType.TEXT, user.getServerReference()); + log.info("Creating mod mail thread in channel {} with db channel {}", channel.getIdLong(), aChannel.getId()); + return modMailThreadManagementService.createModMailThread(user, aChannel); } @Override public void setModMailCategoryTo(Guild guild, Long categoryId) { log.info("Trying to set modmail category to {} in guild {}.", categoryId, guild.getId()); - FeatureValidationResult result = FeatureValidationResult.builder().build(); + FeatureValidationResult result = FeatureValidationResult + .builder() + .build(); modMailFeatureValidator.validateModMailCategory(result, guild, categoryId); - if(result.getValidationResult()) { + if(!result.getValidationResult()) { throw new ModMailCategoryIdException(categoryId); } configService.setLongValue(MODMAIL_CATEGORY, guild.getIdLong(), categoryId); @@ -416,10 +449,10 @@ public class ModMailThreadServiceBean implements ModMailThreadService { /** * Method used to send the header of a newly created mod mail thread. This message contains information about * the user which the thread is about - * @param channel The {@link TextChannel} in which the mod mail thread is present in + * @param channel The {@link GuildMessageChannel} in which the mod mail thread is present in * @param member The {@link Member} which the {@link ModMailThread} is about */ - private CompletableFuture sendModMailHeader(TextChannel channel, Member member) { + private CompletableFuture sendModMailHeader(GuildMessageChannel channel, Member member) { log.debug("Sending modmail thread header for tread in channel {} on server {}.", channel.getIdLong(), channel.getGuild().getId()); AUserInAServer aUserInAServer = userInServerManagementService.loadOrCreateUser(member); ModMailThread latestThread = modMailThreadManagementService.getLatestModMailThread(aUserInAServer); @@ -472,8 +505,8 @@ public class ModMailThreadServiceBean implements ModMailThreadService { * @param modMailThreadId The id of the modmail thread to which the received {@link Message} is a reply to, can be null, if it is null, its the initial message * @param messageFromUser The received message from the user * @param member The {@link Member} instance from the user the thread is about. It is used as author - * @param modMailThreadExists Whether or not the modmail thread already exists and is persisted. - * @return A {@link CompletableFuture} which resolves when the post processing of the message is completed (adding read notification, and storing messageIDs) + * @param modMailThreadExists Whether the modmail thread already exists and is persisted. + * @return A {@link CompletableFuture} which resolves when the postprocessing of the message is completed (adding read notification, and storing messageIDs) */ public CompletableFuture sendUserReply(GuildMessageChannel messageChannel, Long modMailThreadId, Message messageFromUser, Member member, boolean modMailThreadExists) { List> subscriberMemberFutures = new ArrayList<>(); @@ -548,7 +581,7 @@ public class ModMailThreadServiceBean implements ModMailThreadService { } /** - * This message handles the post processing of the messages received by the user. This includes: saving the messageIDs + * This message handles the postprocessing of the messages received by the user. This includes: saving the messageIDs * in the database, updating the state of the {@link ModMailThread} and adding the read reaction to the user message * @param textChannel The channel in which the message * @param messageInModMailThread The actual {@link Message} instance which was sent to the mod mail thread @@ -640,22 +673,31 @@ public class ModMailThreadServiceBean implements ModMailThreadService { List modMailMessages = modMailThread.getMessages(); Long userId = modMailThread.getUser().getUserReference().getId(); Long serverId = modMailThread.getServer().getId(); - if(closingConfig.getLog()) { - if(!modMailMessages.isEmpty()) { - return modMailMessageService.loadModMailMessages(modMailMessages) - .thenAccept(loadedModmailThreadMessages -> self.logMessagesToModMailLog(closingConfig, modMailThreadId, undoActions, loadedModmailThreadMessages, serverId, userId)); - } else { - log.info("Modmail thread {} in server {} has no messages. Only logging header.", modMailThreadId, serverId); - return loadUserAndSendClosingHeader(modMailThread, closingConfig) - .thenAccept(unused -> memberService.getMemberInServerAsync(modMailThread.getUser()).thenCompose(member -> - self.afterSuccessfulLog(modMailThreadId, closingConfig.getNotifyUser(), member, undoActions) - )); - } + if (featureModeService.featureModeActive(ModMailFeatureDefinition.MOD_MAIL, serverId, ModMailMode.THREAD_CONTAINER)) { + ThreadChannel threadChannel = channelService.getThreadChannel(modMailThread.getChannel().getId()); + log.info("Archiving thread {} for modmail thread closing.", modMailThread.getChannel().getId()); + return loadUserAndSendClosingHeader(modMailThread, closingConfig) + .thenCompose(unused -> channelService.archiveThreadChannel(threadChannel)) + .thenCompose(unused -> memberService.getMemberInServerAsync(serverId, userId)) + .thenAccept(member -> self.afterSuccessfulLog(modMailThreadId, closingConfig.getNotifyUser(), member, undoActions)); } else { - log.debug("Not logging modmail thread {}.", modMailThreadId); - return memberService.getMemberInServerAsync(modMailThread.getUser()).thenCompose(member -> - self.afterSuccessfulLog(modMailThreadId, closingConfig.getNotifyUser(), member, undoActions) - ); + if(closingConfig.getLog()) { + if(!modMailMessages.isEmpty()) { + return modMailMessageService.loadModMailMessages(modMailMessages) + .thenAccept(loadedModmailThreadMessages -> self.logMessagesToModMailLog(closingConfig, modMailThreadId, undoActions, loadedModmailThreadMessages, serverId, userId)); + } else { + log.info("Modmail thread {} in server {} has no messages. Only logging header.", modMailThreadId, serverId); + return loadUserAndSendClosingHeader(modMailThread, closingConfig) + .thenAccept(unused -> memberService.getMemberInServerAsync(modMailThread.getUser()).thenCompose(member -> + self.afterSuccessfulLog(modMailThreadId, closingConfig.getNotifyUser(), member, undoActions) + )); + } + } else { + log.debug("Not logging modmail thread {}.", modMailThreadId); + return memberService.getMemberInServerAsync(modMailThread.getUser()).thenCompose(member -> + self.afterSuccessfulLog(modMailThreadId, closingConfig.getNotifyUser(), member, undoActions) + ); + } } } @@ -711,7 +753,7 @@ public class ModMailThreadServiceBean implements ModMailThreadService { * This message is executed after the thread has been logged and notifies the user about the closed {@link ModMailThread} * which a configurable closing text. This method then calls the method to delete the channel. * @param modMailThreadId The ID of the {@link ModMailThread} which is being closed. - * @param notifyUser Whether or not the user should be notified + * @param notifyUser Whether the user should be notified * @param undoActions The list of {@link UndoActionInstance} to execute in case of exceptions * @param modMailThreaduser The {@link Member member} for which the {@link ModMailThread thread} was for * @throws ModMailThreadNotFoundException in case the {@link ModMailThread} is not found by the ID @@ -727,7 +769,7 @@ public class ModMailThreadServiceBean implements ModMailThreadService { ModMailThread modMailThread = modMailThreadOpt.get(); HashMap closingMessage = new HashMap<>(); String defaultValue = templateService.renderSimpleTemplate("modmail_closing_user_message_description"); - closingMessage.put("closingMessage", configService.getStringValue(MODMAIL_CLOSING_MESSAGE_TEXT, modMailThread.getServer().getId(), defaultValue)); + closingMessage.put("closingMessage", configService.getStringValue(MOD_MAIL_CLOSING_TEXT_SYSTEM_CONFIG_KEY, modMailThread.getServer().getId(), defaultValue)); return messageService.sendEmbedToUser(modMailThreaduser.getUser(), "modmail_closing_user_message", closingMessage).thenAccept(message -> self.deleteChannelAndClose(modMailThreadId, undoActions) ).exceptionally(throwable -> { @@ -758,11 +800,17 @@ public class ModMailThreadServiceBean implements ModMailThreadService { ModMailThread modMailThread = modMailThreadOpt.get(); String failureMessage = "Failed to delete text channel containing mod mail thread {}"; try { - log.debug("Deleting channel {} which contained the modmail thread {}.", modMailThread.getChannel().getId(), modMailThreadId); - return channelService.deleteTextChannel(modMailThread.getChannel()).thenAccept(avoid -> { + if (featureModeService.featureModeActive(ModMailFeatureDefinition.MOD_MAIL, modMailThread.getServer().getId(), ModMailMode.THREAD_CONTAINER)) { undoActions.clear(); self.closeModMailThreadInDb(modMailThreadId); - }); + return CompletableFuture.completedFuture(null); + } else { + log.debug("Deleting channel {} which contained the modmail thread {}.", modMailThread.getChannel().getId(), modMailThreadId); + return channelService.deleteTextChannel(modMailThread.getChannel()).thenAccept(avoid -> { + undoActions.clear(); + self.closeModMailThreadInDb(modMailThreadId); + }); + } } catch (InsufficientPermissionException ex) { log.error(failureMessage, modMailThreadId, ex); String message = "Failed To delete mod mail thread channel because no permissions."; @@ -864,16 +912,22 @@ public class ModMailThreadServiceBean implements ModMailThreadService { .serverId(modMailThread.getServer().getId()) .userId(modMailThread.getUser().getUserReference().getId()) .build(); + Long modmailThreadId = modMailThread.getId(); return userService.retrieveUserForId(modMailThread.getUser().getUserReference().getId()).thenApply(user -> { headerModel.setUser(user); - return self.sendClosingHeader(headerModel).get(0); + return self.sendClosingHeader(headerModel, modmailThreadId).get(0); }).thenCompose(Function.identity()); } @Transactional - public List> sendClosingHeader(ModMailClosingHeaderModel model) { + public List> sendClosingHeader(ModMailClosingHeaderModel model, Long modmailThreadId) { MessageToSend messageToSend = templateService.renderEmbedTemplate("modmail_close_header", model, model.getServerId()); - return postTargetService.sendEmbedInPostTarget(messageToSend, ModMailPostTargets.MOD_MAIL_LOG, model.getServerId()); + if (featureModeService.featureModeActive(ModMailFeatureDefinition.MOD_MAIL, model.getServerId(), ModMailMode.THREAD_CONTAINER)) { + ModMailThread modMailThread = modMailThreadManagementService.getById(modmailThreadId); + return channelService.sendMessageEmbedToSendToAChannel(messageToSend, modMailThread.getChannel()); + } else { + return postTargetService.sendEmbedInPostTarget(messageToSend, ModMailPostTargets.MOD_MAIL_LOG, model.getServerId()); + } } /** @@ -899,7 +953,7 @@ public class ModMailThreadServiceBean implements ModMailThreadService { * sends this to the appropriate logging {@link PostTarget} * @param modMailThreadId The ID of {@link ModMailThread} to which the loaded messages belong to * @param loadedMessages The list of {@link ModMailLoggedMessageModel} which can be rendered - * @return A list of {@link CompletableFuture} which represent each of the messages being send to the {@link PostTarget} + * @return A list of {@link CompletableFuture} which represent each of the messages being sent to the {@link PostTarget} */ public List> sendMessagesToPostTarget(Long modMailThreadId, List loadedMessages, Message updateMessage) { List> messageFutures = new ArrayList<>(); diff --git a/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/validator/ModMailFeatureValidatorBean.java b/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/validator/ModMailFeatureValidatorBean.java index dd732659d..3a224626a 100644 --- a/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/validator/ModMailFeatureValidatorBean.java +++ b/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/validator/ModMailFeatureValidatorBean.java @@ -3,21 +3,25 @@ package dev.sheldan.abstracto.modmail.validator; import dev.sheldan.abstracto.core.config.FeatureConfig; import dev.sheldan.abstracto.core.models.FeatureValidationResult; import dev.sheldan.abstracto.core.models.database.AServer; -import dev.sheldan.abstracto.core.service.ConfigService; -import dev.sheldan.abstracto.core.service.FeatureValidatorService; -import dev.sheldan.abstracto.core.service.GuildService; +import dev.sheldan.abstracto.core.service.*; +import dev.sheldan.abstracto.modmail.config.ModMailFeatureDefinition; +import dev.sheldan.abstracto.modmail.config.ModMailMode; +import dev.sheldan.abstracto.modmail.config.ModMailPostTargets; import dev.sheldan.abstracto.modmail.model.template.ModMailCategoryValidationErrorModel; +import dev.sheldan.abstracto.modmail.model.template.ModMailThreadContainerValidationErrorModel; import dev.sheldan.abstracto.modmail.service.ModMailThreadServiceBean; import lombok.extern.slf4j.Slf4j; import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.entities.channel.attribute.IThreadContainer; import net.dv8tion.jda.api.entities.channel.concrete.Category; +import net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.Optional; /** - * This component is used to validate whether or not the mod mail feature has a mod mail category configured, which points to + * This component is used to validate whether the mod mail feature has a mod mail category configured, which points to * a category on the server it is configured for. This and other {@link dev.sheldan.abstracto.core.service.FeatureValidator} * are used to fully validate the mod mail feature. */ @@ -34,8 +38,16 @@ public class ModMailFeatureValidatorBean implements ModMailFeatureValidator { @Autowired private FeatureValidatorService featureValidatorService; + @Autowired + private FeatureModeService featureModeService; + + @Autowired + private PostTargetService postTargetService; + /** - * Checks if the mod mail category contains a value and whether or not this valid also points to a {@link Category} + * Checks if the mod mail category contains a value and whether this valid also points to a {@link Category}. + * Additionally, if the thread container feature mode is already enabled. This will check if the current posttarget + * for threads can hold threads. * @param featureConfig The instance of {@link FeatureConfig} of mod mail * @param server The {@link AServer} to check the config for * @param validationResult The current {@link FeatureValidationResult} used to accumulate the wrong values @@ -52,6 +64,29 @@ public class ModMailFeatureValidatorBean implements ModMailFeatureValidator { Long modMailCategory = configService.getLongValue(ModMailThreadServiceBean.MODMAIL_CATEGORY, server.getId()); validateModMailCategory(validationResult, guild, modMailCategory); } + if (featureModeService.featureModeActive(ModMailFeatureDefinition.MOD_MAIL, server.getId(), ModMailMode.THREAD_CONTAINER)) { + Optional modmailContainerOptional = postTargetService.getPostTargetChannel(ModMailPostTargets.MOD_MAIL_CONTAINER, server.getId()); + if(modmailContainerOptional.isEmpty()) { + ModMailThreadContainerValidationErrorModel newError = ModMailThreadContainerValidationErrorModel + .builder() + .currentChannelId(null) + .build(); + validationResult.getValidationErrorModels().add(newError); + validationResult.setValidationResult(false); + } else { + GuildMessageChannel threadContainer = modmailContainerOptional.get(); + if(!(threadContainer instanceof IThreadContainer)) { + validationResult.setValidationResult(false); + ModMailThreadContainerValidationErrorModel newError = ModMailThreadContainerValidationErrorModel + .builder() + .currentChannelId(threadContainer.getIdLong()) + .build(); + validationResult.getValidationErrorModels().add(newError); + } else { + validationResult.setValidationResult(true); + } + } + } } } @@ -70,6 +105,8 @@ public class ModMailFeatureValidatorBean implements ModMailFeatureValidator { .currentCategoryId(modMailCategory) .build(); validationResult.getValidationErrorModels().add(newError); + } else { + validationResult.setValidationResult(true); } } } diff --git a/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/resources/modmail-config.properties b/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/resources/modmail-config.properties index 86ce2eb78..657a9c230 100644 --- a/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/resources/modmail-config.properties +++ b/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/resources/modmail-config.properties @@ -9,11 +9,16 @@ abstracto.featureFlags.modmail.enabled=false abstracto.postTargets.modmailLog.name=modmailLog abstracto.postTargets.modmailPing.name=modmailPing +abstracto.postTargets.modmailContainer.name=modmailContainer abstracto.featureModes.log.featureName=modmail abstracto.featureModes.log.mode=log abstracto.featureModes.log.enabled=true +abstracto.featureModes.threadContainer.featureName=modmail +abstracto.featureModes.threadContainer.mode=threadContainer +abstracto.featureModes.threadContainer.enabled=false + abstracto.featureModes.threadMessage.featureName=modmail abstracto.featureModes.threadMessage.mode=threadMessage abstracto.featureModes.threadMessage.enabled=true \ No newline at end of file diff --git a/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/config/ModMailFeatureConfig.java b/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/config/ModMailFeatureConfig.java index 1c47114f0..e980d8e02 100644 --- a/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/config/ModMailFeatureConfig.java +++ b/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/config/ModMailFeatureConfig.java @@ -20,6 +20,7 @@ import java.util.List; @Component public class ModMailFeatureConfig implements FeatureConfig { + public static final String MOD_MAIL_CLOSING_TEXT_SYSTEM_CONFIG_KEY = "modMailClosingText"; @Autowired private ModMailFeatureValidator modMailFeatureValidator; @@ -33,7 +34,7 @@ public class ModMailFeatureConfig implements FeatureConfig { @Override public List getRequiredPostTargets() { - return Arrays.asList(ModMailPostTargets.MOD_MAIL_PING, ModMailPostTargets.MOD_MAIL_LOG); + return Arrays.asList(ModMailPostTargets.MOD_MAIL_PING, ModMailPostTargets.MOD_MAIL_LOG, ModMailPostTargets.MOD_MAIL_CONTAINER); } @Override @@ -48,12 +49,12 @@ public class ModMailFeatureConfig implements FeatureConfig { @Override public List getAvailableModes() { - return Arrays.asList(ModMailMode.LOGGING, ModMailMode.SEPARATE_MESSAGE); + return Arrays.asList(ModMailMode.LOGGING, ModMailMode.SEPARATE_MESSAGE, ModMailMode.THREAD_CONTAINER); } @Override public List getRequiredSystemConfigKeys() { - return Arrays.asList("modMailClosingText"); + return Arrays.asList(MOD_MAIL_CLOSING_TEXT_SYSTEM_CONFIG_KEY); } @Override diff --git a/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/config/ModMailMode.java b/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/config/ModMailMode.java index 9469aacbe..048bf10fc 100644 --- a/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/config/ModMailMode.java +++ b/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/config/ModMailMode.java @@ -9,7 +9,7 @@ import lombok.Getter; */ @Getter public enum ModMailMode implements FeatureMode { - LOGGING("log"), SEPARATE_MESSAGE("threadMessage"); + LOGGING("log"), SEPARATE_MESSAGE("threadMessage"), THREAD_CONTAINER("threadContainer"); private final String key; diff --git a/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/config/ModMailPostTargets.java b/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/config/ModMailPostTargets.java index 76ea31b21..8798c09cc 100644 --- a/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/config/ModMailPostTargets.java +++ b/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/config/ModMailPostTargets.java @@ -12,7 +12,11 @@ public enum ModMailPostTargets implements PostTargetEnum { /** * The channel to be used to log the contents of closed mod mail threads */ - MOD_MAIL_LOG("modmailLog"); + MOD_MAIL_LOG("modmailLog"), + /** + * The thread used as a container for the modmail threads + */ + MOD_MAIL_CONTAINER("modmailContainer"); private String key; diff --git a/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/model/template/ModMailClosingHeaderModel.java b/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/model/template/ModMailClosingHeaderModel.java index 891d6dcb4..d4972a844 100644 --- a/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/model/template/ModMailClosingHeaderModel.java +++ b/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/model/template/ModMailClosingHeaderModel.java @@ -41,4 +41,5 @@ public class ModMailClosingHeaderModel { private Boolean silently; private User user; private Long serverId; + private Long modmailThreadId; } diff --git a/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/model/template/ModMailNotificationModel.java b/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/model/template/ModMailNotificationModel.java index aef7b3545..2948ca085 100644 --- a/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/model/template/ModMailNotificationModel.java +++ b/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/model/template/ModMailNotificationModel.java @@ -8,7 +8,7 @@ import lombok.Getter; import lombok.Setter; import lombok.experimental.SuperBuilder; import net.dv8tion.jda.api.entities.Member; -import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; +import net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel; import java.util.List; @@ -33,7 +33,7 @@ public class ModMailNotificationModel extends ServerContext { */ private List roles; /** - * The {@link TextChannel} in which the mod mail thread is handled + * The {@link GuildMessageChannel} in which the mod mail thread is handled */ - private TextChannel channel; + private GuildMessageChannel channel; } diff --git a/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/model/template/ModMailThreadContainerValidationErrorModel.java b/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/model/template/ModMailThreadContainerValidationErrorModel.java new file mode 100644 index 000000000..e873478e9 --- /dev/null +++ b/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/model/template/ModMailThreadContainerValidationErrorModel.java @@ -0,0 +1,23 @@ +package dev.sheldan.abstracto.modmail.model.template; + +import dev.sheldan.abstracto.core.models.ValidationErrorModel; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@Builder +public class ModMailThreadContainerValidationErrorModel implements ValidationErrorModel { + private Long currentChannelId; + + @Override + public String getTemplateName() { + return "feature_setup_modmail_thread_container_not_setup"; + } + + @Override + public Object getTemplateModel() { + return new Object(); + } +} diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/commands/config/features/EnableFeature.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/commands/config/features/EnableFeature.java index f7dff0e2f..ef997d1cf 100644 --- a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/commands/config/features/EnableFeature.java +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/commands/config/features/EnableFeature.java @@ -73,7 +73,17 @@ public class EnableFeature extends AbstractConditionableCommand { EnableFeatureResult result = enableFeature(serverId, featureKey); if(result.featureDependencies.isEmpty()) { - return CompletableFuture.completedFuture(CommandResult.fromSuccess()); + if(!result.validationResult.getValidationResult()) { + FeatureSwitchModel model = FeatureSwitchModel + .builder() + .validationText(result.validationResult.getValidationText()) + .build(); + MessageToSend messageToSend = templateService.renderEmbedTemplate(ENABLE_FEATURE_RESPONSE_TEMPLATE_KEY, model, serverId); + return FutureUtils.toSingleFutureGeneric(channelService.sendMessageToSendToChannel(messageToSend, commandContext.getChannel())) + .thenApply(message -> CommandResult.fromIgnored()); + } else { + return CompletableFuture.completedFuture(CommandResult.fromSuccess()); + } } else { List additionalFeatures = result.featureDependencies .stream() @@ -96,7 +106,11 @@ public class EnableFeature extends AbstractConditionableCommand { String featureName = slashCommandParameterService.getCommandOption(FEATURE_NAME_PARAMETER, event, String.class); EnableFeatureResult enableFeatureResult = enableFeature(event.getGuild().getIdLong(), featureName); if(enableFeatureResult.featureDependencies.isEmpty()) { - return interactionService.replyEmbed(ENABLE_FEATURE_RESPONSE_TEMPLATE_KEY, event) + FeatureSwitchModel model = FeatureSwitchModel + .builder() + .validationText(enableFeatureResult.validationResult.getValidationText()) + .build(); + return interactionService.replyEmbed(ENABLE_FEATURE_RESPONSE_TEMPLATE_KEY, model, event) .thenApply(interactionHook -> CommandResult.fromSuccess()); } else { List additionalFeatures = enableFeatureResult.featureDependencies diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/ChannelServiceBean.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/ChannelServiceBean.java index afa699365..bb69b1a9b 100644 --- a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/ChannelServiceBean.java +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/ChannelServiceBean.java @@ -19,6 +19,7 @@ import net.dv8tion.jda.api.EmbedBuilder; import net.dv8tion.jda.api.entities.*; import net.dv8tion.jda.api.entities.channel.concrete.Category; import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; +import net.dv8tion.jda.api.entities.channel.concrete.ThreadChannel; import net.dv8tion.jda.api.entities.channel.middleman.GuildChannel; import net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; @@ -196,11 +197,8 @@ public class ChannelServiceBean implements ChannelService { @Override public List> sendMessageToSendToChannel(MessageToSend messageToSend, MessageChannel textChannel) { - if(messageToSend.getEphemeral()) { - throw new IllegalArgumentException("Ephemeral messages are only supported in interaction context."); - } - if(textChannel instanceof GuildMessageChannel) { - GuildMessageChannel guildMessageChannel = (GuildMessageChannel) textChannel; + messageToSend.setEphemeral(false); + if(textChannel instanceof GuildMessageChannel guildMessageChannel) { long maxFileSize = guildMessageChannel.getGuild().getMaxFileSize(); // in this case, we cannot upload the file, so we need to fail messageToSend.getAttachedFiles().forEach(attachedFile -> { @@ -427,6 +425,16 @@ public class ChannelServiceBean implements ChannelService { return channel.editMessageComponentsById(messageId, new ArrayList<>()).submit(); } + @Override + public ThreadChannel getThreadChannel(Long threadChannelId) { + return botService.getInstance().getThreadChannelById(threadChannelId); + } + + @Override + public CompletableFuture archiveThreadChannel(ThreadChannel threadChannel) { + return threadChannel.getManager().setArchived(true).submit(); + } + @Override public CompletableFuture deleteTextChannel(AChannel channel) { return deleteTextChannel(channel.getServer().getId(), channel.getId()); @@ -434,11 +442,11 @@ public class ChannelServiceBean implements ChannelService { @Override public CompletableFuture deleteTextChannel(Long serverId, Long channelId) { - TextChannel textChannelById = botService.getInstance().getTextChannelById(channelId); - if(textChannelById != null) { + GuildChannel channelById = botService.getInstance().getGuildChannelById(channelId); + if(channelById != null) { log.info("Deleting channel {} on server {}.", channelId, serverId); metricService.incrementCounter(CHANNEL_DELETE_METRIC); - return textChannelById.delete().submit(); + return channelById.delete().submit(); } throw new ChannelNotInGuildException(channelId); } @@ -508,6 +516,21 @@ public class ChannelServiceBean implements ChannelService { throw new GuildNotFoundException(server.getId()); } + @Override + public CompletableFuture createThread(TextChannel textChannel, String name) { + return textChannel.createThreadChannel(name).submit(); + } + + @Override + public CompletableFuture createThreadWithStarterMessage(TextChannel textChannel, String name, Long messageId) { + return textChannel.createThreadChannel(name, messageId).submit(); + } + + @Override + public CompletableFuture createPrivateThread(TextChannel textChannel, String name) { + return textChannel.createThreadChannel(name, true).submit(); + } + @Override public Optional getChannelFromAChannel(AChannel channel) { return getGuildChannelFromServerOptional(channel.getServer().getId(), channel.getId()); diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/FeatureConfigServiceBean.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/FeatureConfigServiceBean.java index 5adb2ab7f..a08c4b58f 100644 --- a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/FeatureConfigServiceBean.java +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/FeatureConfigServiceBean.java @@ -53,9 +53,12 @@ public class FeatureConfigServiceBean implements FeatureConfigService { @Override public FeatureConfig getFeatureDisplayForFeature(FeatureDefinition featureDefinition) { - Optional any = getAllFeatureConfigs().stream().filter(featureDisplay -> featureDisplay.getFeature().equals(featureDefinition)).findAny(); - if(any.isPresent()) { - return any.get(); + List allFeatureConfigs = getAllFeatureConfigs(); + if(allFeatureConfigs != null) { + Optional any = allFeatureConfigs.stream().filter(featureDisplay -> featureDisplay.getFeature().equals(featureDefinition)).findAny(); + if(any.isPresent()) { + return any.get(); + } } throw new FeatureNotFoundException(featureDefinition.getKey(), getFeaturesAsList()); } diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/FeatureModeServiceBean.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/FeatureModeServiceBean.java index a0c2f7c12..a145561bf 100644 --- a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/FeatureModeServiceBean.java +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/FeatureModeServiceBean.java @@ -7,6 +7,7 @@ import dev.sheldan.abstracto.core.config.FeatureConfig; import dev.sheldan.abstracto.core.config.FeatureDefinition; import dev.sheldan.abstracto.core.config.FeatureMode; import dev.sheldan.abstracto.core.exception.FeatureModeNotFoundException; +import dev.sheldan.abstracto.core.listener.AsyncStartupListener; import dev.sheldan.abstracto.core.models.database.*; import dev.sheldan.abstracto.core.models.property.FeatureModeProperty; import dev.sheldan.abstracto.core.models.template.commands.AFeatureModeDisplay; @@ -15,17 +16,18 @@ import dev.sheldan.abstracto.core.service.management.DefaultFeatureModeManagemen import dev.sheldan.abstracto.core.service.management.FeatureFlagManagementService; import dev.sheldan.abstracto.core.service.management.FeatureModeManagementService; import dev.sheldan.abstracto.core.service.management.ServerManagementService; +import lombok.extern.slf4j.Slf4j; import net.dv8tion.jda.api.entities.Guild; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -import javax.annotation.PostConstruct; import java.util.*; import java.util.function.Consumer; import java.util.stream.Collectors; @Component -public class FeatureModeServiceBean implements FeatureModeService { +@Slf4j +public class FeatureModeServiceBean implements FeatureModeService, AsyncStartupListener { @Autowired private FeatureConfigService featureConfigService; @@ -122,7 +124,10 @@ public class FeatureModeServiceBean implements FeatureModeService { @Override public List getAllAvailableFeatureModes() { List fullFeatureModes = new ArrayList<>(); - featureConfigService.getAllFeatureConfigs().forEach(featureConfig -> fullFeatureModes.addAll(featureConfig.getAvailableModes())); + List allFeatureConfigs = featureConfigService.getAllFeatureConfigs(); + if(allFeatureConfigs != null) { + allFeatureConfigs.forEach(featureConfig -> fullFeatureModes.addAll(featureConfig.getAvailableModes())); + } return fullFeatureModes; } @@ -209,13 +214,16 @@ public class FeatureModeServiceBean implements FeatureModeService { } - @PostConstruct - public void postConstruct() { - featureConfigService.getAllFeatureConfigs().forEach(featureConfig -> { - List modes = new ArrayList<>(); - featureConfig.getAvailableModes().forEach(featureMode -> modes.add(featureMode.getKey())); - featureModes.put(featureConfig.getFeature().getKey(), modes); - }); + @Override + public void execute() { + List allFeatureConfigs = featureConfigService.getAllFeatureConfigs(); + log.info("Loading feature modes."); + if(allFeatureConfigs != null) { + allFeatureConfigs.forEach(featureConfig -> { + List modes = new ArrayList<>(); + featureConfig.getAvailableModes().forEach(featureMode -> modes.add(featureMode.getKey())); + featureModes.put(featureConfig.getFeature().getKey(), modes); + }); + } } - } diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/PostTargetServiceBean.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/PostTargetServiceBean.java index 21916e0bd..2cdae19da 100644 --- a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/PostTargetServiceBean.java +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/PostTargetServiceBean.java @@ -289,11 +289,13 @@ public class PostTargetServiceBean implements PostTargetService { public List getPostTargetsOfEnabledFeatures(AServer server) { List postTargets = new ArrayList<>(); List allFeatureConfigs = featureConfigService.getAllFeatureConfigs(); - allFeatureConfigs.forEach(featureConfig -> { - if (featureFlagService.isFeatureEnabled(featureConfig, server)) { - featureConfig.getRequiredPostTargets().forEach(postTargetEnum -> postTargets.add(postTargetEnum.getKey())); - } - }); + if(allFeatureConfigs != null) { + allFeatureConfigs.forEach(featureConfig -> { + if (featureFlagService.isFeatureEnabled(featureConfig, server)) { + featureConfig.getRequiredPostTargets().forEach(postTargetEnum -> postTargets.add(postTargetEnum.getKey())); + } + }); + } return postTargets; } diff --git a/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/models/FeatureValidationResult.java b/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/models/FeatureValidationResult.java index da4c0232f..c41f4d7b9 100644 --- a/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/models/FeatureValidationResult.java +++ b/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/models/FeatureValidationResult.java @@ -16,7 +16,8 @@ import java.util.List; public class FeatureValidationResult implements Templatable { private FeatureConfig feature; - private Boolean validationResult; + @Builder.Default + private Boolean validationResult = false; @Builder.Default private List validationErrorModels = new ArrayList<>(); private String validationText; diff --git a/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/service/ChannelService.java b/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/service/ChannelService.java index 06faa1eff..23b0b3691 100644 --- a/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/service/ChannelService.java +++ b/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/service/ChannelService.java @@ -5,6 +5,7 @@ import dev.sheldan.abstracto.core.models.database.AServer; import dev.sheldan.abstracto.core.templating.model.MessageToSend; import net.dv8tion.jda.api.entities.*; import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; +import net.dv8tion.jda.api.entities.channel.concrete.ThreadChannel; import net.dv8tion.jda.api.entities.channel.middleman.GuildChannel; import net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; @@ -43,6 +44,8 @@ public interface ChannelService { CompletableFuture editFieldValueInMessage(MessageChannel channel, Long messageId, Integer index, String newValue); CompletableFuture removeFieldFromMessage(MessageChannel channel, Long messageId, Integer index, Integer embedIndex); CompletableFuture removeComponents(MessageChannel channel, Long messageId); + ThreadChannel getThreadChannel(Long threadChannelId); + CompletableFuture archiveThreadChannel(ThreadChannel threadChannel); CompletableFuture deleteTextChannel(AChannel channel); CompletableFuture deleteTextChannel(Long serverId, Long channelId); List> sendEmbedTemplateInTextChannelList(String templateKey, Object model, MessageChannel channel); @@ -52,6 +55,9 @@ public interface ChannelService { CompletableFuture deleteMessagesInChannel(MessageChannel messageChannel, List messages); CompletableFuture createTextChannel(String name, AServer server, Long categoryId); + CompletableFuture createThread(TextChannel textChannel, String name); + CompletableFuture createThreadWithStarterMessage(TextChannel textChannel, String name, Long messageId); + CompletableFuture createPrivateThread(TextChannel textChannel, String name); Optional getChannelFromAChannel(AChannel channel); Optional getGuildMessageChannelFromAChannelOptional(AChannel channel); GuildMessageChannel getGuildMessageChannelFromAChannel(AChannel channel);