mirror of
https://github.com/Sheldan/abstracto.git
synced 2026-01-19 22:43:31 +00:00
[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
This commit is contained in:
@@ -44,11 +44,6 @@
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.google.code.gson</groupId>
|
||||
<artifactId>gson</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>dev.sheldan.abstracto.core</groupId>
|
||||
<artifactId>metrics-int</artifactId>
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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<CommandResult> executeAsync(CommandContext commandContext) {
|
||||
List<Object> 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());
|
||||
}
|
||||
|
||||
|
||||
@@ -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<CommandResult> 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<CommandCondition> getConditions() {
|
||||
List<CommandCondition> conditions = super.getConditions();
|
||||
conditions.add(requiresModMailCondition);
|
||||
return conditions;
|
||||
}
|
||||
|
||||
/**
|
||||
* This command is only available if the LOGGING feature mode is enabled
|
||||
*/
|
||||
@Override
|
||||
public List<FeatureMode> getFeatureModeLimitations() {
|
||||
return Arrays.asList(ModMailMode.LOGGING);
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
@@ -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<Member> 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;
|
||||
}
|
||||
|
||||
@@ -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<ModMailMessage> modMailMessages) {
|
||||
public CompletableFuture<ModmailLoggingThreadMessages> loadModMailMessages(List<ModMailMessage> modMailMessages) {
|
||||
if(modMailMessages.isEmpty()) {
|
||||
return LoadedModmailThreadMessageList.builder().build();
|
||||
return CompletableFuture.completedFuture(ModmailLoggingThreadMessages.builder().build());
|
||||
}
|
||||
CompletableFuture<ModmailLoggingThreadMessages> 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<LoadedModmailThreadMessage> 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<Long> messageIdsToLoad = messageIds
|
||||
.stream()
|
||||
.map(ServerChannelMessageUser::getMessageId)
|
||||
.collect(Collectors.toList());
|
||||
Optional<TextChannel> 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<LoadedModmailThreadMessage> iterator = messageFutures.iterator();
|
||||
messageIds.forEach(serverChannelMessage -> {
|
||||
log.debug("Loading message {}.", serverChannelMessage.getMessageId());
|
||||
CompletableFuture<Message> messageFuture;
|
||||
CompletableFuture<Member> 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<ServerChannelMessageUser> latestThreadMessageOptional = messageIds
|
||||
.stream()
|
||||
.filter(serverChannelMessageUser -> serverChannelMessageUser.getChannelId() != null)
|
||||
.max(Comparator.comparing(ServerChannelMessageUser::getMessageId));
|
||||
Optional<ServerChannelMessageUser> latestPrivateMessageOptional = messageIds
|
||||
.stream()
|
||||
.filter(serverChannelMessageUser -> serverChannelMessageUser.getChannelId() == null)
|
||||
.max(Comparator.comparing(ServerChannelMessageUser::getMessageId));
|
||||
CompletableFuture<MessageHistory> threadHistoryFuture;
|
||||
if(latestThreadMessageOptional.isPresent()) {
|
||||
ServerChannelMessageUser latestPrivateMessage = latestThreadMessageOptional.get();
|
||||
threadHistoryFuture = modMailThread.getHistoryAround(latestPrivateMessage.getMessageId(), HISTORY_RETRIEVAL_LIMIT).submit();
|
||||
} else {
|
||||
threadHistoryFuture = CompletableFuture.completedFuture(null);
|
||||
}
|
||||
CompletableFuture<MessageHistory> privateHistoryFuture;
|
||||
if(latestPrivateMessageOptional.isPresent()) {
|
||||
ServerChannelMessageUser latestThreadMessage = latestPrivateMessageOptional.get();
|
||||
privateHistoryFuture = privateChannel.getHistoryAround(latestThreadMessage.getMessageId(), HISTORY_RETRIEVAL_LIMIT).submit();
|
||||
} else {
|
||||
privateHistoryFuture = CompletableFuture.completedFuture(null);
|
||||
}
|
||||
List<Message> loadedMessages = new ArrayList<>();
|
||||
CompletableFuture.allOf(threadHistoryFuture, privateHistoryFuture)
|
||||
.thenCompose(unused -> loadMoreMessages(messageIdsToLoad, privateHistoryFuture.join(), modMailThread, threadHistoryFuture.join(), privateChannel, loadedMessages, 0))
|
||||
.thenAccept(unused -> {
|
||||
Set<Long> userIds = messageIds
|
||||
.stream()
|
||||
.map(ServerChannelMessageUser::getUserId)
|
||||
.collect(Collectors.toSet());
|
||||
CompletableFutureList<User> 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<Void> loadMoreMessages(List<Long> messagesToLoad,
|
||||
MessageHistory privateMessageHistory, TextChannel thread,
|
||||
MessageHistory threadMessageHistory, PrivateChannel dmChannel, List<Message> 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<Long, Message> threadMessagesInStep = mapHistoryToMessageIds(threadMessageHistory);
|
||||
Map<Long, Message> privateMessagesInStep = mapHistoryToMessageIds(privateMessageHistory);
|
||||
List<Long> 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<MessageHistory> threadHistoryAction;
|
||||
if(doesHistoryContainValues(threadMessageHistory)) {
|
||||
Optional<Message> 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<MessageHistory> privateHistoryAction;
|
||||
if(doesHistoryContainValues(privateMessageHistory)) {
|
||||
Optional<Message> 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<Long, Message> 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<Message> getOldestMessage(List<Message> messages) {
|
||||
return messages.stream().min(Comparator.comparing(ISnowflake::getTimeCreated));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<TextChannel> 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<Void> 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<Void> performModMailThreadSetup(Member member, Message initialMessage, TextChannel channel, boolean userInitiated, List<UndoActionInstance> undoActions) {
|
||||
public CompletableFuture<Void> performModMailThreadSetup(Member member, Message initialMessage, TextChannel channel, boolean userInitiated, List<UndoActionInstance> 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<Void> headerFuture = sendModMailHeader(channel, member);
|
||||
CompletableFuture<Message> 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<AUserInAServer> 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<Guild> 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<ServerChoice> availableGuilds = new ArrayList<>();
|
||||
HashMap<String, Long> 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<Void> 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<Void> 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<TextChannel> 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<Message> relayMessage(Message messageFromUser, Long serverId, Long channelId, Long modmailThreadId, Member member) {
|
||||
Optional<TextChannel> 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<FullUserInServer> subscribers = new ArrayList<>();
|
||||
CompletableFuture<Message> messageFuture = new CompletableFuture<>();
|
||||
FutureUtils.toSingleFutureGeneric(subscriberMemberFutures).whenComplete((unused, throwable) -> {
|
||||
if(throwable != null) {
|
||||
log.warn("Failed to load subscriber users. Still relaying message.", throwable);
|
||||
}
|
||||
List<Member> 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<CompletableFuture<Message>> 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<Void> relayMessageToDm(Long modmailThreadId, String text, Message replyCommandMessage, boolean anonymous, MessageChannel feedBack, List<UndoActionInstance> 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<Void> closeModMailThread(ModMailThread modMailThread, String note, boolean notifyUser, List<UndoActionInstance> undoActions, Boolean log) {
|
||||
public CompletableFuture<Void> closeModMailThreadEvaluateLogging(ModMailThread modMailThread, ClosingContext closingConfig, List<UndoActionInstance> 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<Void> closeModMailThread(ModMailThread modMailThread, String note, boolean notifyUser, boolean logThread, List<UndoActionInstance> undoActions) {
|
||||
public CompletableFuture<Void> closeModMailThread(ModMailThread modMailThread, ClosingContext closingConfig, List<UndoActionInstance> undoActions) {
|
||||
metricService.incrementCounter(MODMAIL_THREAD_CLOSED_COUNTER);
|
||||
Long modMailThreadId = modMailThread.getId();
|
||||
log.info("Starting closing procedure for thread {}", modMailThread.getId());
|
||||
List<ModMailMessage> modMailMessages = modMailThread.getMessages();
|
||||
Long userId = modMailThread.getUser().getUserReference().getId();
|
||||
Long serverId = modMailThread.getServer().getId();
|
||||
if(logThread) {
|
||||
LoadedModmailThreadMessageList messages = modMailMessageService.loadModMailMessages(modMailMessages);
|
||||
CompletableFuture<Void> 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<Void> logMessagesToModMailLog(String note, Boolean notifyUser, Long modMailThreadId, List<UndoActionInstance> undoActions, LoadedModmailThreadMessageList messages, Long serverId, Long userId) {
|
||||
log.debug("Logging {} modmail messages for modmail thread {}.", messages.getMessageList().size(), modMailThreadId);
|
||||
public CompletableFuture<Void> logMessagesToModMailLog(ClosingContext closingContext, Long modMailThreadId, List<UndoActionInstance> undoActions,
|
||||
ModmailLoggingThreadMessages messages, Long serverId, Long userId) {
|
||||
log.debug("Logging {} modmail messages for modmail thread {}.", messages.getMessages().size(), modMailThreadId);
|
||||
try {
|
||||
CompletableFutureList<Message> 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<Message> logModMailThread(Long modMailThreadId, LoadedModmailThreadMessageList messages, String note, List<UndoActionInstance> undoActions) {
|
||||
log.info("Logging mod mail thread {} with {} messages.", modMailThreadId, messages.getMessageList().size());
|
||||
public CompletableFuture<CompletableFutureList<Message>> logModMailThread(Long modMailThreadId, ModmailLoggingThreadMessages messages,
|
||||
ClosingContext context, List<UndoActionInstance> 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<CompletableFuture<Message>> 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<Message> logMessages(Long modMailThreadId, ModmailLoggingThreadMessages messages, ClosingContext context, Message updateMessage) {
|
||||
Optional<ModMailThread> modMailThreadOpt = modMailThreadManagementService.getByIdOptional(modMailThreadId);
|
||||
if(modMailThreadOpt.isPresent()) {
|
||||
ModMailThread modMailThread = modMailThreadOpt.get();
|
||||
List<ModMailLoggedMessageModel> loggedMessages = new ArrayList<>();
|
||||
messages.getMessageList().forEach(futures -> {
|
||||
try {
|
||||
CompletableFuture<Message> 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<Long, User> 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<CompletableFuture<Message>> 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<CompletableFuture<Message>> closeHeaderFutures = postTargetService.sendEmbedInPostTarget(messageToSend, ModMailPostTargets.MOD_MAIL_LOG, modMailThread.getServer().getId());
|
||||
CompletableFuture<Message> 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<Message> 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<CompletableFuture<Message>> 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<CompletableFuture<Message>> sendMessagesToPostTarget(ModMailThread modMailThread, List<ModMailLoggedMessageModel> loadedMessages) {
|
||||
public List<CompletableFuture<Message>> sendMessagesToPostTarget(ModMailThread modMailThread, List<ModMailLoggedMessageModel> loadedMessages, Message updateMessage) {
|
||||
List<CompletableFuture<Message>> 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<CompletableFuture<Message>> 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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -20,11 +20,6 @@
|
||||
<column name="module_id" valueComputed="${modmailModule}"/>
|
||||
<column name="feature_id" valueComputed="${modmailFeature}"/>
|
||||
</insert>
|
||||
<insert tableName="command">
|
||||
<column name="name" value="closeNoLog"/>
|
||||
<column name="module_id" valueComputed="${modmailModule}"/>
|
||||
<column name="feature_id" valueComputed="${modmailFeature}"/>
|
||||
</insert>
|
||||
<insert tableName="command">
|
||||
<column name="name" value="closeSilently"/>
|
||||
<column name="module_id" valueComputed="${modmailModule}"/>
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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<Message> messageFuture;
|
||||
private CompletableFuture<Member> memberFuture;
|
||||
}
|
||||
@@ -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<LoadedModmailThreadMessage> messageList;
|
||||
public List<CompletableFuture> getAllFutures() {
|
||||
List<CompletableFuture> futures = new ArrayList<>();
|
||||
messageList.forEach(loadedModmailThreadMessage -> {
|
||||
futures.add(loadedModmailThreadMessage.getMemberFuture());
|
||||
futures.add(loadedModmailThreadMessage.getMessageFuture());
|
||||
});
|
||||
return futures;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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<FullUserInServer> subscribers;
|
||||
private List<Member> subscribers;
|
||||
}
|
||||
|
||||
@@ -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<Message> messages;
|
||||
private List<User> authors;
|
||||
}
|
||||
@@ -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<ModMailMessage> modMailMessages);
|
||||
CompletableFuture<ModmailLoggingThreadMessages> loadModMailMessages(List<ModMailMessage> modMailMessages);
|
||||
}
|
||||
|
||||
@@ -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<Void> closeModMailThread(ModMailThread modMailThread, String note, boolean notifyUser, List<UndoActionInstance> undoActions, Boolean log);
|
||||
CompletableFuture<Void> closeModMailThreadEvaluateLogging(ModMailThread modMailThread, ClosingContext closingConfig, List<UndoActionInstance> 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<Void> closeModMailThread(ModMailThread modMailThread, String note, boolean notifyUser, boolean logThread, List<UndoActionInstance> undoActions);
|
||||
CompletableFuture<Void> closeModMailThread(ModMailThread modMailThread, ClosingContext closingConfig, List<UndoActionInstance> undoActions);
|
||||
|
||||
boolean isModMailThread(AChannel channel);
|
||||
boolean isModMailThread(Long channelId);
|
||||
|
||||
@@ -281,7 +281,7 @@ public class ChannelServiceBean implements ChannelService {
|
||||
@Override
|
||||
public CompletableFuture<Message> 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()) {
|
||||
|
||||
@@ -205,6 +205,12 @@ public class MessageServiceBean implements MessageService {
|
||||
return loadMessage(message.getGuild().getIdLong(), message.getChannel().getIdLong(), message.getIdLong());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Message> 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);
|
||||
|
||||
@@ -35,6 +35,7 @@ public interface MessageService {
|
||||
CompletableFuture<Message> loadMessageFromCachedMessage(CachedMessage cachedMessage);
|
||||
CompletableFuture<Message> loadMessage(Long serverId, Long channelId, Long messageId);
|
||||
CompletableFuture<Message> loadMessage(Message message);
|
||||
CompletableFuture<Message> editMessageWithNewTemplate(Message message, String templateKey, Object model);
|
||||
MessageAction editMessage(Message message, MessageEmbed messageEmbed);
|
||||
MessageAction editMessage(Message message, String text, MessageEmbed messageEmbed);
|
||||
AuditableRestAction<Void> deleteMessageWithAction(Message message);
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user