From 18929c9a016b0aa67cd6f702eb0ebf2f26ff6028 Mon Sep 17 00:00:00 2001
From: Sheldan <5037282+Sheldan@users.noreply.github.com>
Date: Sun, 4 Jul 2021 12:14:04 +0200
Subject: [PATCH] [AB-298] fixing various issues related to modmail: fixing
editing a message without messages but only embeds not possible in channel
service refactoring closing parameters to use an object instead of parameters
always adding a progress indicator to closing a modmail thread adding a
notification to contact in order to show where the thread was created fixing
configuration for category (this caused the setup to fail, because there was
no default value) and threadMessage feature modes not being correct
refactored model for closing header and added additional information
refactored modmail message logging to use the message history instead of
individually loading the messages adding nicer exception in case the mod mail
message update failed adding creation of AUserInAServer in case the user did
not interact on the server yet changed ID of modmail thread to be identical
to the channel it was created in, this is so we can load the channel easier
---
.../link-embed/link-embed-impl/pom.xml | 5 -
...ssageEmbedDeleteButtonClickedListener.java | 4 -
.../abstracto/modmail/command/Close.java | 15 +-
.../abstracto/modmail/command/CloseNoLog.java | 93 -----
.../modmail/command/CloseSilently.java | 10 +-
.../ModMailMessageEditedListener.java | 8 +-
.../service/ModMailMessageServiceBean.java | 181 ++++++---
.../service/ModMailThreadServiceBean.java | 348 +++++++++++-------
.../ModMailThreadManagementServiceBean.java | 1 +
.../setup/ModMailCategoryDelayedAction.java | 2 +-
.../1.0-modmail/modMail-seedData/command.xml | 5 -
.../main/resources/modmail-config.properties | 5 +-
.../modmail/model/ClosingContext.java | 16 +
.../modmail/model/database/ModMailThread.java | 1 -
.../model/dto/LoadedModmailThreadMessage.java | 17 -
.../dto/LoadedModmailThreadMessageList.java | 24 --
.../model/template/ClosingProgressModel.java | 13 +
.../template/ContactNotificationModel.java | 15 +
.../template/ModMailClosingHeaderModel.java | 14 +-
.../template/ModMailLoggedMessageModel.java | 7 +-
.../model/template/ModMailUserReplyModel.java | 5 +-
.../ModmailLoggingThreadMessages.java | 17 +
.../service/ModMailMessageService.java | 14 +-
.../modmail/service/ModMailThreadService.java | 13 +-
.../core/service/ChannelServiceBean.java | 2 +-
.../core/service/MessageServiceBean.java | 6 +
.../core/service/MessageService.java | 1 +
.../main/docs/asciidoc/modules/modmail.adoc | 4 -
28 files changed, 483 insertions(+), 363 deletions(-)
delete mode 100644 abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/command/CloseNoLog.java
create mode 100644 abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/model/ClosingContext.java
delete mode 100644 abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/model/dto/LoadedModmailThreadMessage.java
delete mode 100644 abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/model/dto/LoadedModmailThreadMessageList.java
create mode 100644 abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/model/template/ClosingProgressModel.java
create mode 100644 abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/model/template/ContactNotificationModel.java
create mode 100644 abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/model/template/ModmailLoggingThreadMessages.java
diff --git a/abstracto-application/abstracto-modules/link-embed/link-embed-impl/pom.xml b/abstracto-application/abstracto-modules/link-embed/link-embed-impl/pom.xml
index c5146d884..8c224834a 100644
--- a/abstracto-application/abstracto-modules/link-embed/link-embed-impl/pom.xml
+++ b/abstracto-application/abstracto-modules/link-embed/link-embed-impl/pom.xml
@@ -44,11 +44,6 @@
${project.version}
-
- com.google.code.gson
- gson
-
-
dev.sheldan.abstracto.core
metrics-int
diff --git a/abstracto-application/abstracto-modules/link-embed/link-embed-impl/src/main/java/dev/sheldan/abstracto/linkembed/listener/MessageEmbedDeleteButtonClickedListener.java b/abstracto-application/abstracto-modules/link-embed/link-embed-impl/src/main/java/dev/sheldan/abstracto/linkembed/listener/MessageEmbedDeleteButtonClickedListener.java
index 693efc925..7b851639d 100644
--- a/abstracto-application/abstracto-modules/link-embed/link-embed-impl/src/main/java/dev/sheldan/abstracto/linkembed/listener/MessageEmbedDeleteButtonClickedListener.java
+++ b/abstracto-application/abstracto-modules/link-embed/link-embed-impl/src/main/java/dev/sheldan/abstracto/linkembed/listener/MessageEmbedDeleteButtonClickedListener.java
@@ -1,6 +1,5 @@
package dev.sheldan.abstracto.linkembed.listener;
-import com.google.gson.Gson;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.listener.ButtonClickedListenerResult;
import dev.sheldan.abstracto.core.listener.async.jda.ButtonClickedListener;
@@ -26,9 +25,6 @@ import org.springframework.transaction.annotation.Transactional;
@Slf4j
public class MessageEmbedDeleteButtonClickedListener implements ButtonClickedListener {
- @Autowired
- private Gson gson;
-
@Autowired
private MessageService messageService;
diff --git a/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/command/Close.java b/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/command/Close.java
index 842f0b948..2e2057938 100644
--- a/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/command/Close.java
+++ b/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/command/Close.java
@@ -12,10 +12,12 @@ import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.service.management.ChannelManagementService;
import dev.sheldan.abstracto.modmail.condition.ModMailContextCondition;
import dev.sheldan.abstracto.modmail.config.ModMailFeatureDefinition;
+import dev.sheldan.abstracto.modmail.model.ClosingContext;
import dev.sheldan.abstracto.modmail.model.database.ModMailThread;
import dev.sheldan.abstracto.modmail.service.ModMailThreadService;
import dev.sheldan.abstracto.modmail.service.management.ModMailThreadManagementService;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
+import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@@ -29,8 +31,10 @@ import java.util.concurrent.CompletableFuture;
* This command takes an optional parameter, the note, which will be replaced with a default value, if not present
*/
@Component
+@Slf4j
public class Close extends AbstractConditionableCommand {
+ public static final String MODMAIL_CLOSE_DEFAULT_NOTE_TEMPLATE_KEY = "modmail_close_default_note";
@Autowired
private ModMailContextCondition requiresModMailCondition;
@@ -50,10 +54,17 @@ public class Close extends AbstractConditionableCommand {
public CompletableFuture executeAsync(CommandContext commandContext) {
List parameters = commandContext.getParameters().getParameters();
// the default value of the note is configurable via template
- String note = parameters.size() == 1 ? (String) parameters.get(0) : templateService.renderTemplate("modmail_close_default_note", new Object());
+ String note = parameters.size() == 1 ? (String) parameters.get(0) : templateService.renderTemplate(MODMAIL_CLOSE_DEFAULT_NOTE_TEMPLATE_KEY, new Object());
AChannel channel = channelManagementService.loadChannel(commandContext.getChannel());
ModMailThread thread = modMailThreadManagementService.getByChannel(channel);
- return modMailThreadService.closeModMailThread(thread, note, true, commandContext.getUndoActions(), true)
+ ClosingContext context = ClosingContext
+ .builder()
+ .closingMember(commandContext.getAuthor())
+ .notifyUser(true)
+ .log(true)
+ .note(note)
+ .build();
+ return modMailThreadService.closeModMailThread(thread, context, commandContext.getUndoActions())
.thenApply(aVoid -> CommandResult.fromIgnored());
}
diff --git a/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/command/CloseNoLog.java b/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/command/CloseNoLog.java
deleted file mode 100644
index 8651383d4..000000000
--- a/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/command/CloseNoLog.java
+++ /dev/null
@@ -1,93 +0,0 @@
-package dev.sheldan.abstracto.modmail.command;
-
-import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand;
-import dev.sheldan.abstracto.core.command.condition.CommandCondition;
-import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
-import dev.sheldan.abstracto.core.command.config.HelpInfo;
-import dev.sheldan.abstracto.core.command.execution.CommandContext;
-import dev.sheldan.abstracto.core.command.execution.CommandResult;
-import dev.sheldan.abstracto.core.config.FeatureDefinition;
-import dev.sheldan.abstracto.core.config.FeatureMode;
-import dev.sheldan.abstracto.core.models.database.AChannel;
-import dev.sheldan.abstracto.core.service.management.ChannelManagementService;
-import dev.sheldan.abstracto.modmail.condition.ModMailContextCondition;
-import dev.sheldan.abstracto.modmail.config.ModMailFeatureConfig;
-import dev.sheldan.abstracto.modmail.config.ModMailFeatureDefinition;
-import dev.sheldan.abstracto.modmail.config.ModMailMode;
-import dev.sheldan.abstracto.modmail.model.database.ModMailThread;
-import dev.sheldan.abstracto.modmail.service.ModMailThreadService;
-import dev.sheldan.abstracto.modmail.service.management.ModMailThreadManagementService;
-import dev.sheldan.abstracto.core.templating.service.TemplateService;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Component;
-
-import java.util.Arrays;
-import java.util.List;
-import java.util.concurrent.CompletableFuture;
-
-/**
- * This command closes a mod mail thread without logging the closing and the contents of the {@link ModMailThread}.
- * This command is only available if the server has the {@link ModMailFeatureConfig}
- * 'LOGGING' mode enabled, because else the normal close command behaves the same way.
- */
-@Component
-public class CloseNoLog extends AbstractConditionableCommand {
-
- @Autowired
- private ModMailContextCondition requiresModMailCondition;
-
- @Autowired
- private ModMailThreadManagementService modMailThreadManagementService;
-
- @Autowired
- private ModMailThreadService modMailThreadService;
-
- @Autowired
- private TemplateService templateService;
-
- @Autowired
- private ChannelManagementService channelManagementService;
-
- @Override
- public CompletableFuture executeAsync(CommandContext commandContext) {
- AChannel channel = channelManagementService.loadChannel(commandContext.getChannel());
- ModMailThread thread = modMailThreadManagementService.getByChannel(channel);
- // we don't have a note, therefore we cant pass any, the method handles this accordingly
- return modMailThreadService.closeModMailThread(thread, null, false, commandContext.getUndoActions(), false)
- .thenApply(aVoid -> CommandResult.fromIgnored());
- }
-
- @Override
- public CommandConfiguration getConfiguration() {
- HelpInfo helpInfo = HelpInfo.builder().templated(true).build();
- return CommandConfiguration.builder()
- .name("closeNoLog")
- .module(ModMailModuleDefinition.MODMAIL)
- .async(true)
- .help(helpInfo)
- .supportsEmbedException(true)
- .templated(true)
- .causesReaction(true)
- .build();
- }
-
- @Override
- public FeatureDefinition getFeature() {
- return ModMailFeatureDefinition.MOD_MAIL;
- }
-
- @Override
- public List getConditions() {
- List conditions = super.getConditions();
- conditions.add(requiresModMailCondition);
- return conditions;
- }
-
- /**
- * This command is only available if the LOGGING feature mode is enabled
- */
- @Override
- public List getFeatureModeLimitations() {
- return Arrays.asList(ModMailMode.LOGGING);
- }
-}
diff --git a/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/command/CloseSilently.java b/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/command/CloseSilently.java
index d84960638..e1fd8bbff 100644
--- a/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/command/CloseSilently.java
+++ b/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/command/CloseSilently.java
@@ -12,6 +12,7 @@ import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.service.management.ChannelManagementService;
import dev.sheldan.abstracto.modmail.condition.ModMailContextCondition;
import dev.sheldan.abstracto.modmail.config.ModMailFeatureDefinition;
+import dev.sheldan.abstracto.modmail.model.ClosingContext;
import dev.sheldan.abstracto.modmail.model.database.ModMailThread;
import dev.sheldan.abstracto.modmail.service.ModMailThreadService;
import dev.sheldan.abstracto.modmail.service.management.ModMailThreadManagementService;
@@ -52,7 +53,14 @@ public class CloseSilently extends AbstractConditionableCommand {
String note = parameters.size() == 1 ? (String) parameters.get(0) : templateService.renderTemplate("modmail_close_default_note", new Object());
AChannel channel = channelManagementService.loadChannel(commandContext.getChannel());
ModMailThread thread = modMailThreadManagementService.getByChannel(channel);
- return modMailThreadService.closeModMailThread(thread, note, false, commandContext.getUndoActions(), true)
+ ClosingContext context = ClosingContext
+ .builder()
+ .closingMember(commandContext.getAuthor())
+ .notifyUser(false)
+ .log(true)
+ .note(note)
+ .build();
+ return modMailThreadService.closeModMailThread(thread, context, commandContext.getUndoActions())
.thenApply(aVoid -> CommandResult.fromIgnored());
}
diff --git a/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/listener/ModMailMessageEditedListener.java b/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/listener/ModMailMessageEditedListener.java
index 62083fca5..ce4815da9 100644
--- a/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/listener/ModMailMessageEditedListener.java
+++ b/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/listener/ModMailMessageEditedListener.java
@@ -75,7 +75,8 @@ public class ModMailMessageEditedListener implements AsyncMessageUpdatedListener
messageOptional.ifPresent(modMailMessage -> {
log.info("Editing send message {} in channel {} in mod mail thread {} in server {}.", messageBefore.getMessageId(), messageBefore.getChannelId(), modMailMessage.getThreadReference().getId(), messageBefore.getServerId());
String contentStripped = message.getContentStripped();
- String commandName = commandRegistry.getCommandName(contentStripped.substring(0, contentStripped.indexOf(" ")), messageBefore.getServerId());
+ int spaceIndex = contentStripped.contains(" ") ? contentStripped.indexOf(" ") : contentStripped.length() - 1;
+ String commandName = commandRegistry.getCommandName(contentStripped.substring(0, spaceIndex), messageBefore.getServerId());
if(!commandService.doesCommandExist(commandName)) {
commandName = DEFAULT_COMMAND_FOR_MODMAIL_EDIT;
log.info("Edit did not contain the original command to retrieve the parameters for. Resulting to {}.", DEFAULT_COMMAND_FOR_MODMAIL_EDIT);
@@ -85,7 +86,10 @@ public class ModMailMessageEditedListener implements AsyncMessageUpdatedListener
CompletableFuture loadEditingUser = memberService.getMemberInServerAsync(messageBefore.getServerId(), modMailMessage.getAuthor().getUserReference().getId());
CompletableFuture.allOf(parameterParseFuture, loadTargetUser, loadEditingUser).thenAccept(unused ->
self.updateMessageInThread(message, parameterParseFuture.join(), loadTargetUser.join(), loadEditingUser.join())
- );
+ ).exceptionally(throwable -> {
+ log.error("Failed to update reply for mod mail thread in channel {}.", model.getAfter().getChannel().getIdLong(), throwable);
+ return null;
+ });
});
return DefaultListenerResult.PROCESSED;
}
diff --git a/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/service/ModMailMessageServiceBean.java b/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/service/ModMailMessageServiceBean.java
index 66cd723e0..388aabe89 100644
--- a/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/service/ModMailMessageServiceBean.java
+++ b/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/service/ModMailMessageServiceBean.java
@@ -1,25 +1,24 @@
package dev.sheldan.abstracto.modmail.service;
+import dev.sheldan.abstracto.core.exception.AbstractoRunTimeException;
import dev.sheldan.abstracto.core.models.ServerChannelMessageUser;
import dev.sheldan.abstracto.core.service.BotService;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.service.MemberService;
+import dev.sheldan.abstracto.core.service.UserService;
+import dev.sheldan.abstracto.core.utils.CompletableFutureList;
import dev.sheldan.abstracto.modmail.model.database.ModMailMessage;
import dev.sheldan.abstracto.modmail.model.database.ModMailThread;
-import dev.sheldan.abstracto.modmail.model.dto.LoadedModmailThreadMessage;
-import dev.sheldan.abstracto.modmail.model.dto.LoadedModmailThreadMessageList;
+import dev.sheldan.abstracto.modmail.model.template.ModmailLoggingThreadMessages;
import lombok.extern.slf4j.Slf4j;
-import net.dv8tion.jda.api.entities.Member;
-import net.dv8tion.jda.api.entities.Message;
-import net.dv8tion.jda.api.entities.TextChannel;
+import net.dv8tion.jda.api.entities.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Optional;
+import java.util.*;
import java.util.concurrent.CompletableFuture;
+import java.util.function.Function;
+import java.util.stream.Collectors;
@Component
@Slf4j
@@ -34,11 +33,16 @@ public class ModMailMessageServiceBean implements ModMailMessageService {
@Autowired
private ChannelService channelService;
+ @Autowired
+ private UserService userService;
+
+ private static final Integer HISTORY_RETRIEVAL_LIMIT = 100;
@Override
- public LoadedModmailThreadMessageList loadModMailMessages(List modMailMessages) {
+ public CompletableFuture loadModMailMessages(List modMailMessages) {
if(modMailMessages.isEmpty()) {
- return LoadedModmailThreadMessageList.builder().build();
+ return CompletableFuture.completedFuture(ModmailLoggingThreadMessages.builder().build());
}
+ CompletableFuture future = new CompletableFuture<>();
// all message must be from the same thread
ModMailThread thread = modMailMessages.get(0).getThreadReference();
log.debug("Loading {} mod mail messages from thread {} in server {}.", modMailMessages.size(), thread.getId(), thread.getServer().getId());
@@ -49,6 +53,11 @@ public class ModMailMessageServiceBean implements ModMailMessageService {
.userId(modMailMessage.getAuthor().getUserReference().getId())
.serverId(thread.getServer().getId());
// if its not from a private chat, we need to set channel ID in order to fetch the data
+ // this is necessary, because we only log to the current modmail channel in a certain feature mode
+ // but the DMs _always_ receive the messages from modmail thread.
+ // the channelID is null, if it was a message from a modmail thread
+ // which means, in order to retrieve the messages which were mod -> member
+ // we need to select the elements in which channel is null
if(Boolean.FALSE.equals(modMailMessage.getDmChannel())) {
log.debug("Message {} was from DM.", modMailMessage.getMessageId());
serverChannelMessageBuilder
@@ -59,53 +68,129 @@ public class ModMailMessageServiceBean implements ModMailMessageService {
}
messageIds.add(serverChannelMessageBuilder.build());
});
- List messageFutures = new ArrayList<>();
- // add the place holder futures, which are then resolved one by one
- // because we cannot directly fetch the messages, in case they are in a private channel
- // the opening of a private channel is a rest operation it itself, so we need
- // to create the promises here already, else the list is empty for example
- modMailMessages.forEach(modMailMessage -> messageFutures.add(getLoadedModmailThreadMessage()));
+ List messageIdsToLoad = messageIds
+ .stream()
+ .map(ServerChannelMessageUser::getMessageId)
+ .collect(Collectors.toList());
Optional textChannelFromServer = channelService.getTextChannelFromServerOptional(thread.getServer().getId(), thread.getChannel().getId());
if(textChannelFromServer.isPresent()) {
TextChannel modMailThread = textChannelFromServer.get();
Long userId = thread.getUser().getUserReference().getId();
botService.getInstance().openPrivateChannelById(userId).queue(privateChannel -> {
- Iterator iterator = messageFutures.iterator();
- messageIds.forEach(serverChannelMessage -> {
- log.debug("Loading message {}.", serverChannelMessage.getMessageId());
- CompletableFuture messageFuture;
- CompletableFuture memberFuture = memberService.getMemberInServerAsync(serverChannelMessage.getServerId(), serverChannelMessage.getUserId());
- if(serverChannelMessage.getChannelId() == null){
- messageFuture = channelService.retrieveMessageInChannel(privateChannel, serverChannelMessage.getMessageId());
- } else {
- messageFuture = channelService.retrieveMessageInChannel(modMailThread, serverChannelMessage.getMessageId());
- }
- CompletableFuture.allOf(messageFuture, memberFuture).whenComplete((aVoid, throwable) -> {
- LoadedModmailThreadMessage next = iterator.next();
- if(messageFuture.isCompletedExceptionally()) {
- log.warn("Message {} from user {} in server {} failed to load.", serverChannelMessage.getMessageId(), serverChannelMessage.getUserId(), serverChannelMessage.getServerId());
- messageFuture.exceptionally(throwable1 -> {
- log.warn("Failed with:", throwable1);
- return null;
- });
- next.getMessageFuture().complete(null);
- } else {
- next.getMessageFuture().complete(messageFuture.join());
- }
-
- if(memberFuture.isCompletedExceptionally()) {
- next.getMemberFuture().complete(null);
- } else {
- next.getMemberFuture().complete(memberFuture.join());
- }
+ Optional latestThreadMessageOptional = messageIds
+ .stream()
+ .filter(serverChannelMessageUser -> serverChannelMessageUser.getChannelId() != null)
+ .max(Comparator.comparing(ServerChannelMessageUser::getMessageId));
+ Optional latestPrivateMessageOptional = messageIds
+ .stream()
+ .filter(serverChannelMessageUser -> serverChannelMessageUser.getChannelId() == null)
+ .max(Comparator.comparing(ServerChannelMessageUser::getMessageId));
+ CompletableFuture threadHistoryFuture;
+ if(latestThreadMessageOptional.isPresent()) {
+ ServerChannelMessageUser latestPrivateMessage = latestThreadMessageOptional.get();
+ threadHistoryFuture = modMailThread.getHistoryAround(latestPrivateMessage.getMessageId(), HISTORY_RETRIEVAL_LIMIT).submit();
+ } else {
+ threadHistoryFuture = CompletableFuture.completedFuture(null);
+ }
+ CompletableFuture privateHistoryFuture;
+ if(latestPrivateMessageOptional.isPresent()) {
+ ServerChannelMessageUser latestThreadMessage = latestPrivateMessageOptional.get();
+ privateHistoryFuture = privateChannel.getHistoryAround(latestThreadMessage.getMessageId(), HISTORY_RETRIEVAL_LIMIT).submit();
+ } else {
+ privateHistoryFuture = CompletableFuture.completedFuture(null);
+ }
+ List loadedMessages = new ArrayList<>();
+ CompletableFuture.allOf(threadHistoryFuture, privateHistoryFuture)
+ .thenCompose(unused -> loadMoreMessages(messageIdsToLoad, privateHistoryFuture.join(), modMailThread, threadHistoryFuture.join(), privateChannel, loadedMessages, 0))
+ .thenAccept(unused -> {
+ Set userIds = messageIds
+ .stream()
+ .map(ServerChannelMessageUser::getUserId)
+ .collect(Collectors.toSet());
+ CompletableFutureList userFuture = userService.retrieveUsers(new ArrayList<>(userIds));
+ userFuture.getMainFuture().thenAccept(unused1 -> {
+ ModmailLoggingThreadMessages result = ModmailLoggingThreadMessages
+ .builder()
+ .messages(loadedMessages)
+ .authors(userFuture.getObjects())
+ .build();
+ future.complete(result);
});
});
});
+ } else {
+ future.completeExceptionally(new AbstractoRunTimeException("Channel for modmail thread not found. How did we get here?"));
}
- return LoadedModmailThreadMessageList.builder().messageList(messageFutures).build();
+ return future;
}
- public LoadedModmailThreadMessage getLoadedModmailThreadMessage() {
- return LoadedModmailThreadMessage.builder().memberFuture(new CompletableFuture<>()).messageFuture(new CompletableFuture<>()).build();
+ public CompletableFuture loadMoreMessages(List messagesToLoad,
+ MessageHistory privateMessageHistory, TextChannel thread,
+ MessageHistory threadMessageHistory, PrivateChannel dmChannel, List loadedMessages, Integer counter) {
+ if(counter == messagesToLoad.size()) {
+ log.warn("We encountered the maximum of {} iterations when loading modmail history - aborting.", messagesToLoad.size());
+ return CompletableFuture.completedFuture(null);
+ }
+ Map threadMessagesInStep = mapHistoryToMessageIds(threadMessageHistory);
+ Map privateMessagesInStep = mapHistoryToMessageIds(privateMessageHistory);
+ List messagesLoadedThisStep = new ArrayList<>();
+ messagesToLoad.forEach(messageId -> {
+ if(threadMessagesInStep.containsKey(messageId)) {
+ loadedMessages.add(threadMessagesInStep.get(messageId));
+ messagesLoadedThisStep.add(messageId);
+ } else if(privateMessagesInStep.containsKey(messageId)){
+ loadedMessages.add(privateMessagesInStep.get(messageId));
+ messagesLoadedThisStep.add(messageId);
+ }
+ });
+ messagesToLoad.removeAll(messagesLoadedThisStep);
+ if(messagesToLoad.isEmpty()) {
+ return CompletableFuture.completedFuture(null);
+ } else {
+ final CompletableFuture threadHistoryAction;
+ if(doesHistoryContainValues(threadMessageHistory)) {
+ Optional minThreadMessage = getOldestMessage(threadMessageHistory.getRetrievedHistory());
+ if(minThreadMessage.isPresent()) {
+ threadHistoryAction = thread.getHistoryBefore(minThreadMessage.get(), HISTORY_RETRIEVAL_LIMIT).submit();
+ } else {
+ threadHistoryAction = CompletableFuture.completedFuture(null);
+ }
+ } else {
+ threadHistoryAction = CompletableFuture.completedFuture(null);
+ }
+
+ final CompletableFuture privateHistoryAction;
+ if(doesHistoryContainValues(privateMessageHistory)) {
+ Optional minDmMessage = getOldestMessage(privateMessageHistory.getRetrievedHistory());
+ if(minDmMessage.isPresent()) {
+ privateHistoryAction = dmChannel.getHistoryBefore(minDmMessage.get(), HISTORY_RETRIEVAL_LIMIT).submit();
+ } else {
+ privateHistoryAction = CompletableFuture.completedFuture(null);
+ }
+ } else {
+ privateHistoryAction = CompletableFuture.completedFuture(null);
+ }
+ return CompletableFuture.allOf(threadHistoryAction, privateHistoryAction)
+ .thenCompose(lists -> loadMoreMessages(messagesToLoad, threadHistoryAction.join(), thread, privateHistoryAction.join(), dmChannel, loadedMessages, counter + 1));
+ }
}
+
+ private Map mapHistoryToMessageIds(MessageHistory threadMessageHistory) {
+ if(!doesHistoryContainValues(threadMessageHistory)) {
+ return new HashMap<>();
+ }
+ return threadMessageHistory
+ .getRetrievedHistory()
+ .stream()
+ .collect(Collectors.toMap(ISnowflake::getIdLong, Function.identity()));
+ }
+
+ private boolean doesHistoryContainValues(MessageHistory threadMessageHistory) {
+ return threadMessageHistory != null && !threadMessageHistory.isEmpty();
+ }
+
+ private Optional getOldestMessage(List messages) {
+ return messages.stream().min(Comparator.comparing(ISnowflake::getTimeCreated));
+ }
+
}
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 2fd484a74..44811af91 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
@@ -25,8 +25,8 @@ import dev.sheldan.abstracto.modmail.config.ModMailPostTargets;
import dev.sheldan.abstracto.modmail.exception.ModMailCategoryIdException;
import dev.sheldan.abstracto.modmail.exception.ModMailThreadChannelNotFound;
import dev.sheldan.abstracto.modmail.exception.ModMailThreadNotFoundException;
+import dev.sheldan.abstracto.modmail.model.ClosingContext;
import dev.sheldan.abstracto.modmail.model.database.*;
-import dev.sheldan.abstracto.modmail.model.dto.LoadedModmailThreadMessageList;
import dev.sheldan.abstracto.modmail.model.dto.ServerChoice;
import dev.sheldan.abstracto.modmail.model.template.*;
import dev.sheldan.abstracto.modmail.service.management.ModMailMessageManagementService;
@@ -47,6 +47,8 @@ import javax.annotation.PostConstruct;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.CompletableFuture;
+import java.util.function.Function;
+import java.util.stream.Collectors;
@Component
@Slf4j
@@ -63,8 +65,9 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
/**
* The template key used for default mod mail exceptions
*/
- public static final String MODMAIL_EXCEPTION_GENERIC_TEMPLATE = "modmail_exception_generic";
+ public static final String MODMAIL_CLOSE_PROGRESS_TEMPLATE_KEY = "modmail_closing_progress";
public static final String MODMAIL_STAFF_MESSAGE_TEMPLATE_KEY = "modmail_staff_message";
+ public static final String MODMAIL_THREAD_CREATED_TEMPLATE_KEY = "modmail_thread_created";
@Autowired
private ModMailThreadManagementService modMailThreadManagementService;
@@ -132,6 +135,9 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
@Autowired
private ServerManagementService serverManagementService;
+ @Autowired
+ private UserService userService;
+
@Autowired
private ModMailThreadServiceBean self;
@@ -186,10 +192,20 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
CompletableFuture textChannelFuture = channelService.createTextChannel(user.getName() + user.getDiscriminator(), server, categoryId);
return textChannelFuture.thenCompose(channel -> {
undoActions.add(UndoActionInstance.getChannelDeleteAction(serverId, channel.getIdLong()));
- return self.performModMailThreadSetup(member, initialMessage, channel, userInitiated, undoActions);
+ return self.performModMailThreadSetup(member, initialMessage, channel, userInitiated, undoActions, feedBackChannel);
});
}
+ @Transactional
+ public CompletableFuture sendContactNotification(Member member, TextChannel textChannel, MessageChannel feedBackChannel) {
+ ContactNotificationModel model = ContactNotificationModel
+ .builder()
+ .createdChannel(textChannel)
+ .targetMember(member)
+ .build();
+ return FutureUtils.toSingleFutureGeneric(channelService.sendEmbedTemplateInMessageChannelList(MODMAIL_THREAD_CREATED_TEMPLATE_KEY, model, feedBackChannel));
+ }
+
/**
* This method is responsible for creating the instance in the database, sending the header in the newly created text channel and forwarding the initial message
* by the user (if any), after this is complete, this method executes the method to perform the mod mail notification.
@@ -201,7 +217,7 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
* @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, TextChannel channel, boolean userInitiated, List undoActions, MessageChannel feedBackChannel) {
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;
@@ -221,6 +237,10 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
return CompletableFuture.allOf(headerFuture, notificationFuture, userReplyMessage).thenAccept(aVoid -> {
undoActions.clear();
self.setupModMailThreadInDB(initialMessage, channel, member, userReplyMessage.join());
+ }).thenAccept(unused -> {
+ if(!userInitiated) {
+ self.sendContactNotification(member, channel, feedBackChannel);
+ }
});
}
@@ -286,9 +306,16 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
@Override
public void createModMailPrompt(AUser user, Message initialMessage) {
List knownServers = userInServerManagementService.getUserInAllServers(user.getId());
- // do nothing if we don't know the user
+ // if the user doesnt exist in the servery set, we need to create the user first in all of them, in order to offer it
+ if(knownServers.isEmpty()) {
+ List mutualServers = initialMessage.getJDA().getMutualGuilds(initialMessage.getAuthor());
+ mutualServers.forEach(guild -> {
+ AServer server = serverManagementService.loadServer(guild);
+ knownServers.add(userInServerManagementService.loadOrCreateUser(server, user));
+ });
+ }
if(!knownServers.isEmpty()) {
- log.info("There are {} shared servers between user and abstracto.", knownServers.size());
+ log.info("There are {} shared servers between user and the bot.", knownServers.size());
List availableGuilds = new ArrayList<>();
HashMap choices = new HashMap<>();
for (int i = 0; i < knownServers.size(); i++) {
@@ -309,7 +336,7 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
availableGuilds.add(serverChoice);
}
}
- log.info("There were {} shared servers found which have modmailenabled.", availableGuilds.size());
+ log.info("There were {} shared servers found which have modmail enabled.", availableGuilds.size());
// if more than 1 server is available, show a choice dialog
if(availableGuilds.size() > 1) {
ModMailServerChooserModel modMailServerChooserModel = ModMailServerChooserModel
@@ -323,13 +350,21 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
.setDescription(text)
.setAction(reactionEmote -> {
Long chosenServerId = choices.get(reactionEmote.getEmoji());
- log.debug("Executing action for creationg a modmail thread in server {} for user {}.", chosenServerId, initialMessage.getAuthor().getIdLong());
- memberService.getMemberInServerAsync(chosenServerId, initialMessage.getAuthor().getIdLong()).thenCompose(member ->
- self.createModMailThreadForUser(member, initialMessage, initialMessage.getChannel(), true, new ArrayList<>()).exceptionally(throwable -> {
- log.error("Failed to setup thread correctly", throwable);
- return null;
- })
- );
+ Long userId = initialMessage.getAuthor().getIdLong();
+ log.debug("Executing action for creationg a modmail thread in server {} for user {}.", chosenServerId, userId);
+ memberService.getMemberInServerAsync(chosenServerId, userId).thenCompose(member -> {
+ try {
+ return self.createModMailThreadForUser(member, initialMessage, initialMessage.getChannel(), true, new ArrayList<>());
+ } catch (Exception exception) {
+ log.error("Setting up modmail thread for user {} in server {} failed.", userId, chosenServerId, exception);
+ CompletableFuture future = new CompletableFuture<>();
+ future.completeExceptionally(exception);
+ return future;
+ }
+ }).exceptionally(throwable -> {
+ log.error("Failed to load member {} for modmail in server {}.", userId, chosenServerId, throwable);
+ return null;
+ });
})
.build();
log.debug("Displaying server choice message for user {} in channel {}.", user.getId(), initialMessage.getChannel().getId());
@@ -338,19 +373,25 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
// if exactly one server is available, open the thread directly
Long chosenServerId = choices.get(availableGuilds.get(0).getReactionEmote());
log.info("Only one server available to modmail. Directly opening modmail thread for user {} in server {}.", initialMessage.getAuthor().getId(), chosenServerId);
- memberService.getMemberInServerAsync(chosenServerId, initialMessage.getAuthor().getIdLong()).thenCompose(member ->
- self.createModMailThreadForUser(member, initialMessage, initialMessage.getChannel(), true, new ArrayList<>()).exceptionally(throwable -> {
- log.error("Failed to setup thread correctly", throwable);
- return null;
- })
- );
+ memberService.getMemberInServerAsync(chosenServerId, initialMessage.getAuthor().getIdLong()).thenCompose(member -> {
+ try {
+ return self.createModMailThreadForUser(member, initialMessage, initialMessage.getChannel(), true, new ArrayList<>());
+ } catch (Exception exception) {
+ CompletableFuture future = new CompletableFuture<>();
+ future.completeExceptionally(exception);
+ return future;
+ }
+ }).exceptionally(throwable -> {
+ log.error("Failed to setup thread correctly", throwable);
+ return null;
+ });
} else {
log.info("No server available to open a modmail thread in.");
// in case there is no server available, send an error message
channelService.sendEmbedTemplateInMessageChannelList("modmail_no_server_available", new Object(), initialMessage.getChannel());
}
} else {
- log.warn("User which was not known in any of the servers tried to contact the bot. {}", user.getId());
+ log.warn("User {} which was not known in any of the servers tried to contact the bot.", user.getId());
}
}
@@ -383,23 +424,28 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
Long modmailThreadId = modMailThread.getId();
metricService.incrementCounter(MDOMAIL_THREAD_MESSAGE_RECEIVED);
log.debug("Relaying message {} to modmail thread {} for user {} to server {}.", messageFromUser.getId(), modMailThread.getId(), messageFromUser.getAuthor().getIdLong(), modMailThread.getServer().getId());
- return memberService.getMemberInServerAsync(modMailThread.getServer().getId(), messageFromUser.getAuthor().getIdLong()).thenCompose(member -> {
- Optional textChannelFromServer = channelService.getTextChannelFromServerOptional(serverId, channelId);
- if(textChannelFromServer.isPresent()) {
- TextChannel textChannel = textChannelFromServer.get();
- return self.sendUserReply(textChannel, modmailThreadId, messageFromUser, member, true);
- } else {
- log.warn("Closing mod mail thread {}, because it seems the channel {} in server {} got deleted.", modmailThreadId, channelId, serverId);
- // in this case there was no text channel on the server associated with the mod mail thread
- // close the existing one, so the user can start a new one
- self.closeModMailThreadInDb(modmailThreadId);
- String textToSend = templateService.renderTemplate("modmail_failed_to_forward_message", new Object());
- return channelService.sendTextToChannel(textToSend, messageFromUser.getChannel());
- }
- });
+ return memberService.getMemberInServerAsync(modMailThread.getServer().getId(), messageFromUser.getAuthor().getIdLong()).thenCompose(member ->
+ self.relayMessage(messageFromUser, serverId, channelId, modmailThreadId, member)
+ );
}
+ @Transactional
+ public CompletableFuture relayMessage(Message messageFromUser, Long serverId, Long channelId, Long modmailThreadId, Member member) {
+ Optional textChannelFromServer = channelService.getTextChannelFromServerOptional(serverId, channelId);
+ if(textChannelFromServer.isPresent()) {
+ TextChannel textChannel = textChannelFromServer.get();
+ return self.sendUserReply(textChannel, modmailThreadId, messageFromUser, member, true);
+ } else {
+ log.warn("Closing mod mail thread {}, because it seems the channel {} in server {} got deleted.", modmailThreadId, channelId, serverId);
+ // in this case there was no text channel on the server associated with the mod mail thread
+ // close the existing one, so the user can start a new one
+ self.closeModMailThreadInDb(modmailThreadId);
+ String textToSend = templateService.renderTemplate("modmail_failed_to_forward_message", new Object());
+ return channelService.sendTextToChannel(textToSend, messageFromUser.getChannel());
+ }
+ }
+
/**
* This message takes a received {@link Message} from a user, renders it to a new message to send and sends it to
* the appropriate {@link ModMailThread} channel, the returned promise only returns if the message was dealt with on the user
@@ -422,12 +468,21 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
if(subscriberList.isEmpty()) {
subscriberMemberFutures.add(CompletableFuture.completedFuture(null));
}
- log.debug("Pinging {} subscribers for modmail thread {}.", subscriberList.size(), modMailThreadId);
+ log.debug("Mentioning {} subscribers for modmail thread {}.", subscriberList.size(), modMailThreadId);
} else {
subscriberMemberFutures.add(CompletableFuture.completedFuture(null));
}
- return FutureUtils.toSingleFutureGeneric(subscriberMemberFutures).thenCompose(firstVoid -> {
- List subscribers = new ArrayList<>();
+ CompletableFuture messageFuture = new CompletableFuture<>();
+ FutureUtils.toSingleFutureGeneric(subscriberMemberFutures).whenComplete((unused, throwable) -> {
+ if(throwable != null) {
+ log.warn("Failed to load subscriber users. Still relaying message.", throwable);
+ }
+ List subscribers = subscriberMemberFutures
+ .stream()
+ .filter(memberCompletableFuture -> !memberCompletableFuture.isCompletedExceptionally())
+ .map(CompletableFuture::join)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toList());
ModMailUserReplyModel modMailUserReplyModel = ModMailUserReplyModel
.builder()
.postedMessage(messageFromUser)
@@ -436,19 +491,24 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
.build();
MessageToSend messageToSend = templateService.renderEmbedTemplate("modmail_user_message", modMailUserReplyModel, textChannel.getGuild().getIdLong());
List> completableFutures = channelService.sendMessageToSendToChannel(messageToSend, textChannel);
- return CompletableFuture.allOf(completableFutures.toArray(new CompletableFuture[0]))
+ CompletableFuture.allOf(completableFutures.toArray(new CompletableFuture[0]))
.thenCompose(aVoid -> {
log.debug("Adding read reaction to initial message for mod mail thread in channel {}.", textChannel.getGuild().getId());
return reactionService.addReactionToMessageAsync("readReaction", textChannel.getGuild().getIdLong(), messageFromUser);
})
.thenApply(aVoid -> {
+ Message createdMessage = completableFutures.get(0).join();
if(modMailThreadExists) {
- self.postProcessSendMessages(textChannel, completableFutures.get(0).join(), messageFromUser);
+ self.postProcessSendMessages(textChannel, createdMessage, messageFromUser);
}
- return completableFutures.get(0).join();
+ return messageFuture.complete(createdMessage);
+ }).exceptionally(throwable1 -> {
+ log.error("Failed to forward message to thread.", throwable1);
+ messageFuture.completeExceptionally(throwable1);
+ return null;
});
});
-
+ return messageFuture;
}
@@ -474,6 +534,7 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
}
@Override
+ @Transactional
public CompletableFuture relayMessageToDm(Long modmailThreadId, String text, Message replyCommandMessage, boolean anonymous, MessageChannel feedBack, List undoActions, Member targetMember) {
log.info("Relaying message {} to user {} in modmail thread {} on server {}.", replyCommandMessage.getId(), targetMember.getId(), modmailThreadId, targetMember.getGuild().getId());
AUserInAServer moderator = userInServerManagementService.loadOrCreateUser(replyCommandMessage.getMember());
@@ -514,32 +575,35 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
}
@Override
- public CompletableFuture closeModMailThread(ModMailThread modMailThread, String note, boolean notifyUser, List undoActions, Boolean log) {
+ public CompletableFuture closeModMailThreadEvaluateLogging(ModMailThread modMailThread, ClosingContext closingConfig, List undoActions) {
boolean loggingMode = featureModeService.featureModeActive(ModMailFeatureDefinition.MOD_MAIL, modMailThread.getServer(), ModMailMode.LOGGING);
- boolean shouldLogThread = log && loggingMode;
- return closeModMailThread(modMailThread, note, notifyUser, shouldLogThread, undoActions);
+ closingConfig.setLog(closingConfig.getLog() && loggingMode);
+ return closeModMailThread(modMailThread, closingConfig, undoActions);
}
@Override
- public CompletableFuture closeModMailThread(ModMailThread modMailThread, String note, boolean notifyUser, boolean logThread, List undoActions) {
+ public CompletableFuture closeModMailThread(ModMailThread modMailThread, ClosingContext closingConfig, List undoActions) {
metricService.incrementCounter(MODMAIL_THREAD_CLOSED_COUNTER);
Long modMailThreadId = modMailThread.getId();
log.info("Starting closing procedure for thread {}", modMailThread.getId());
List modMailMessages = modMailThread.getMessages();
Long userId = modMailThread.getUser().getUserReference().getId();
Long serverId = modMailThread.getServer().getId();
- if(logThread) {
- LoadedModmailThreadMessageList messages = modMailMessageService.loadModMailMessages(modMailMessages);
- CompletableFuture messagesFuture = FutureUtils.toSingleFuture(messages.getAllFutures());
-
- return messagesFuture.handle((aVoid, throwable) ->
- self.logMessagesToModMailLog(note, notifyUser, modMailThreadId, undoActions, messages, serverId, userId)
- ).toCompletableFuture().thenCompose(o -> o);
-
+ 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, notifyUser, member, undoActions)
+ self.afterSuccessfulLog(modMailThreadId, closingConfig.getNotifyUser(), member, undoActions)
);
}
}
@@ -558,8 +622,6 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
/**
* This method takes the actively loaded futures, calls the method responsible for logging the messages, and calls the method
* after the logging has been done.
- * @param note The note which was provided when closing the {@link ModMailThread}
- * @param notifyUser Whether or not to notify the user
* @param modMailThreadId The ID of the {@link ModMailThread} which is being closed
* @param undoActions The list of {@link UndoActionInstance} to execute in case of exceptions
* @param messages The list of loaded {@link Message} to log
@@ -568,23 +630,24 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
* @return A {@link CompletableFuture future} which completes when the messages have been logged
*/
@Transactional
- public CompletableFuture logMessagesToModMailLog(String note, Boolean notifyUser, Long modMailThreadId, List undoActions, LoadedModmailThreadMessageList messages, Long serverId, Long userId) {
- log.debug("Logging {} modmail messages for modmail thread {}.", messages.getMessageList().size(), modMailThreadId);
+ public CompletableFuture logMessagesToModMailLog(ClosingContext closingContext, Long modMailThreadId, List undoActions,
+ ModmailLoggingThreadMessages messages, Long serverId, Long userId) {
+ log.debug("Logging {} modmail messages for modmail thread {}.", messages.getMessages().size(), modMailThreadId);
try {
- CompletableFutureList list = self.logModMailThread(modMailThreadId, messages, note, undoActions);
- return list.getMainFuture().thenCompose(avoid -> {
- list.getFutures().forEach(messageCompletableFuture -> {
- Message message = messageCompletableFuture.join();
- undoActions.add(UndoActionInstance.getMessageDeleteAction(message.getGuild().getIdLong(), message.getChannel().getIdLong(), message.getIdLong()));
- });
- return memberService.getMemberInServerAsync(serverId, userId).thenCompose(member ->
- self.afterSuccessfulLog(modMailThreadId, notifyUser, member, undoActions)
- ).exceptionally(throwable -> {
- log.warn("Failed to retrieve member for closing the modmail thread. Closing without member information.", throwable);
- self.afterSuccessfulLog(modMailThreadId, false, null, undoActions);
- return null;
- });
- });
+ return self.logModMailThread(modMailThreadId, messages, closingContext, undoActions, serverId)
+ .thenCompose(list -> list.getMainFuture().thenCompose(unused -> {
+ list.getFutures().forEach(messageCompletableFuture -> {
+ Message message = messageCompletableFuture.join();
+ undoActions.add(UndoActionInstance.getMessageDeleteAction(message.getGuild().getIdLong(), message.getChannel().getIdLong(), message.getIdLong()));
+ });
+ return memberService.getMemberInServerAsync(serverId, userId).thenCompose(member ->
+ self.afterSuccessfulLog(modMailThreadId, closingContext.getNotifyUser(), member, undoActions)
+ ).exceptionally(throwable -> {
+ log.warn("Failed to retrieve member for closing the modmail thread. Closing without member information.", throwable);
+ self.afterSuccessfulLog(modMailThreadId, false, null, undoActions);
+ return null;
+ });
+ }));
} catch (Exception e) {
log.error("Failed to log mod mail messages", e);
throw new AbstractoRunTimeException(e);
@@ -663,74 +726,99 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
* log concerning general information about the closed {@link ModMailThread}
* @param modMailThreadId The ID of the {@link ModMailThread} to log the messages of
* @param messages The list of {@link CompletableFuture} which contain the {@link Message} which could be loaded
- * @param note The note which was entered when closing the {@link ModMailThread}
* @param undoActions A list of {@link dev.sheldan.abstracto.core.models.UndoAction actions} to be undone in case the operation fails. This list will be filled in the method.
+ * @param serverId The ID of the {@link Guild server} the modmail thread is in
* @throws ModMailThreadNotFoundException in case the {@link ModMailThread} is not found by the ID
* @return An instance of {@link CompletableFutureList}, which contains a main {@link CompletableFuture} which is resolved,
* when all of the smaller {@link CompletableFuture} in it are resolved. We need this construct, because we need to access
* the result values of the individual futures after they are done.
*/
@Transactional
- public CompletableFutureList logModMailThread(Long modMailThreadId, LoadedModmailThreadMessageList messages, String note, List undoActions) {
- log.info("Logging mod mail thread {} with {} messages.", modMailThreadId, messages.getMessageList().size());
+ public CompletableFuture> logModMailThread(Long modMailThreadId, ModmailLoggingThreadMessages messages,
+ ClosingContext context, List undoActions, Long serverId) {
+ log.info("Logging mod mail thread {} with {} messages.", modMailThreadId, messages.getMessages().size());
+ if(messages.getMessages().isEmpty()) {
+ log.info("Modmail thread {} is empty. No messages to log.", modMailThreadId);
+ return CompletableFuture.completedFuture(new CompletableFutureList<>(new ArrayList<>()));
+ }
+ TextChannel channel = channelService.getTextChannelFromServer(serverId, modMailThreadId);
+ ClosingProgressModel progressModel = ClosingProgressModel
+ .builder()
+ .loggedMessages(0)
+ .totalMessages(messages.getMessages().size())
+ .build();
+ List> updateMessageFutures = channelService.sendEmbedTemplateInTextChannelList(MODMAIL_CLOSE_PROGRESS_TEMPLATE_KEY, progressModel, channel);
+ return FutureUtils.toSingleFutureGeneric(updateMessageFutures)
+ .thenApply(updateMessage -> self.logMessages(modMailThreadId, messages, context, updateMessageFutures.get(0).join()));
+ }
+
+ @Transactional
+ public CompletableFutureList logMessages(Long modMailThreadId, ModmailLoggingThreadMessages messages, ClosingContext context, Message updateMessage) {
Optional modMailThreadOpt = modMailThreadManagementService.getByIdOptional(modMailThreadId);
if(modMailThreadOpt.isPresent()) {
ModMailThread modMailThread = modMailThreadOpt.get();
List loggedMessages = new ArrayList<>();
- messages.getMessageList().forEach(futures -> {
- try {
- CompletableFuture future = futures.getMessageFuture();
- if(!future.isCompletedExceptionally()) {
- Message loadedMessage = future.join();
- if(loadedMessage != null) {
- log.info("Logging message {} in modmail thread {}.", loadedMessage.getId(), modMailThreadId);
- ModMailMessage modmailMessage = modMailThread.getMessages()
- .stream()
- .filter(modMailMessage -> {
- if(modMailMessage.getDmChannel()) {
- return modMailMessage.getCreatedMessageInDM().equals(loadedMessage.getIdLong());
- } else {
- return modMailMessage.getCreatedMessageInChannel().equals(loadedMessage.getIdLong());
- }
- })
- .findFirst().orElseThrow(() -> new AbstractoRunTimeException("Could not find desired message in list of messages in thread. This should not happen, as we just retrieved them from the same place."));
- Member author = futures.getMemberFuture().join();
- ModMailLoggedMessageModel modMailLoggedMessageModel =
- ModMailLoggedMessageModel
- .builder()
- .message(loadedMessage)
- .modMailMessage(modmailMessage)
- .author(author) // doesnt work for the messages from the DM channel
- .build();
- loggedMessages.add(modMailLoggedMessageModel);
- }
- } else {
- log.warn("One future failed to load. Will not log a message in modmail thread {}.", modMailThreadId);
- }
- } catch (Exception e) {
- log.error("Failed handle the loaded messages.", e);
- }
+ Map authors = messages
+ .getAuthors()
+ .stream().collect(Collectors.toMap(ISnowflake::getIdLong, Function.identity()));
+ messages.getMessages().forEach(message -> {
+ log.info("Logging message {} in modmail thread {}.", message.getId(), modMailThreadId);
+ ModMailMessage modmailMessage = modMailThread.getMessages()
+ .stream()
+ .filter(modMailMessage -> {
+ if(modMailMessage.getDmChannel()) {
+ return modMailMessage.getCreatedMessageInDM().equals(message.getIdLong());
+ } else {
+ return modMailMessage.getCreatedMessageInChannel().equals(message.getIdLong());
+ }
+ })
+ .findFirst().orElseThrow(() -> new AbstractoRunTimeException("Could not find desired message in list of messages in thread. This should not happen, as we just retrieved them from the same place."));
+ User author = authors.getOrDefault(modmailMessage.getAuthor().getUserReference().getId(), message.getJDA().getSelfUser());
+ ModMailLoggedMessageModel modMailLoggedMessageModel =
+ ModMailLoggedMessageModel
+ .builder()
+ .message(message)
+ .author(author)
+ .modMailMessage(modmailMessage)
+ .build();
+ loggedMessages.add(modMailLoggedMessageModel);
});
List> completableFutures = new ArrayList<>();
- // TODO dont use this
- modMailThread.setClosed(Instant.now());
- ModMailClosingHeaderModel headerModel = ModMailClosingHeaderModel
- .builder()
- .closedThread(modMailThread)
- .note(note)
- .build();
log.debug("Sending close header and individual mod mail messages to mod mail log target for thread {}.", modMailThreadId);
- MessageToSend messageToSend = templateService.renderEmbedTemplate("modmail_close_header", headerModel, modMailThread.getServer().getId());
- List> closeHeaderFutures = postTargetService.sendEmbedInPostTarget(messageToSend, ModMailPostTargets.MOD_MAIL_LOG, modMailThread.getServer().getId());
+ CompletableFuture headerFuture = loadUserAndSendClosingHeader(modMailThread, context);
// TODO in case the rendering fails, the already sent messages are not deleted
- completableFutures.addAll(closeHeaderFutures);
- completableFutures.addAll(self.sendMessagesToPostTarget(modMailThread, loggedMessages));
+ completableFutures.add(headerFuture);
+ completableFutures.addAll(self.sendMessagesToPostTarget(modMailThread, loggedMessages, updateMessage));
return new CompletableFutureList<>(completableFutures);
} else {
throw new ModMailThreadNotFoundException(modMailThreadId);
}
}
+ private CompletableFuture loadUserAndSendClosingHeader(ModMailThread modMailThread, ClosingContext closingContext) {
+ ModMailClosingHeaderModel headerModel = ModMailClosingHeaderModel
+ .builder()
+ .closingMember(closingContext.getClosingMember())
+ .note(closingContext.getNote())
+ .silently(closingContext.getNotifyUser())
+ .messageCount(modMailThread.getMessages().size())
+ .startDate(modMailThread.getCreated())
+ .serverId(modMailThread.getServer().getId())
+ .silently(!closingContext.getNotifyUser())
+ .userId(modMailThread.getUser().getUserReference().getId())
+ .build();
+ return userService.retrieveUserForId(modMailThread.getUser().getUserReference().getId()).thenApply(user -> {
+ headerModel.setUser(user);
+ return self.sendClosingHeader(headerModel).get(0);
+ }).thenCompose(Function.identity());
+ }
+
+ @Transactional
+ public List> sendClosingHeader(ModMailClosingHeaderModel model) {
+ MessageToSend messageToSend = templateService.renderEmbedTemplate("modmail_close_header", model, model.getServerId());
+ return postTargetService.sendEmbedInPostTarget(messageToSend, ModMailPostTargets.MOD_MAIL_LOG, model.getServerId());
+ }
+
/**
* Sets the {@link ModMailThread} in the database to CLOSED.
* @param modMailThreadId The ID of the {@link ModMailThread} to update the state of
@@ -756,15 +844,25 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
* @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}
*/
- public List> sendMessagesToPostTarget(ModMailThread modMailThread, List loadedMessages) {
+ public List> sendMessagesToPostTarget(ModMailThread modMailThread, List loadedMessages, Message updateMessage) {
List> messageFutures = new ArrayList<>();
- // TODO order messages
- loadedMessages.forEach(message -> {
- log.debug("Sending message {} of modmail thread {} to modmail log post target.", modMailThread.getId(), message.getMessage().getId());
+ ClosingProgressModel progressModel = ClosingProgressModel
+ .builder()
+ .loggedMessages(0)
+ .totalMessages(loadedMessages.size())
+ .build();
+ loadedMessages = loadedMessages.stream().sorted(Comparator.comparing(o -> o.getMessage().getTimeCreated())).collect(Collectors.toList());
+ for (int i = 0; i < loadedMessages.size(); i++) {
+ ModMailLoggedMessageModel message = loadedMessages.get(i);
+ log.debug("Sending message {} of modmail thread {} to modmail log post target.", modMailThread.getId(), message.getMessage().getId());
MessageToSend messageToSend = templateService.renderEmbedTemplate("modmail_close_logged_message", message, modMailThread.getServer().getId());
List> logFuture = postTargetService.sendEmbedInPostTarget(messageToSend, ModMailPostTargets.MOD_MAIL_LOG, modMailThread.getServer().getId());
+ if(i != 0 && (i % 10) == 0) {
+ progressModel.setLoggedMessages(i);
+ messageService.editMessageWithNewTemplate(updateMessage, MODMAIL_CLOSE_PROGRESS_TEMPLATE_KEY, progressModel);
+ }
messageFutures.addAll(logFuture);
- });
+ }
return messageFutures;
}
diff --git a/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/service/management/ModMailThreadManagementServiceBean.java b/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/service/management/ModMailThreadManagementServiceBean.java
index cbc2f3f41..583ffcc63 100644
--- a/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/service/management/ModMailThreadManagementServiceBean.java
+++ b/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/service/management/ModMailThreadManagementServiceBean.java
@@ -104,6 +104,7 @@ public class ModMailThreadManagementServiceBean implements ModMailThreadManageme
public ModMailThread createModMailThread(AUserInAServer userInAServer, AChannel channel) {
ModMailThread thread = ModMailThread
.builder()
+ .id(channel.getId())
.channel(channel)
.created(Instant.now())
.user(userInAServer)
diff --git a/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/setup/ModMailCategoryDelayedAction.java b/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/setup/ModMailCategoryDelayedAction.java
index 07b8db2f5..178fdabdd 100644
--- a/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/setup/ModMailCategoryDelayedAction.java
+++ b/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/setup/ModMailCategoryDelayedAction.java
@@ -28,7 +28,7 @@ public class ModMailCategoryDelayedAction implements DelayedAction {
public void execute(DelayedActionConfig delayedActionConfig) {
ModMailCategoryDelayedActionConfig concrete = (ModMailCategoryDelayedActionConfig) delayedActionConfig;
log.info("Executing delayed action for configuration the mdomail category to {} in server {}.", concrete.getCategoryId(), concrete.getServerId());
- configService.setLongValue(ModMailThreadServiceBean.MODMAIL_CATEGORY, concrete.getServerId(), concrete.getCategoryId());
+ configService.setOrCreateConfigValue(ModMailThreadServiceBean.MODMAIL_CATEGORY, concrete.getServerId(), concrete.getCategoryId().toString());
}
/**
diff --git a/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/resources/migrations/1.0-modmail/modMail-seedData/command.xml b/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/resources/migrations/1.0-modmail/modMail-seedData/command.xml
index 345ef6143..407202a6d 100644
--- a/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/resources/migrations/1.0-modmail/modMail-seedData/command.xml
+++ b/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/resources/migrations/1.0-modmail/modMail-seedData/command.xml
@@ -20,11 +20,6 @@
-
-
-
-
-
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 f50e0b32e..86ce2eb78 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
@@ -1,6 +1,9 @@
abstracto.systemConfigs.modMailClosingText.name=modMailClosingText
abstracto.systemConfigs.modMailClosingText.stringValue=Thread has been closed.
+abstracto.systemConfigs.modmailCategory.name=modmailCategory
+abstracto.systemConfigs.modmailCategory.longValue=0
+
abstracto.featureFlags.modmail.featureName=modmail
abstracto.featureFlags.modmail.enabled=false
@@ -12,5 +15,5 @@ abstracto.featureModes.log.mode=log
abstracto.featureModes.log.enabled=true
abstracto.featureModes.threadMessage.featureName=modmail
-abstracto.featureModes.threadMessage.mode=filterNotifications
+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/model/ClosingContext.java b/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/model/ClosingContext.java
new file mode 100644
index 000000000..a1cfe28de
--- /dev/null
+++ b/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/model/ClosingContext.java
@@ -0,0 +1,16 @@
+package dev.sheldan.abstracto.modmail.model;
+
+import lombok.Builder;
+import lombok.Getter;
+import lombok.Setter;
+import net.dv8tion.jda.api.entities.Member;
+
+@Getter
+@Setter
+@Builder
+public class ClosingContext {
+ private Boolean notifyUser;
+ private Boolean log;
+ private Member closingMember;
+ private String note;
+}
diff --git a/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/model/database/ModMailThread.java b/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/model/database/ModMailThread.java
index 7e1b587a1..1642a0bd7 100644
--- a/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/model/database/ModMailThread.java
+++ b/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/model/database/ModMailThread.java
@@ -26,7 +26,6 @@ import java.util.List;
public class ModMailThread implements Serializable {
@Id
- @GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false)
private Long id;
diff --git a/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/model/dto/LoadedModmailThreadMessage.java b/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/model/dto/LoadedModmailThreadMessage.java
deleted file mode 100644
index 01f2f6c68..000000000
--- a/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/model/dto/LoadedModmailThreadMessage.java
+++ /dev/null
@@ -1,17 +0,0 @@
-package dev.sheldan.abstracto.modmail.model.dto;
-
-import lombok.Builder;
-import lombok.Getter;
-import lombok.Setter;
-import net.dv8tion.jda.api.entities.Member;
-import net.dv8tion.jda.api.entities.Message;
-
-import java.util.concurrent.CompletableFuture;
-
-@Getter
-@Setter
-@Builder
-public class LoadedModmailThreadMessage {
- private CompletableFuture messageFuture;
- private CompletableFuture memberFuture;
-}
diff --git a/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/model/dto/LoadedModmailThreadMessageList.java b/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/model/dto/LoadedModmailThreadMessageList.java
deleted file mode 100644
index 6c08fd1d4..000000000
--- a/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/model/dto/LoadedModmailThreadMessageList.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package dev.sheldan.abstracto.modmail.model.dto;
-
-import lombok.Builder;
-import lombok.Getter;
-import lombok.Setter;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.CompletableFuture;
-
-@Getter
-@Setter
-@Builder
-public class LoadedModmailThreadMessageList {
- private List messageList;
- public List getAllFutures() {
- List futures = new ArrayList<>();
- messageList.forEach(loadedModmailThreadMessage -> {
- futures.add(loadedModmailThreadMessage.getMemberFuture());
- futures.add(loadedModmailThreadMessage.getMessageFuture());
- });
- return futures;
- }
-}
diff --git a/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/model/template/ClosingProgressModel.java b/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/model/template/ClosingProgressModel.java
new file mode 100644
index 000000000..4eb936f48
--- /dev/null
+++ b/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/model/template/ClosingProgressModel.java
@@ -0,0 +1,13 @@
+package dev.sheldan.abstracto.modmail.model.template;
+
+import lombok.Builder;
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+@Builder
+public class ClosingProgressModel {
+ private Integer loggedMessages;
+ private Integer totalMessages;
+}
diff --git a/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/model/template/ContactNotificationModel.java b/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/model/template/ContactNotificationModel.java
new file mode 100644
index 000000000..00ce96208
--- /dev/null
+++ b/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/model/template/ContactNotificationModel.java
@@ -0,0 +1,15 @@
+package dev.sheldan.abstracto.modmail.model.template;
+
+import lombok.Builder;
+import lombok.Getter;
+import lombok.Setter;
+import net.dv8tion.jda.api.entities.Member;
+import net.dv8tion.jda.api.entities.TextChannel;
+
+@Getter
+@Setter
+@Builder
+public class ContactNotificationModel {
+ private Member targetMember;
+ private TextChannel createdChannel;
+}
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 b7989825f..891d6dcb4 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
@@ -4,8 +4,11 @@ import dev.sheldan.abstracto.modmail.model.database.ModMailThread;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
+import net.dv8tion.jda.api.entities.Member;
+import net.dv8tion.jda.api.entities.User;
import java.time.Duration;
+import java.time.Instant;
/**
* This model is used when rendering the message before logging the messages in a closed {@link ModMailThread} and contains
@@ -22,13 +25,20 @@ public class ModMailClosingHeaderModel {
/**
* The {@link ModMailThread} which was closed
*/
- private ModMailThread closedThread;
+ private Integer messageCount;
+ private Instant startDate;
+ private Long userId;
/**
* The duration between the creation and closed date of a {@link ModMailThread}
* @return The duration between the creation date and the date the thread has been closed
*/
public Duration getDuration() {
- return Duration.between(closedThread.getCreated(), closedThread.getClosed());
+ return Duration.between(startDate, Instant.now());
}
+
+ private Member closingMember;
+ private Boolean silently;
+ private User user;
+ private Long serverId;
}
diff --git a/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/model/template/ModMailLoggedMessageModel.java b/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/model/template/ModMailLoggedMessageModel.java
index f9ed1fd61..7627f66d3 100644
--- a/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/model/template/ModMailLoggedMessageModel.java
+++ b/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/model/template/ModMailLoggedMessageModel.java
@@ -1,12 +1,11 @@
package dev.sheldan.abstracto.modmail.model.template;
-import dev.sheldan.abstracto.core.models.FullUserInServer;
import dev.sheldan.abstracto.modmail.model.database.ModMailMessage;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
-import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
+import net.dv8tion.jda.api.entities.User;
/**
* This model is used to render a message from a mod mail thread when closing the thread and logging the thread to the logging post target
@@ -25,9 +24,9 @@ public class ModMailLoggedMessageModel {
private ModMailMessage modMailMessage;
/**
- * A reference to the {@link FullUserInServer} which is the author. The member part is null, if the member left the guild.
+ * A reference to the {@link User} which is the author. The member part is null, if the member left the guild.
*/
- private Member author;
+ private User author;
}
diff --git a/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/model/template/ModMailUserReplyModel.java b/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/model/template/ModMailUserReplyModel.java
index 170bc50b4..55775f3f7 100644
--- a/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/model/template/ModMailUserReplyModel.java
+++ b/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/model/template/ModMailUserReplyModel.java
@@ -1,6 +1,5 @@
package dev.sheldan.abstracto.modmail.model.template;
-import dev.sheldan.abstracto.core.models.FullUserInServer;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
@@ -25,8 +24,8 @@ public class ModMailUserReplyModel {
*/
private Message postedMessage;
/**
- * List of {@link FullUserInServer} which are registered as subscribers for a particular mod mail thread and will be pinged
+ * List of {@link Member} which are registered as subscribers for a particular mod mail thread and will be pinged
* when the user sends a new message
*/
- private List subscribers;
+ private List subscribers;
}
diff --git a/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/model/template/ModmailLoggingThreadMessages.java b/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/model/template/ModmailLoggingThreadMessages.java
new file mode 100644
index 000000000..c8f5f6ec5
--- /dev/null
+++ b/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/model/template/ModmailLoggingThreadMessages.java
@@ -0,0 +1,17 @@
+package dev.sheldan.abstracto.modmail.model.template;
+
+import lombok.Builder;
+import lombok.Getter;
+import lombok.Setter;
+import net.dv8tion.jda.api.entities.Message;
+import net.dv8tion.jda.api.entities.User;
+
+import java.util.List;
+
+@Getter
+@Setter
+@Builder
+public class ModmailLoggingThreadMessages {
+ private List messages;
+ private List authors;
+}
diff --git a/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/service/ModMailMessageService.java b/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/service/ModMailMessageService.java
index 09631b38e..409cf1f89 100644
--- a/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/service/ModMailMessageService.java
+++ b/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/service/ModMailMessageService.java
@@ -1,19 +1,11 @@
package dev.sheldan.abstracto.modmail.service;
import dev.sheldan.abstracto.modmail.model.database.ModMailMessage;
-import dev.sheldan.abstracto.modmail.model.dto.LoadedModmailThreadMessageList;
-import net.dv8tion.jda.api.entities.Message;
+import dev.sheldan.abstracto.modmail.model.template.ModmailLoggingThreadMessages;
import java.util.List;
+import java.util.concurrent.CompletableFuture;
-/**
- * Service to handle the messages of a {@link dev.sheldan.abstracto.modmail.model.database.ModMailThread}
- */
public interface ModMailMessageService {
- /**
- * Loads the given mod mail messages in the form of {@link Message} from Discord and returns the created promises, some of which might fail, if the message was already deleted
- * @param modMailMessages The list of {@link ModMailMessage} to load
- * @return A instance of {@link LoadedModmailThreadMessageList} which contain the individual results of actively loading the {@link Message} and the {@link net.dv8tion.jda.api.entities.Member}
- */
- LoadedModmailThreadMessageList loadModMailMessages(List modMailMessages);
+ CompletableFuture loadModMailMessages(List modMailMessages);
}
diff --git a/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/service/ModMailThreadService.java b/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/service/ModMailThreadService.java
index 8f76b8c27..ea834bca6 100644
--- a/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/service/ModMailThreadService.java
+++ b/abstracto-application/abstracto-modules/modmail/modmail-int/src/main/java/dev/sheldan/abstracto/modmail/service/ModMailThreadService.java
@@ -5,6 +5,7 @@ import dev.sheldan.abstracto.core.models.UndoActionInstance;
import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.models.database.AUser;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
+import dev.sheldan.abstracto.modmail.model.ClosingContext;
import dev.sheldan.abstracto.modmail.model.database.ModMailThread;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
@@ -79,13 +80,10 @@ public interface ModMailThreadService {
* post target. This also takes an optional note, which will be displayed in the first message of the logging. This method changes the state of the
* {@link ModMailThread} to CLOSED and notifies the user about closing.
* @param modMailThread The {@link ModMailThread} which is being closed.
- * @param note The text of the note used for the header message of the logged mod mail thread.
- * @param notifyUser Whether or not the user should be notified
- * @param log whether or not the closed {@link ModMailThread} should be logged (if the {@link dev.sheldan.abstracto.core.config.FeatureMode} is enabled)
* @param undoActions A list of {@link dev.sheldan.abstracto.core.models.UndoAction actions} to be undone in case the operation fails. This list will be filled in the method.
* @return A {@link CompletableFuture future} which completes when the {@link ModMailThread thread} has been closed.
*/
- CompletableFuture closeModMailThread(ModMailThread modMailThread, String note, boolean notifyUser, List undoActions, Boolean log);
+ CompletableFuture closeModMailThreadEvaluateLogging(ModMailThread modMailThread, ClosingContext closingConfig, List undoActions);
/**
* Closes the mod mail thread which means: deletes the {@link net.dv8tion.jda.api.entities.TextChannel} associated with the mod mail thread,
@@ -93,14 +91,11 @@ public interface ModMailThreadService {
* be displayed in the first message of the logging. This method changes the state of the {@link ModMailThread} to
* CLOSED and notifies the user about closing.
* @param modMailThread The {@link ModMailThread} which is being closed.
- * @param note The text of the note used for the header message of the logged mod mail thread, this is only required when actually
- * logging the mod mail thread
- * @param notifyUser Whether or not the user should be notified
- * @param logThread Whether or not the thread should be logged to the appropriate post target
+ * @param closingConfig The {@link ClosingContext config} how the thread shoudl be closed
* @param undoActions A list of {@link dev.sheldan.abstracto.core.models.UndoAction actions} to be undone in case the operation fails. This list will be filled in the method.
* @return A {@link CompletableFuture future} which completes when the {@link ModMailThread thread} has been closed
*/
- CompletableFuture closeModMailThread(ModMailThread modMailThread, String note, boolean notifyUser, boolean logThread, List undoActions);
+ CompletableFuture closeModMailThread(ModMailThread modMailThread, ClosingContext closingConfig, List undoActions);
boolean isModMailThread(AChannel channel);
boolean isModMailThread(Long channelId);
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 86166a646..3d6a5ef19 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
@@ -281,7 +281,7 @@ public class ChannelServiceBean implements ChannelService {
@Override
public CompletableFuture editMessageInAChannelFuture(MessageToSend messageToSend, MessageChannel channel, Long messageId) {
MessageAction messageAction;
- if(!StringUtils.isBlank(messageToSend.getMessages().get(0))) {
+ if(!messageToSend.getMessages().isEmpty() && !StringUtils.isBlank(messageToSend.getMessages().get(0))) {
log.debug("Editing message {} with new text content.", messageId);
messageAction = channel.editMessageById(messageId, messageToSend.getMessages().get(0));
if(messageToSend.getEmbeds() != null && !messageToSend.getEmbeds().isEmpty()) {
diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/MessageServiceBean.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/MessageServiceBean.java
index 69428a25e..341c8ad90 100644
--- a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/MessageServiceBean.java
+++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/MessageServiceBean.java
@@ -205,6 +205,12 @@ public class MessageServiceBean implements MessageService {
return loadMessage(message.getGuild().getIdLong(), message.getChannel().getIdLong(), message.getIdLong());
}
+ @Override
+ public CompletableFuture editMessageWithNewTemplate(Message message, String templateKey, Object model) {
+ MessageToSend messageToSend = templateService.renderEmbedTemplate(templateKey, model, message.getGuild().getIdLong());
+ return channelService.editMessageInAChannelFuture(messageToSend, message.getChannel(), message.getIdLong());
+ }
+
@Override
public MessageAction editMessage(Message message, MessageEmbed messageEmbed) {
metricService.incrementCounter(MESSAGE_EDIT_METRIC);
diff --git a/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/service/MessageService.java b/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/service/MessageService.java
index bcc4313f0..6ec32c397 100644
--- a/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/service/MessageService.java
+++ b/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/service/MessageService.java
@@ -35,6 +35,7 @@ public interface MessageService {
CompletableFuture loadMessageFromCachedMessage(CachedMessage cachedMessage);
CompletableFuture loadMessage(Long serverId, Long channelId, Long messageId);
CompletableFuture loadMessage(Message message);
+ CompletableFuture editMessageWithNewTemplate(Message message, String templateKey, Object model);
MessageAction editMessage(Message message, MessageEmbed messageEmbed);
MessageAction editMessage(Message message, String text, MessageEmbed messageEmbed);
AuditableRestAction deleteMessageWithAction(Message message);
diff --git a/abstracto-application/documentation/src/main/docs/asciidoc/modules/modmail.adoc b/abstracto-application/documentation/src/main/docs/asciidoc/modules/modmail.adoc
index c845184ef..d63bccc6d 100644
--- a/abstracto-application/documentation/src/main/docs/asciidoc/modules/modmail.adoc
+++ b/abstracto-application/documentation/src/main/docs/asciidoc/modules/modmail.adoc
@@ -74,8 +74,4 @@ Closing the mod mail thread without notifying the user::
* Usage: `closeSilently [note]`
* Description: Closes the thread, deletes the text channel containing the thread and logs the interactions between the member and the moderators in the `modmailLog` post target. (only if `modmail_logging` is enabled)
When closing a thread, a closing header with general information will be send and the note will be displayed there.
-Close a thread without logging::
-* Usage: `closeNoLog`
-* Description: Closes the thread without notifying the user and without logging the messages.
-* Mode Restriction: This command is only available when the feature mode `log` is enabled.