[AB-358] upgrading to JDA 5

removal of jda-utils
adding message context commands
[AB-360] fixing confirmation buttons being triggered by somebody not the author
This commit is contained in:
Sheldan
2022-02-27 23:51:06 +01:00
parent 17470f9718
commit 09450429dd
248 changed files with 2362 additions and 1482 deletions

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>modmail</artifactId>
<version>1.3.14-SNAPSHOT</version>
<version>1.4.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -0,0 +1,98 @@
package dev.sheldan.abstracto.modmail.listener;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.config.ListenerPriority;
import dev.sheldan.abstracto.core.listener.ButtonClickedListenerResult;
import dev.sheldan.abstracto.core.listener.async.jda.ButtonClickedListener;
import dev.sheldan.abstracto.core.models.UndoActionInstance;
import dev.sheldan.abstracto.core.models.listener.ButtonClickedListenerModel;
import dev.sheldan.abstracto.core.service.*;
import dev.sheldan.abstracto.core.service.management.ComponentPayloadManagementService;
import dev.sheldan.abstracto.modmail.config.ModMailFeatureDefinition;
import dev.sheldan.abstracto.modmail.model.dto.ServerChoicePayload;
import dev.sheldan.abstracto.modmail.model.dto.ServiceChoicesPayload;
import dev.sheldan.abstracto.modmail.service.ModMailThreadService;
import dev.sheldan.abstracto.modmail.service.ModMailThreadServiceBean;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
@Component
@Slf4j
public class ModMailInitialButtonListener implements ButtonClickedListener {
@Autowired
private MemberService memberService;
@Autowired
private ModMailThreadService modMailThreadService;
@Autowired
private UndoActionService undoActionService;
@Autowired
private MessageService messageService;
@Autowired
private ComponentPayloadManagementService componentPayloadService;
@Autowired
private ModMailInitialButtonListener self;
@Autowired
private ChannelService channelService;
@Override
public ButtonClickedListenerResult execute(ButtonClickedListenerModel model) {
ServiceChoicesPayload choices = (ServiceChoicesPayload) model.getDeserializedPayload();
ServerChoicePayload chosenServer = choices.getChoices().get(model.getEvent().getComponentId());
Long userId = choices.getUserId();
log.debug("Executing action for creationg a modmail thread in server {} for user {}.", chosenServer.getServerId(), userId);
ArrayList<UndoActionInstance> undoActions = new ArrayList<>();
memberService.getMemberInServerAsync(chosenServer.getServerId(), userId)
.thenCompose(member -> channelService.retrieveMessageInChannel(model.getEvent().getChannel(), choices.getMessageId())
.thenCompose(originalMessage -> {
try {
return modMailThreadService.createModMailThreadForUser(member, originalMessage, model.getEvent().getChannel(), true, undoActions);
} catch (Exception ex) {
log.error("Failed to setup thread correctly", ex);
undoActionService.performActions(undoActions);
return null;
}
})
.thenAccept(unused -> self.cleanup(model)))
.exceptionally(throwable -> {
log.error("Failed to setup thread correctly", throwable);
undoActionService.performActions(undoActions);
return null;
});
return ButtonClickedListenerResult.ACKNOWLEDGED;
}
@Transactional
public void cleanup(ButtonClickedListenerModel model) {
ServiceChoicesPayload choices = (ServiceChoicesPayload) model.getDeserializedPayload();
choices.getChoices().keySet().forEach(componentId -> componentPayloadService.deletePayload(componentId));
}
@Override
public Boolean handlesEvent(ButtonClickedListenerModel model) {
return ModMailThreadServiceBean.MODMAIL_INITIAL_ORIGIN.equals(model.getOrigin()) && !model.getEvent().isFromGuild();
}
@Override
public Integer getPriority() {
return ListenerPriority.LOW;
}
@Override
public FeatureDefinition getFeature() {
return ModMailFeatureDefinition.MOD_MAIL;
}
}

View File

@@ -72,9 +72,9 @@ public class ModMailMessageServiceBean implements ModMailMessageService {
.stream()
.map(ServerChannelMessageUser::getMessageId)
.collect(Collectors.toList());
Optional<TextChannel> textChannelFromServer = channelService.getTextChannelFromServerOptional(thread.getServer().getId(), thread.getChannel().getId());
Optional<GuildMessageChannel> textChannelFromServer = channelService.getMessageChannelFromServerOptional(thread.getServer().getId(), thread.getChannel().getId());
if(textChannelFromServer.isPresent()) {
TextChannel modMailThread = textChannelFromServer.get();
GuildMessageChannel modMailThread = textChannelFromServer.get();
Long userId = thread.getUser().getUserReference().getId();
botService.getInstance().openPrivateChannelById(userId).queue(privateChannel -> {
Optional<ServerChannelMessageUser> latestThreadMessageOptional = messageIds
@@ -125,7 +125,7 @@ public class ModMailMessageServiceBean implements ModMailMessageService {
}
public CompletableFuture<Void> loadMoreMessages(Integer totalMessageCount, List<Long> messagesToLoad,
MessageHistory privateMessageHistory, TextChannel thread,
MessageHistory privateMessageHistory, GuildMessageChannel thread,
MessageHistory threadMessageHistory, PrivateChannel dmChannel, List<Message> loadedMessages, Integer counter) {
// TODO maybe find a better mechanism for this... one which does not lead to infinite loops, but also doesnt miss out on history
if(counter.equals(totalMessageCount * 2)) {

View File

@@ -1,7 +1,5 @@
package dev.sheldan.abstracto.modmail.service;
import com.jagrosh.jdautilities.commons.waiter.EventWaiter;
import com.jagrosh.jdautilities.menu.ButtonMenu;
import dev.sheldan.abstracto.core.command.exception.AbstractoTemplatedException;
import dev.sheldan.abstracto.core.exception.AbstractoRunTimeException;
import dev.sheldan.abstracto.core.metric.service.CounterMetric;
@@ -26,8 +24,10 @@ 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.dto.ServiceChoicesPayload;
import dev.sheldan.abstracto.modmail.model.template.ServerChoices;
import dev.sheldan.abstracto.modmail.model.database.*;
import dev.sheldan.abstracto.modmail.model.dto.ServerChoice;
import dev.sheldan.abstracto.modmail.model.template.ServerChoice;
import dev.sheldan.abstracto.modmail.model.template.*;
import dev.sheldan.abstracto.modmail.service.management.ModMailMessageManagementService;
import dev.sheldan.abstracto.modmail.service.management.ModMailRoleManagementService;
@@ -125,9 +125,6 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
@Autowired
private ModMailSubscriberManagementService modMailSubscriberManagementService;
@Autowired
private EventWaiter eventWaiter;
@Autowired
private FeatureModeService featureModeService;
@@ -146,6 +143,12 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
@Autowired
private MetricService metricService;
@Autowired
private ComponentService componentService;
@Autowired
private ComponentPayloadService componentPayloadService;
public static final String MODMAIL_THREAD_METRIC = "modmail.threads";
public static final String MODMAIL_MESSAGE_METRIC = "modmail.messges";
public static final String ACTION = "action";
@@ -174,14 +177,7 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
.tagList(Arrays.asList(MetricTag.getTag(MESSAGE_DIRECTION, "sent")))
.build();
/**
* The emoji used when the user can decide for a server to open a mod mail thread in.
*/
private static List<String> NUMBER_EMOJI = Arrays.asList("\u0031\u20e3", "\u0032\u20e3", "\u0033\u20e3",
"\u0034\u20e3", "\u0035\u20e3", "\u0036\u20e3",
"\u0037\u20e3", "\u0038\u20e3", "\u0039\u20e3",
"\u0040\u20e3");
public static final String MODMAIL_INITIAL_ORIGIN = "modmailInitial";
@Override
public CompletableFuture<Void> createModMailThreadForUser(Member member, Message initialMessage, MessageChannel feedBackChannel, boolean userInitiated, List<UndoActionInstance> undoActions) {
@@ -315,34 +311,29 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
@Override
public void createModMailPrompt(AUser user, Message initialMessage) {
List<AUserInAServer> knownServers = userInServerManagementService.getUserInAllServers(user.getId());
// 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 the bot.", knownServers.size());
List<AServer> servers = new ArrayList<>();
List<Guild> mutualServers = initialMessage.getJDA().getMutualGuilds(initialMessage.getAuthor());
mutualServers.forEach(guild -> {
AServer server = serverManagementService.loadServer(guild);
servers.add(server);
});
if(!servers.isEmpty()) {
log.info("There are {} shared servers between user and the bot.", servers.size());
List<ServerChoice> availableGuilds = new ArrayList<>();
HashMap<String, Long> choices = new HashMap<>();
for (int i = 0; i < knownServers.size(); i++) {
AUserInAServer aUserInAServer = knownServers.get(i);
for (AServer server : servers) {
// only take the servers in which mod mail is actually enabled, would not make much sense to make the
// other servers available
if(featureFlagService.isFeatureEnabled(modMailFeatureConfig, aUserInAServer.getServerReference())) {
AServer serverReference = aUserInAServer.getServerReference();
if (featureFlagService.isFeatureEnabled(modMailFeatureConfig, server)) {
FullGuild guild = FullGuild
.builder()
.guild(guildService.getGuildById(serverReference.getId()))
.server(serverReference)
.guild(guildService.getGuildById(server.getId()))
.server(server)
.build();
ServerChoice serverChoice = ServerChoice
.builder()
.serverId(guild.getGuild().getIdLong())
.serverName(guild.getGuild().getName())
.build();
// TODO support more than this limited amount of servers
String reactionEmote = NUMBER_EMOJI.get(i);
ServerChoice serverChoice = ServerChoice.builder().guild(guild).reactionEmote(reactionEmote).build();
choices.put(reactionEmote, aUserInAServer.getServerReference().getId());
availableGuilds.add(serverChoice);
}
}
@@ -350,40 +341,30 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
// if more than 1 server is available, show a choice dialog
ArrayList<UndoActionInstance> undoActions = new ArrayList<>();
if(availableGuilds.size() > 1) {
Map<String, ServerChoice> choices = new HashMap<>();
ServerChoices serverChoices = ServerChoices
.builder()
.commonGuilds(choices)
.userId(initialMessage.getAuthor().getIdLong())
.messageId(initialMessage.getIdLong())
.build();
availableGuilds.forEach(serverChoice -> choices.put(componentService.generateComponentId(), serverChoice));
ModMailServerChooserModel modMailServerChooserModel = ModMailServerChooserModel
.builder()
.commonGuilds(availableGuilds)
.build();
String text = templateService.renderTemplate("modmail_modal_server_choice", modMailServerChooserModel);
ButtonMenu menu = new ButtonMenu.Builder()
.setChoices(choices.keySet().toArray(new String[0]))
.setEventWaiter(eventWaiter)
.setDescription(text)
.setAction(reactionEmote -> {
Long chosenServerId = choices.get(reactionEmote.getEmoji());
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, undoActions);
} 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);
undoActionService.performActions(undoActions);
return null;
});
})
.choices(serverChoices)
.build();
MessageToSend messageToSend = templateService.renderEmbedTemplate("modmail_modal_server_choice", modMailServerChooserModel);
FutureUtils.toSingleFutureGeneric(channelService.sendMessageToSendToChannel(messageToSend, initialMessage.getChannel()))
.thenAccept(unused -> self.persistInitialCallbacks(serverChoices))
.exceptionally(throwable -> {
log.error("Failed to setup prompt message correctly", throwable);
undoActionService.performActions(undoActions);
return null;
});
log.debug("Displaying server choice message for user {} in channel {}.", user.getId(), initialMessage.getChannel().getId());
menu.display(initialMessage.getChannel());
} else if(availableGuilds.size() == 1) {
// if exactly one server is available, open the thread directly
Long chosenServerId = choices.get(availableGuilds.get(0).getReactionEmote());
Long chosenServerId = availableGuilds.get(0).getServerId();
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 -> {
try {
@@ -408,6 +389,13 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
}
}
@Transactional
public void persistInitialCallbacks(ServerChoices choices) {
ServiceChoicesPayload payload = ServiceChoicesPayload.fromServerChoices(choices);
choices.getCommonGuilds().keySet().forEach(componentId ->
componentPayloadService.createButtonPayload(componentId, payload, MODMAIL_INITIAL_ORIGIN, null));
}
/**
* Method used to send the header of a newly created mod mail thread. This message contains information about
@@ -446,10 +434,10 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
@Transactional
public CompletableFuture<Message> relayMessage(Message messageFromUser, Long serverId, Long channelId, Long modmailThreadId, Member member) {
Optional<TextChannel> textChannelFromServer = channelService.getTextChannelFromServerOptional(serverId, channelId);
Optional<GuildMessageChannel> textChannelFromServer = channelService.getMessageChannelFromServerOptional(serverId, channelId);
if(textChannelFromServer.isPresent()) {
TextChannel textChannel = textChannelFromServer.get();
return self.sendUserReply(textChannel, modmailThreadId, messageFromUser, member, true);
GuildMessageChannel guildMessageChannel = textChannelFromServer.get();
return self.sendUserReply(guildMessageChannel, 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
@@ -464,14 +452,14 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
* 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
* side.
* @param textChannel The {@link TextChannel} in which the {@link ModMailThread} is being handled
* @param textChannel The {@link GuildMessageChannel} in which the {@link ModMailThread} is being handled
* @param modMailThreadId The id of the modmail thread to which the received {@link Message} is a reply to, can be null, if it is null, its the initial message
* @param messageFromUser The received message from the user
* @param member The {@link Member} instance from the user the thread is about. It is used as author
* @param modMailThreadExists Whether or not the modmail thread already exists and is persisted.
* @return A {@link CompletableFuture} which resolves when the post processing of the message is completed (adding read notification, and storing messageIDs)
*/
public CompletableFuture<Message> sendUserReply(TextChannel textChannel, Long modMailThreadId, Message messageFromUser, Member member, boolean modMailThreadExists) {
public CompletableFuture<Message> sendUserReply(GuildMessageChannel textChannel, Long modMailThreadId, Message messageFromUser, Member member, boolean modMailThreadExists) {
List<CompletableFuture<Member>> subscriberMemberFutures = new ArrayList<>();
if(modMailThreadExists) {
ModMailThread modMailThread = modMailThreadManagementService.getById(modMailThreadId);
@@ -551,7 +539,7 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
* @param messageFromUser The {@link Message} object which was sent from the user
*/
@Transactional
public void postProcessSendMessages(TextChannel textChannel, Message messageInModMailThread, Message messageFromUser) {
public void postProcessSendMessages(GuildMessageChannel textChannel, Message messageInModMailThread, Message messageFromUser) {
Optional<ModMailThread> modMailThreadOpt = modMailThreadManagementService.getByChannelIdOptional(textChannel.getIdLong());
if(modMailThreadOpt.isPresent()) {
ModMailThread modMailThread = modMailThreadOpt.get();
@@ -793,7 +781,7 @@ public class ModMailThreadServiceBean implements ModMailThreadService {
log.info("Modmail thread {} is empty. No messages to log.", modMailThreadId);
return CompletableFuture.completedFuture(new CompletableFutureList<>(new ArrayList<>()));
}
TextChannel channel = channelService.getTextChannelFromServer(serverId, modMailThreadId);
GuildMessageChannel channel = channelService.getMessageChannelFromServer(serverId, modMailThreadId);
ClosingProgressModel progressModel = ClosingProgressModel
.builder()
.loggedMessages(0)

View File

@@ -5,7 +5,6 @@ import dev.sheldan.abstracto.modmail.model.template.ModMailCategoryActionModel;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import net.dv8tion.jda.api.entities.Category;
/**
* This represents both an instance of a {@link DelayedActionConfig} used to be executed in the
@@ -19,7 +18,6 @@ import net.dv8tion.jda.api.entities.Category;
public class ModMailCategoryDelayedActionConfig implements DelayedActionConfig {
private Long serverId;
private Long categoryId;
private Category category;
@Override
public String getTemplateName() {
@@ -30,7 +28,7 @@ public class ModMailCategoryDelayedActionConfig implements DelayedActionConfig {
public Object getTemplateModel() {
return ModMailCategoryActionModel
.builder()
.category(this.category)
.serverId(serverId)
.categoryId(categoryId)
.build();
}

View File

@@ -5,6 +5,7 @@ import dev.sheldan.abstracto.core.models.AServerChannelUserId;
import dev.sheldan.abstracto.core.models.FeatureValidationResult;
import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.models.listener.MessageReceivedModel;
import dev.sheldan.abstracto.core.service.ConfigService;
import dev.sheldan.abstracto.core.service.GuildService;
import dev.sheldan.abstracto.core.service.management.ChannelManagementService;
@@ -82,7 +83,10 @@ public class ModMailCategorySetupBean implements ModMailCategorySetup {
Long categoryId = configService.getLongValue(ModMailThreadServiceBean.MODMAIL_CATEGORY, user.getGuildId());
log.debug("Previous modmail category exists for server {}. Loading value {}.", guild.getId(), categoryId);
Category category = guild.getCategoryById(categoryId);
model.setCategory(category);
if(category != null) {
model.setCategoryId(category.getIdLong());
}
model.setServerId(user.getGuildId());
}
log.info("Executing mod mail category setup for server {}.", user.getGuildId());
String messageText = templateService.renderTemplate(messageTemplateKey, model);
@@ -90,8 +94,8 @@ public class ModMailCategorySetupBean implements ModMailCategorySetup {
CompletableFuture<SetupStepResult> future = new CompletableFuture<>();
AUserInAServer aUserInAServer = userInServerManagementService.loadOrCreateUser(user.getGuildId(), user.getUserId());
Runnable finalAction = getTimeoutRunnable(user.getGuildId(), user.getChannelId());
Consumer<MessageReceivedEvent> configAction = (MessageReceivedEvent event) -> {
Consumer<MessageReceivedModel> finalAction = getTimeoutConsumer(user.getGuildId(), user.getChannelId());
Consumer<MessageReceivedModel> configAction = (MessageReceivedModel event) -> {
try {
SetupStepResult result;
@@ -113,10 +117,14 @@ public class ModMailCategorySetupBean implements ModMailCategorySetup {
ModMailCategoryDelayedActionConfig build = ModMailCategoryDelayedActionConfig
.builder()
.serverId(user.getGuildId())
.category(guild.getCategoryById(categoryId))
.categoryId(categoryId)
.build();
List<DelayedActionConfig> delayedSteps = Arrays.asList(build);
DelayedActionConfigContainer container = DelayedActionConfigContainer
.builder()
.type(build.getClass())
.object(build)
.build();
List<DelayedActionConfigContainer> delayedSteps = Arrays.asList(container);
result = SetupStepResult
.builder()
.result(SetupStepResultType.SUCCESS)
@@ -135,12 +143,12 @@ public class ModMailCategorySetupBean implements ModMailCategorySetup {
future.completeExceptionally(new SetupStepException(e));
}
};
interactiveService.createMessageWithResponse(messageText, aUserInAServer, channel, parameter.getPreviousMessageId(), configAction, finalAction);
interactiveService.createMessageWithResponse(messageText, aUserInAServer, channel, configAction, finalAction);
return future;
}
protected Runnable getTimeoutRunnable(Long serverId, Long channelId) {
return () -> interactiveUtils.sendTimeoutMessage(serverId, channelId);
protected Consumer<MessageReceivedModel> getTimeoutConsumer(Long serverId, Long channelId) {
return (MessageReceivedModel) -> interactiveUtils.sendTimeoutMessage(serverId, channelId);
}
protected boolean checkForExit(Message message) {