mirror of
https://github.com/Sheldan/abstracto.git
synced 2026-04-02 16:05:00 +00:00
[AB-296] adding support for buttons
adding buttons for message embed via feature mode
This commit is contained in:
@@ -44,6 +44,11 @@
|
||||
<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>
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
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;
|
||||
import dev.sheldan.abstracto.core.metric.service.MetricService;
|
||||
import dev.sheldan.abstracto.core.models.listener.ButtonClickedListenerModel;
|
||||
import dev.sheldan.abstracto.core.interaction.InteractionService;
|
||||
import dev.sheldan.abstracto.core.service.MessageService;
|
||||
import dev.sheldan.abstracto.core.service.management.ComponentPayloadManagementService;
|
||||
import dev.sheldan.abstracto.core.templating.service.TemplateService;
|
||||
import dev.sheldan.abstracto.linkembed.config.LinkEmbedFeatureDefinition;
|
||||
import dev.sheldan.abstracto.linkembed.exception.LinkEmbedRemovalNotAllowedException;
|
||||
import dev.sheldan.abstracto.linkembed.model.template.MessageEmbedDeleteButtonPayload;
|
||||
import dev.sheldan.abstracto.linkembed.service.MessageEmbedMetricService;
|
||||
import dev.sheldan.abstracto.linkembed.service.MessageEmbedServiceBean;
|
||||
import dev.sheldan.abstracto.linkembed.service.management.MessageEmbedPostManagementService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.dv8tion.jda.api.events.interaction.ButtonClickEvent;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Component
|
||||
@Slf4j
|
||||
public class MessageEmbedDeleteButtonClickedListener implements ButtonClickedListener {
|
||||
|
||||
@Autowired
|
||||
private Gson gson;
|
||||
|
||||
@Autowired
|
||||
private MessageService messageService;
|
||||
|
||||
@Autowired
|
||||
private MetricService metricService;
|
||||
|
||||
@Autowired
|
||||
private MessageEmbedPostManagementService messageEmbedPostManagementService;
|
||||
|
||||
@Autowired
|
||||
private MessageEmbedMetricService messageEmbedMetricService;
|
||||
|
||||
@Autowired
|
||||
private InteractionService interactionService;
|
||||
|
||||
@Autowired
|
||||
private TemplateService templateService;
|
||||
|
||||
@Autowired
|
||||
private ComponentPayloadManagementService componentPayloadManagementService;
|
||||
|
||||
@Autowired
|
||||
private MessageEmbedDeleteButtonClickedListener self;
|
||||
|
||||
@Override
|
||||
public ButtonClickedListenerResult execute(ButtonClickedListenerModel model) {
|
||||
ButtonClickEvent event = model.getEvent();
|
||||
MessageEmbedDeleteButtonPayload payload = (MessageEmbedDeleteButtonPayload) model.getDeserializedPayload();
|
||||
Long clickingUserId = event.getInteraction().getUser().getIdLong();
|
||||
boolean embeddedUserRemoves = clickingUserId.equals(payload.getEmbeddedUserId());
|
||||
if(embeddedUserRemoves || clickingUserId.equals(payload.getEmbeddingUserId())) {
|
||||
messageService.deleteMessageInChannelInServer(payload.getEmbeddingServerId(), payload.getEmbeddingChannelId(), payload.getEmbeddingMessageId())
|
||||
.thenAccept(aVoid -> self.executeAfterDeletion(payload, clickingUserId, embeddedUserRemoves, event.getComponentId()));
|
||||
} else {
|
||||
log.info("Not the original author or embedding user clicked the button of component {} in server {} in channel {} on message {}.",
|
||||
event.getComponentId(), event.getGuild().getIdLong(), event.getGuildChannel().getIdLong(), event.getMessageId());
|
||||
throw new LinkEmbedRemovalNotAllowedException();
|
||||
}
|
||||
return ButtonClickedListenerResult.ACKNOWLEDGED;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void executeAfterDeletion(MessageEmbedDeleteButtonPayload payload, Long clickingUserId, boolean embeddedUserRemoves, String componentId) {
|
||||
log.info("User {} deleted embedding message {} in channel {} in server {} from embedded message {} in channel {} and server {}.",
|
||||
clickingUserId, payload.getEmbeddingMessageId(), payload.getEmbeddingChannelId(), payload.getEmbeddingServerId(),
|
||||
payload.getEmbeddedMessageId(), payload.getEmbeddedChannelId(), payload.getEmbeddedServerId());
|
||||
messageEmbedMetricService.incrementMessageEmbedDeletedMetric(embeddedUserRemoves);
|
||||
componentPayloadManagementService.deletePayload(componentId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean handlesEvent(ButtonClickedListenerModel model) {
|
||||
return model.getOrigin().equals(MessageEmbedServiceBean.MESSAGE_EMBED_DELETE_ORIGIN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FeatureDefinition getFeature() {
|
||||
return LinkEmbedFeatureDefinition.LINK_EMBEDS;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Integer getPriority() {
|
||||
return 10;
|
||||
}
|
||||
}
|
||||
@@ -3,9 +3,6 @@ package dev.sheldan.abstracto.linkembed.listener;
|
||||
import dev.sheldan.abstracto.core.config.FeatureDefinition;
|
||||
import dev.sheldan.abstracto.core.listener.DefaultListenerResult;
|
||||
import dev.sheldan.abstracto.core.listener.async.jda.AsyncReactionAddedListener;
|
||||
import dev.sheldan.abstracto.core.metric.service.CounterMetric;
|
||||
import dev.sheldan.abstracto.core.metric.service.MetricService;
|
||||
import dev.sheldan.abstracto.core.metric.service.MetricTag;
|
||||
import dev.sheldan.abstracto.core.models.database.AEmote;
|
||||
import dev.sheldan.abstracto.core.models.listener.ReactionAddedModel;
|
||||
import dev.sheldan.abstracto.core.service.BotService;
|
||||
@@ -13,18 +10,14 @@ import dev.sheldan.abstracto.core.service.EmoteService;
|
||||
import dev.sheldan.abstracto.core.service.MessageService;
|
||||
import dev.sheldan.abstracto.linkembed.config.LinkEmbedFeatureDefinition;
|
||||
import dev.sheldan.abstracto.linkembed.model.database.EmbeddedMessage;
|
||||
import dev.sheldan.abstracto.linkembed.service.MessageEmbedMetricService;
|
||||
import dev.sheldan.abstracto.linkembed.service.management.MessageEmbedPostManagementService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
|
||||
import static dev.sheldan.abstracto.linkembed.listener.MessageEmbedListener.MESSAGE_EMBEDDED;
|
||||
import static dev.sheldan.abstracto.linkembed.listener.MessageEmbedListener.MESSAGE_EMBED_ACTION;
|
||||
|
||||
@Component
|
||||
@Slf4j
|
||||
public class MessageEmbedRemovalReactionListener implements AsyncReactionAddedListener {
|
||||
@@ -44,19 +37,7 @@ public class MessageEmbedRemovalReactionListener implements AsyncReactionAddedLi
|
||||
private EmoteService emoteService;
|
||||
|
||||
@Autowired
|
||||
private MetricService metricService;
|
||||
|
||||
private static final CounterMetric MESSAGE_EMBED_REMOVED_CREATOR = CounterMetric
|
||||
.builder()
|
||||
.name(MESSAGE_EMBEDDED)
|
||||
.tagList(Arrays.asList(MetricTag.getTag(MESSAGE_EMBED_ACTION, "removed.creator")))
|
||||
.build();
|
||||
|
||||
private static final CounterMetric MESSAGE_EMBED_REMOVED_SOURCE = CounterMetric
|
||||
.builder()
|
||||
.name(MESSAGE_EMBEDDED)
|
||||
.tagList(Arrays.asList(MetricTag.getTag(MESSAGE_EMBED_ACTION, "removed.source")))
|
||||
.build();
|
||||
private MessageEmbedMetricService messageEmbedMetricService;
|
||||
|
||||
@Override
|
||||
public DefaultListenerResult execute(ReactionAddedModel model) {
|
||||
@@ -75,11 +56,7 @@ public class MessageEmbedRemovalReactionListener implements AsyncReactionAddedLi
|
||||
messageService.deleteMessageInChannelInServer(serverId, channelId, messageId).thenAccept(aVoid -> {
|
||||
Optional<EmbeddedMessage> innerOptional = messageEmbedPostManagementService.findEmbeddedPostByMessageId(messageId);
|
||||
innerOptional.ifPresent(value -> messageEmbedPostManagementService.deleteEmbeddedMessage(value));
|
||||
if(embeddedUserRemoves) {
|
||||
metricService.incrementCounter(MESSAGE_EMBED_REMOVED_SOURCE);
|
||||
} else {
|
||||
metricService.incrementCounter(MESSAGE_EMBED_REMOVED_CREATOR);
|
||||
}
|
||||
messageEmbedMetricService.incrementMessageEmbedDeletedMetric(embeddedUserRemoves);
|
||||
});
|
||||
} else {
|
||||
log.debug("Somebody besides the original author and the user embedding added the removal reaction to the message {} in channel {} in server {}.",
|
||||
@@ -103,10 +80,4 @@ public class MessageEmbedRemovalReactionListener implements AsyncReactionAddedLi
|
||||
return LinkEmbedFeatureDefinition.LINK_EMBEDS;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void postConstruct() {
|
||||
metricService.registerCounter(MESSAGE_EMBED_REMOVED_CREATOR, "Message embeds which are created by the embedding user.");
|
||||
metricService.registerCounter(MESSAGE_EMBED_REMOVED_SOURCE, "Message embeds which are created by the embedded user.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
package dev.sheldan.abstracto.linkembed.service;
|
||||
|
||||
import dev.sheldan.abstracto.core.metric.service.CounterMetric;
|
||||
import dev.sheldan.abstracto.core.metric.service.MetricService;
|
||||
import dev.sheldan.abstracto.core.metric.service.MetricTag;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static dev.sheldan.abstracto.linkembed.listener.MessageEmbedListener.MESSAGE_EMBEDDED;
|
||||
import static dev.sheldan.abstracto.linkembed.listener.MessageEmbedListener.MESSAGE_EMBED_ACTION;
|
||||
|
||||
@Component
|
||||
public class MessageEmbedMetricServiceBean implements MessageEmbedMetricService {
|
||||
|
||||
private static final CounterMetric MESSAGE_EMBED_REMOVED_CREATOR = CounterMetric
|
||||
.builder()
|
||||
.name(MESSAGE_EMBEDDED)
|
||||
.tagList(Arrays.asList(MetricTag.getTag(MESSAGE_EMBED_ACTION, "removed.creator")))
|
||||
.build();
|
||||
|
||||
private static final CounterMetric MESSAGE_EMBED_REMOVED_SOURCE = CounterMetric
|
||||
.builder()
|
||||
.name(MESSAGE_EMBEDDED)
|
||||
.tagList(Arrays.asList(MetricTag.getTag(MESSAGE_EMBED_ACTION, "removed.source")))
|
||||
.build();
|
||||
|
||||
@Autowired
|
||||
private MetricService metricService;
|
||||
|
||||
@Override
|
||||
public void incrementMessageEmbedDeletedMetric(boolean embeddedUserDeleted) {
|
||||
if(embeddedUserDeleted) {
|
||||
incrementMessageEmbedDeletedEmbeddedMetric();
|
||||
} else {
|
||||
incrementMessageEmbedDeletedEmbeddingMetric();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void incrementMessageEmbedDeletedEmbeddedMetric() {
|
||||
metricService.incrementCounter(MESSAGE_EMBED_REMOVED_SOURCE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void incrementMessageEmbedDeletedEmbeddingMetric() {
|
||||
metricService.incrementCounter(MESSAGE_EMBED_REMOVED_CREATOR);
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void postConstruct() {
|
||||
metricService.registerCounter(MESSAGE_EMBED_REMOVED_CREATOR, "Message embeds which are deleted by the embedding user.");
|
||||
metricService.registerCounter(MESSAGE_EMBED_REMOVED_SOURCE, "Message embeds which are deleted by the embedded user.");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,8 +2,14 @@ package dev.sheldan.abstracto.linkembed.service;
|
||||
|
||||
import dev.sheldan.abstracto.core.models.ServerChannelMessage;
|
||||
import dev.sheldan.abstracto.core.models.cache.CachedMessage;
|
||||
import dev.sheldan.abstracto.core.models.database.AServer;
|
||||
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
|
||||
import dev.sheldan.abstracto.core.models.template.listener.MessageEmbeddedModel;
|
||||
import dev.sheldan.abstracto.core.models.template.button.ButtonConfigModel;
|
||||
import dev.sheldan.abstracto.core.service.management.ComponentPayloadManagementService;
|
||||
import dev.sheldan.abstracto.linkembed.config.LinkEmbedFeatureDefinition;
|
||||
import dev.sheldan.abstracto.linkembed.config.LinkEmbedFeatureMode;
|
||||
import dev.sheldan.abstracto.linkembed.model.template.MessageEmbedDeleteButtonPayload;
|
||||
import dev.sheldan.abstracto.linkembed.model.template.MessageEmbeddedModel;
|
||||
import dev.sheldan.abstracto.core.service.*;
|
||||
import dev.sheldan.abstracto.core.service.management.ChannelManagementService;
|
||||
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
|
||||
@@ -26,10 +32,7 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Function;
|
||||
import java.util.regex.Matcher;
|
||||
@@ -84,9 +87,20 @@ public class MessageEmbedServiceBean implements MessageEmbedService {
|
||||
@Autowired
|
||||
private EmoteService emoteService;
|
||||
|
||||
@Autowired
|
||||
private ComponentService componentServiceBean;
|
||||
|
||||
@Autowired
|
||||
private FeatureModeService featureModeService;
|
||||
|
||||
@Autowired
|
||||
private ComponentPayloadManagementService componentPayloadManagementService;
|
||||
|
||||
@Value("${abstracto.feature.linkEmbed.removalDays}")
|
||||
private Long embedRemovalDays;
|
||||
|
||||
public static final String MESSAGE_EMBED_DELETE_ORIGIN = "messageEmbedDelete";
|
||||
|
||||
@Override
|
||||
public List<MessageEmbedLink> getLinksInMessage(String message) {
|
||||
List<MessageEmbedLink> links = new ArrayList<>();
|
||||
@@ -126,8 +140,9 @@ public class MessageEmbedServiceBean implements MessageEmbedService {
|
||||
@Override
|
||||
@Transactional
|
||||
public CompletableFuture<Void> embedLink(CachedMessage cachedMessage, TextChannel target, Long userEmbeddingUserInServerId, Message embeddingMessage) {
|
||||
return buildTemplateParameter(embeddingMessage, cachedMessage).thenCompose(messageEmbeddedModel ->
|
||||
self.sendEmbeddingMessage(cachedMessage, target, userEmbeddingUserInServerId, messageEmbeddedModel)
|
||||
boolean deletionButtonEnabled = featureModeService.featureModeActive(LinkEmbedFeatureDefinition.LINK_EMBEDS, target.getGuild(), LinkEmbedFeatureMode.DELETE_BUTTON);
|
||||
return buildTemplateParameter(embeddingMessage, cachedMessage, deletionButtonEnabled).thenCompose(messageEmbeddedModel ->
|
||||
self.sendEmbeddingMessage(cachedMessage, target, userEmbeddingUserInServerId, messageEmbeddedModel, deletionButtonEnabled)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -140,25 +155,30 @@ public class MessageEmbedServiceBean implements MessageEmbedService {
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
log.info("Cleaning up {} embedded embeddedMessages", embeddedMessages.size());
|
||||
List<ServerChannelMessage> serverChannelMessages = embeddedMessages.stream().map(embeddedMessage ->
|
||||
ServerChannelMessage
|
||||
.builder()
|
||||
.serverId(embeddedMessage.getEmbeddingServer().getId())
|
||||
.channelId(embeddedMessage.getEmbeddingChannel().getId())
|
||||
.messageId(embeddedMessage.getEmbeddingMessageId())
|
||||
.build()
|
||||
)
|
||||
List<ServerChannelMessage> reactionChannelMessages = embeddedMessages.stream()
|
||||
.filter(embeddedMessage -> embeddedMessage.getDeletionComponentId() == null)
|
||||
.map(this::convertEmbedMessageToServerChannelMessage)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
List<ServerChannelMessage> buttonChannelMessages = embeddedMessages.stream()
|
||||
.filter(embeddedMessage -> embeddedMessage.getDeletionComponentId() != null)
|
||||
.map(this::convertEmbedMessageToServerChannelMessage)
|
||||
.collect(Collectors.toList());
|
||||
List<Long> embeddedMessagesHandled = embeddedMessages
|
||||
.stream()
|
||||
.map(EmbeddedMessage::getEmbeddingMessageId)
|
||||
.collect(Collectors.toList());
|
||||
List<CompletableFuture<Message>> messageFutures = messageService.retrieveMessages(serverChannelMessages);
|
||||
CompletableFutureList<Message> future = new CompletableFutureList<>(messageFutures);
|
||||
return future.getMainFuture()
|
||||
.handle((unused, throwable) -> self.removeReactions(future.getObjects()))
|
||||
List<CompletableFuture<Message>> reactionMessageFutures = messageService.retrieveMessages(reactionChannelMessages);
|
||||
List<CompletableFuture<Message>> buttonMessageFutures = messageService.retrieveMessages(buttonChannelMessages);
|
||||
CompletableFutureList<Message> reactionFutureList = new CompletableFutureList<>(reactionMessageFutures);
|
||||
CompletableFutureList<Message> buttonFutureList = new CompletableFutureList<>(buttonMessageFutures);
|
||||
return reactionFutureList.getMainFuture()
|
||||
.handle((unused, throwable) -> self.removeReactions(reactionFutureList.getObjects()))
|
||||
.thenCompose(Function.identity())
|
||||
.thenCompose(unused -> buttonFutureList.getMainFuture())
|
||||
.handle((unused, throwable) -> self.removeButtons(buttonFutureList.getObjects()))
|
||||
// deleting the messages from db regardless of exceptions, at most the reaction remains
|
||||
.thenCompose(Function.identity())
|
||||
.whenComplete((unused, throwable) -> self.deleteEmbeddedMessages(embeddedMessagesHandled))
|
||||
.exceptionally(throwable -> {
|
||||
log.error("Failed to clean up embedded messages.", throwable);
|
||||
@@ -166,6 +186,21 @@ public class MessageEmbedServiceBean implements MessageEmbedService {
|
||||
});
|
||||
}
|
||||
|
||||
public CompletableFuture<Void> removeButtons(List<Message> messages) {
|
||||
List<CompletableFuture<Void>> removalFutures = new ArrayList<>();
|
||||
messages.forEach(message -> removalFutures.add(messageService.clearButtons(message)));
|
||||
return FutureUtils.toSingleFutureGeneric(removalFutures);
|
||||
}
|
||||
|
||||
private ServerChannelMessage convertEmbedMessageToServerChannelMessage(EmbeddedMessage embeddedMessage) {
|
||||
return ServerChannelMessage
|
||||
.builder()
|
||||
.serverId(embeddedMessage.getEmbeddingServer().getId())
|
||||
.channelId(embeddedMessage.getEmbeddingChannel().getId())
|
||||
.messageId(embeddedMessage.getEmbeddingMessageId())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public CompletableFuture<Void> removeReactions(List<Message> allMessages) {
|
||||
List<CompletableFuture<Void>> removalFutures = new ArrayList<>();
|
||||
@@ -186,39 +221,79 @@ public class MessageEmbedServiceBean implements MessageEmbedService {
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public CompletableFuture<Void> sendEmbeddingMessage(CachedMessage cachedMessage, TextChannel target, Long userEmbeddingUserInServerId, MessageEmbeddedModel messageEmbeddedModel) {
|
||||
public CompletableFuture<Void> sendEmbeddingMessage(CachedMessage cachedMessage, TextChannel target,
|
||||
Long userEmbeddingUserInServerId, MessageEmbeddedModel messageEmbeddedModel, Boolean deletionButtonEnabled) {
|
||||
MessageToSend embed = templateService.renderEmbedTemplate(MESSAGE_EMBED_TEMPLATE, messageEmbeddedModel, target.getGuild().getIdLong());
|
||||
AUserInAServer cause = userInServerManagementService.loadOrCreateUser(userEmbeddingUserInServerId);
|
||||
List<CompletableFuture<Message>> completableFutures = channelService.sendMessageToSendToChannel(embed, target);
|
||||
Long embeddingUserId = cause.getUserReference().getId();
|
||||
log.debug("Embedding message {} from channel {} from server {}, because of user {}", cachedMessage.getMessageId(),
|
||||
cachedMessage.getChannelId(), cachedMessage.getServerId(), cause.getUserReference().getId());
|
||||
cachedMessage.getChannelId(), cachedMessage.getServerId(), embeddingUserId);
|
||||
return CompletableFuture.allOf(completableFutures.toArray(new CompletableFuture[0])).thenCompose(aVoid -> {
|
||||
Message createdMessage = completableFutures.get(0).join();
|
||||
return reactionService.addReactionToMessageAsync(REMOVAL_EMOTE, cachedMessage.getServerId(), createdMessage).thenAccept(aVoid1 ->
|
||||
self.loadUserAndPersistMessage(cachedMessage, userEmbeddingUserInServerId, createdMessage)
|
||||
);
|
||||
return self.addDeletionPossibility(cachedMessage, messageEmbeddedModel, createdMessage, embeddingUserId, deletionButtonEnabled);
|
||||
});
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void loadUserAndPersistMessage(CachedMessage cachedMessage, Long embeddingUserId, Message createdMessage) {
|
||||
AUserInAServer innerCause = userInServerManagementService.loadOrCreateUser(embeddingUserId);
|
||||
messageEmbedPostManagementService.createMessageEmbed(cachedMessage, createdMessage, innerCause);
|
||||
public CompletableFuture<Void> addDeletionPossibility(CachedMessage cachedMessage, MessageEmbeddedModel messageEmbeddedModel,
|
||||
Message createdMessage, Long embeddingUserId, Boolean deletionButtonEnabled) {
|
||||
Long serverId = createdMessage.getGuild().getIdLong();
|
||||
AUserInAServer aUserInAServer = userInServerManagementService.loadOrCreateUser(serverId, embeddingUserId);
|
||||
Long embeddingUserInServerId = aUserInAServer.getUserInServerId();
|
||||
if(deletionButtonEnabled) {
|
||||
ButtonConfigModel buttonConfigModel = messageEmbeddedModel.getButtonConfigModel();
|
||||
buttonConfigModel.setButtonPayload(getButtonPayload(createdMessage, cachedMessage, embeddingUserId));
|
||||
buttonConfigModel.setOrigin(MESSAGE_EMBED_DELETE_ORIGIN);
|
||||
buttonConfigModel.setPayloadType(MessageEmbedDeleteButtonPayload.class);
|
||||
AServer server = serverManagementService.loadServer(serverId);
|
||||
componentPayloadManagementService.createPayload(buttonConfigModel, server);
|
||||
self.loadUserAndPersistMessage(cachedMessage, embeddingUserInServerId, createdMessage, messageEmbeddedModel.getButtonConfigModel().getButtonId());
|
||||
return CompletableFuture.completedFuture(null);
|
||||
} else {
|
||||
return reactionService.addReactionToMessageAsync(REMOVAL_EMOTE, cachedMessage.getServerId(), createdMessage).thenAccept(aVoid1 ->
|
||||
self.loadUserAndPersistMessage(cachedMessage, embeddingUserInServerId, createdMessage, null)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private CompletableFuture<MessageEmbeddedModel> buildTemplateParameter(Message message, CachedMessage embeddedMessage) {
|
||||
public MessageEmbedDeleteButtonPayload getButtonPayload(Message embeddingMessage, CachedMessage embeddedMessage, Long embeddingUserId){
|
||||
return MessageEmbedDeleteButtonPayload
|
||||
.builder()
|
||||
.embeddedMessageId(embeddedMessage.getMessageId())
|
||||
.embeddedChannelId(embeddedMessage.getChannelId())
|
||||
.embeddedServerId(embeddedMessage.getServerId())
|
||||
.embeddedUserId(embeddedMessage.getAuthor().getAuthorId())
|
||||
.embeddingMessageId(embeddingMessage.getIdLong())
|
||||
.embeddingChannelId(embeddingMessage.getChannel().getIdLong())
|
||||
.embeddingServerId(embeddingMessage.getGuild().getIdLong())
|
||||
.embeddingUserId(embeddingUserId)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void loadUserAndPersistMessage(CachedMessage cachedMessage, Long embeddingUserId, Message createdMessage, String deletionButtonId) {
|
||||
AUserInAServer innerCause = userInServerManagementService.loadOrCreateUser(embeddingUserId);
|
||||
messageEmbedPostManagementService.createMessageEmbed(cachedMessage, createdMessage, innerCause, deletionButtonId);
|
||||
}
|
||||
|
||||
private CompletableFuture<MessageEmbeddedModel> buildTemplateParameter(Message message, CachedMessage embeddedMessage, Boolean deletionButtonEnabled) {
|
||||
return userService.retrieveUserForId(embeddedMessage.getAuthor().getAuthorId()).thenApply(authorUser ->
|
||||
self.loadMessageEmbedModel(message, embeddedMessage, authorUser)
|
||||
self.loadMessageEmbedModel(message, embeddedMessage, authorUser, deletionButtonEnabled)
|
||||
).exceptionally(throwable -> {
|
||||
log.warn("Failed to retrieve author for user {}.", embeddedMessage.getAuthor().getAuthorId(), throwable);
|
||||
return self.loadMessageEmbedModel(message, embeddedMessage, null);
|
||||
return self.loadMessageEmbedModel(message, embeddedMessage, null, deletionButtonEnabled);
|
||||
});
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public MessageEmbeddedModel loadMessageEmbedModel(Message message, CachedMessage embeddedMessage, User userAuthor) {
|
||||
public MessageEmbeddedModel loadMessageEmbedModel(Message message, CachedMessage embeddedMessage, User userAuthor, Boolean deletionButtonEnabled) {
|
||||
Optional<TextChannel> textChannelFromServer = channelService.getTextChannelFromServerOptional(embeddedMessage.getServerId(), embeddedMessage.getChannelId());
|
||||
TextChannel sourceChannel = textChannelFromServer.orElse(null);
|
||||
ButtonConfigModel buttonConfigModel = ButtonConfigModel
|
||||
.builder()
|
||||
.buttonId(deletionButtonEnabled ? componentServiceBean.generateComponentId() : null)
|
||||
.build();
|
||||
return MessageEmbeddedModel
|
||||
.builder()
|
||||
.member(message.getMember())
|
||||
@@ -227,7 +302,9 @@ public class MessageEmbedServiceBean implements MessageEmbedService {
|
||||
.embeddingUser(message.getMember())
|
||||
.messageChannel(message.getChannel())
|
||||
.guild(message.getGuild())
|
||||
.useButton(deletionButtonEnabled)
|
||||
.embeddedMessage(embeddedMessage)
|
||||
.buttonConfigModel(buttonConfigModel)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ public class MessageEmbedPostManagementServiceBean implements MessageEmbedPostMa
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void createMessageEmbed(CachedMessage embeddedMessage, Message messageContainingEmbed, AUserInAServer embeddingUser) {
|
||||
public void createMessageEmbed(CachedMessage embeddedMessage, Message messageContainingEmbed, AUserInAServer embeddingUser, String deletionComponentId) {
|
||||
AServer embeddedServer = serverManagementService.loadOrCreate(embeddedMessage.getServerId());
|
||||
AServer embeddingServer = serverManagementService.loadOrCreate(messageContainingEmbed.getGuild().getIdLong());
|
||||
if(!embeddedMessage.getServerId().equals(messageContainingEmbed.getGuild().getIdLong())) {
|
||||
@@ -52,6 +52,7 @@ public class MessageEmbedPostManagementServiceBean implements MessageEmbedPostMa
|
||||
.embeddedMessageId(embeddedMessage.getMessageId())
|
||||
.embeddedChannel(embeddedChannel)
|
||||
.embeddedServer(embeddedServer)
|
||||
.deletionComponentId(deletionComponentId)
|
||||
.embeddingServer(embeddingServer)
|
||||
.embeddingChannel(embeddingChannel)
|
||||
.embeddingMessageId(messageContainingEmbed.getIdLong())
|
||||
|
||||
@@ -2,4 +2,8 @@ abstracto.featureFlags.linkEmbeds.featureName=linkEmbeds
|
||||
abstracto.featureFlags.linkEmbeds.enabled=false
|
||||
|
||||
|
||||
abstracto.feature.linkEmbed.removalDays=1
|
||||
abstracto.feature.linkEmbed.removalDays=1
|
||||
|
||||
abstracto.featureModes.messageEmbedDeleteButton.featureName=linkEmbeds
|
||||
abstracto.featureModes.messageEmbedDeleteButton.mode=messageEmbedDeleteButton
|
||||
abstracto.featureModes.messageEmbedDeleteButton.enabled=true
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
|
||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
|
||||
xmlns:pro="http://www.liquibase.org/xml/ns/pro"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog dbchangelog.xsd
|
||||
http://www.liquibase.org/xml/ns/dbchangelog-ext dbchangelog.xsd
|
||||
http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" >
|
||||
<include file="tables/tables.xml" relativeToChangelogFile="true"/>
|
||||
</databaseChangeLog>
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
|
||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
|
||||
xmlns:pro="http://www.liquibase.org/xml/ns/pro"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog dbchangelog.xsd
|
||||
http://www.liquibase.org/xml/ns/dbchangelog-ext dbchangelog.xsd
|
||||
http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" >
|
||||
<changeSet author="Sheldan" id="embedded_message-notnull">
|
||||
<addColumn tableName="embedded_message">
|
||||
<column name="deletion_component_id" type="VARCHAR(100)" />
|
||||
</addColumn>
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
|
||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
|
||||
xmlns:pro="http://www.liquibase.org/xml/ns/pro"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog dbchangelog.xsd
|
||||
http://www.liquibase.org/xml/ns/dbchangelog-ext dbchangelog.xsd
|
||||
http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" >
|
||||
<include file="embedded_message.xml" relativeToChangelogFile="true"/>
|
||||
</databaseChangeLog>
|
||||
@@ -9,4 +9,5 @@
|
||||
<include file="1.0-link-embed/collection.xml" relativeToChangelogFile="true"/>
|
||||
<include file="1.2.8-link-embed/collection.xml" relativeToChangelogFile="true"/>
|
||||
<include file="1.2.12/collection.xml" relativeToChangelogFile="true"/>
|
||||
<include file="1.3.0/collection.xml" relativeToChangelogFile="true"/>
|
||||
</databaseChangeLog>
|
||||
@@ -4,7 +4,9 @@ import dev.sheldan.abstracto.core.models.cache.CachedAuthor;
|
||||
import dev.sheldan.abstracto.core.models.cache.CachedMessage;
|
||||
import dev.sheldan.abstracto.core.models.database.AUser;
|
||||
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
|
||||
import dev.sheldan.abstracto.core.models.template.listener.MessageEmbeddedModel;
|
||||
import dev.sheldan.abstracto.linkembed.config.LinkEmbedFeatureDefinition;
|
||||
import dev.sheldan.abstracto.linkembed.config.LinkEmbedFeatureMode;
|
||||
import dev.sheldan.abstracto.linkembed.model.template.MessageEmbeddedModel;
|
||||
import dev.sheldan.abstracto.core.service.*;
|
||||
import dev.sheldan.abstracto.core.service.management.ChannelManagementService;
|
||||
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
|
||||
@@ -67,6 +69,9 @@ public class MessageEmbedServiceBeanTest {
|
||||
@Mock
|
||||
private ReactionService reactionService;
|
||||
|
||||
@Mock
|
||||
private FeatureModeService featureModeService;
|
||||
|
||||
@Mock
|
||||
private TextChannel textChannel;
|
||||
|
||||
@@ -211,8 +216,8 @@ public class MessageEmbedServiceBeanTest {
|
||||
when(cachedMessage.getAuthor()).thenReturn(cachedAuthor);
|
||||
when(userService.retrieveUserForId(USER_ID)).thenReturn(CompletableFuture.completedFuture(embeddedUser));
|
||||
MessageEmbeddedModel model = Mockito.mock(MessageEmbeddedModel.class);
|
||||
when(self.loadMessageEmbedModel(embeddingMessage, cachedMessage, embeddedUser)).thenReturn(model);
|
||||
when(self.sendEmbeddingMessage(cachedMessage, textChannel, EMBEDDING_USER_IN_SERVER_ID, model)).thenReturn(CompletableFuture.completedFuture(null));
|
||||
when(self.loadMessageEmbedModel(embeddingMessage, cachedMessage, embeddedUser, false)).thenReturn(model);
|
||||
when(self.sendEmbeddingMessage(cachedMessage, textChannel, EMBEDDING_USER_IN_SERVER_ID, model, false)).thenReturn(CompletableFuture.completedFuture(null));
|
||||
CompletableFuture<Void> embedFuture = testUnit.embedLink(cachedMessage, textChannel, EMBEDDING_USER_IN_SERVER_ID, embeddingMessage);
|
||||
Assert.assertTrue(embedFuture.isDone());
|
||||
}
|
||||
@@ -225,6 +230,8 @@ public class MessageEmbedServiceBeanTest {
|
||||
CachedAuthor cachedAuthor = Mockito.mock(CachedAuthor.class);
|
||||
when(cachedAuthor.getAuthorId()).thenReturn(USER_ID);
|
||||
when(cachedMessage.getAuthor()).thenReturn(cachedAuthor);
|
||||
when(textChannel.getGuild()).thenReturn(guild);
|
||||
when(featureModeService.featureModeActive(LinkEmbedFeatureDefinition.LINK_EMBEDS, guild, LinkEmbedFeatureMode.DELETE_BUTTON)).thenReturn(false);
|
||||
when(userService.retrieveUserForId(USER_ID)).thenReturn(CompletableFuture.completedFuture(embeddedUser));
|
||||
testUnit.embedLink(cachedMessage, textChannel, userEmbeddingUserInServerId, embeddingMessage);
|
||||
verify(messageCache, times(0)).getMessageFromCache(anyLong(), anyLong(), anyLong());
|
||||
@@ -243,18 +250,16 @@ public class MessageEmbedServiceBeanTest {
|
||||
List<CompletableFuture<Message>> messageFutures = CommandTestUtilities.messageFutureList();
|
||||
when(channelService.sendMessageToSendToChannel(messageToSend, textChannel)).thenReturn(messageFutures);
|
||||
Message createdMessage = messageFutures.get(0).join();
|
||||
when(reactionService.addReactionToMessageAsync(MessageEmbedServiceBean.REMOVAL_EMOTE, cachedMessage.getServerId(),
|
||||
createdMessage)).thenReturn(CompletableFuture.completedFuture(null));
|
||||
CompletableFuture<Void> future = testUnit.sendEmbeddingMessage(cachedMessage, textChannel, EMBEDDING_USER_IN_SERVER_ID, embeddedModel);
|
||||
when(self.addDeletionPossibility(cachedMessage, embeddedModel, createdMessage, 0L, false)).thenReturn(CompletableFuture.completedFuture(null));
|
||||
CompletableFuture<Void> future = testUnit.sendEmbeddingMessage(cachedMessage, textChannel, EMBEDDING_USER_IN_SERVER_ID, embeddedModel, false);
|
||||
Assert.assertFalse(future.isCompletedExceptionally());
|
||||
verify(self, times(1)).loadUserAndPersistMessage(cachedMessage, EMBEDDING_USER_IN_SERVER_ID, createdMessage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoadUserAndPersistMessage() {
|
||||
when(userInServerManagementService.loadOrCreateUser(EMBEDDING_USER_IN_SERVER_ID)).thenReturn(embeddingUser);
|
||||
testUnit.loadUserAndPersistMessage(cachedMessage, EMBEDDING_USER_IN_SERVER_ID, embeddingMessage);
|
||||
verify(messageEmbedPostManagementService, times(1)).createMessageEmbed(cachedMessage, embeddingMessage, embeddingUser);
|
||||
testUnit.loadUserAndPersistMessage(cachedMessage, EMBEDDING_USER_IN_SERVER_ID, embeddingMessage, null);
|
||||
verify(messageEmbedPostManagementService, times(1)).createMessageEmbed(cachedMessage, embeddingMessage, embeddingUser, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -265,7 +270,7 @@ public class MessageEmbedServiceBeanTest {
|
||||
when(embeddingMessage.getGuild()).thenReturn(guild);
|
||||
when(embeddingMessage.getChannel()).thenReturn(textChannel);
|
||||
when(embeddingMessage.getMember()).thenReturn(embeddingMember);
|
||||
MessageEmbeddedModel createdModel = testUnit.loadMessageEmbedModel(embeddingMessage, cachedMessage, embeddedUser);
|
||||
MessageEmbeddedModel createdModel = testUnit.loadMessageEmbedModel(embeddingMessage, cachedMessage, embeddedUser, false);
|
||||
Assert.assertEquals(textChannel, createdModel.getSourceChannel());
|
||||
Assert.assertEquals(guild, createdModel.getGuild());
|
||||
Assert.assertEquals(textChannel, createdModel.getMessageChannel());
|
||||
|
||||
@@ -80,7 +80,7 @@ public class MessageEmbedPostManagementServiceBeanTest {
|
||||
when(guild.getIdLong()).thenReturn(SERVER_ID);
|
||||
when(embeddingMessage.getIdLong()).thenReturn(EMBEDDING_MESSAGE_ID);
|
||||
when(userInServerManagementService.loadOrCreateUser(SERVER_ID, EMBEDDED_USER_ID)).thenReturn(embeddedUser);
|
||||
testUnit.createMessageEmbed(cachedMessage, embeddingMessage, embeddingUser);
|
||||
testUnit.createMessageEmbed(cachedMessage, embeddingMessage, embeddingUser, null);
|
||||
verify(embeddedMessageRepository, times(1)).save(messageArgumentCaptor.capture());
|
||||
EmbeddedMessage savedMessage = messageArgumentCaptor.getValue();
|
||||
Assert.assertEquals(EMBEDDED_MESSAGE_ID, savedMessage.getEmbeddedMessageId());
|
||||
@@ -108,7 +108,7 @@ public class MessageEmbedPostManagementServiceBeanTest {
|
||||
Guild guild = Mockito.mock(Guild.class);
|
||||
when(message.getGuild()).thenReturn(guild);
|
||||
when(guild.getIdLong()).thenReturn(SERVER_ID);
|
||||
testUnit.createMessageEmbed(cachedMessage, message, embeddingUser);
|
||||
testUnit.createMessageEmbed(cachedMessage, message, embeddingUser, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -2,6 +2,7 @@ package dev.sheldan.abstracto.linkembed.config;
|
||||
|
||||
import dev.sheldan.abstracto.core.config.FeatureConfig;
|
||||
import dev.sheldan.abstracto.core.config.FeatureDefinition;
|
||||
import dev.sheldan.abstracto.core.config.FeatureMode;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Arrays;
|
||||
@@ -19,4 +20,9 @@ public class LinkEmbedFeatureConfig implements FeatureConfig {
|
||||
public List<String> getRequiredEmotes() {
|
||||
return Arrays.asList("removeEmbed");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<FeatureMode> getAvailableModes() {
|
||||
return Arrays.asList(LinkEmbedFeatureMode.DELETE_BUTTON);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package dev.sheldan.abstracto.linkembed.config;
|
||||
|
||||
import dev.sheldan.abstracto.core.config.FeatureMode;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public enum LinkEmbedFeatureMode implements FeatureMode {
|
||||
DELETE_BUTTON("messageEmbedDeleteButton");
|
||||
|
||||
private final String key;
|
||||
|
||||
LinkEmbedFeatureMode(String key) {
|
||||
this.key = key;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package dev.sheldan.abstracto.linkembed.exception;
|
||||
|
||||
import dev.sheldan.abstracto.core.exception.AbstractoTemplatableException;
|
||||
|
||||
public class LinkEmbedRemovalNotAllowedException extends AbstractoTemplatableException {
|
||||
|
||||
public LinkEmbedRemovalNotAllowedException() {
|
||||
super("User is not allowed remove the embedded link.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTemplateName() {
|
||||
return "link_embed_removal_not_allowed_exception";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getTemplateModel() {
|
||||
return new Object();
|
||||
}
|
||||
}
|
||||
@@ -56,6 +56,9 @@ public class EmbeddedMessage implements Serializable {
|
||||
@Id
|
||||
private Long embeddingMessageId;
|
||||
|
||||
@Column(name = "deletion_component_id", length = 100)
|
||||
private String deletionComponentId;
|
||||
|
||||
@Column(name = "created", nullable = false, insertable = false, updatable = false)
|
||||
private Instant created;
|
||||
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package dev.sheldan.abstracto.linkembed.model.template;
|
||||
|
||||
import dev.sheldan.abstracto.core.models.template.button.ButtonPayload;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
public class MessageEmbedDeleteButtonPayload implements ButtonPayload {
|
||||
private Long embeddingServerId;
|
||||
private Long embeddingChannelId;
|
||||
private Long embeddingMessageId;
|
||||
private Long embeddedServerId;
|
||||
private Long embeddedChannelId;
|
||||
private Long embeddedMessageId;
|
||||
private Long embeddedUserId;
|
||||
private Long embeddingUserId;
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
package dev.sheldan.abstracto.core.models.template.listener;
|
||||
package dev.sheldan.abstracto.linkembed.model.template;
|
||||
|
||||
import dev.sheldan.abstracto.core.models.cache.CachedMessage;
|
||||
import dev.sheldan.abstracto.core.models.context.UserInitiatedServerContext;
|
||||
import dev.sheldan.abstracto.core.models.template.button.ButtonConfigModel;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
@@ -17,4 +18,6 @@ public class MessageEmbeddedModel extends UserInitiatedServerContext {
|
||||
private User author;
|
||||
private TextChannel sourceChannel;
|
||||
private Member embeddingUser;
|
||||
private ButtonConfigModel buttonConfigModel;
|
||||
private Boolean useButton;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package dev.sheldan.abstracto.linkembed.service;
|
||||
|
||||
public interface MessageEmbedMetricService {
|
||||
void incrementMessageEmbedDeletedMetric(boolean embeddedUserDeleted);
|
||||
void incrementMessageEmbedDeletedEmbeddedMetric();
|
||||
void incrementMessageEmbedDeletedEmbeddingMetric();
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface MessageEmbedPostManagementService {
|
||||
void createMessageEmbed(CachedMessage embeddedMessage, Message messageContainingEmbed, AUserInAServer cause);
|
||||
void createMessageEmbed(CachedMessage embeddedMessage, Message messageContainingEmbed, AUserInAServer cause, String deletionButtonId);
|
||||
Optional<EmbeddedMessage> findEmbeddedPostByMessageId(Long messageId);
|
||||
void deleteEmbeddedMessage(EmbeddedMessage embeddedMessage);
|
||||
List<EmbeddedMessage> getEmbeddedMessagesOlderThan(Instant date);
|
||||
|
||||
@@ -4,10 +4,14 @@ import dev.sheldan.abstracto.core.command.Command;
|
||||
import dev.sheldan.abstracto.core.command.execution.CommandContext;
|
||||
import dev.sheldan.abstracto.core.command.execution.CommandResult;
|
||||
import dev.sheldan.abstracto.core.command.model.exception.GenericExceptionModel;
|
||||
import dev.sheldan.abstracto.core.interaction.GenericInteractionExceptionModel;
|
||||
import dev.sheldan.abstracto.core.interaction.InteractionService;
|
||||
import dev.sheldan.abstracto.core.listener.async.jda.ButtonClickedListener;
|
||||
import dev.sheldan.abstracto.core.models.FullUser;
|
||||
import dev.sheldan.abstracto.core.models.FullUserInServer;
|
||||
import dev.sheldan.abstracto.core.models.database.AUser;
|
||||
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
|
||||
import dev.sheldan.abstracto.core.models.listener.ButtonClickedListenerModel;
|
||||
import dev.sheldan.abstracto.core.service.ChannelService;
|
||||
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
|
||||
import dev.sheldan.abstracto.core.service.management.UserManagementService;
|
||||
@@ -39,6 +43,9 @@ public class ExceptionServiceBean implements ExceptionService {
|
||||
@Autowired
|
||||
private UserManagementService userManagementService;
|
||||
|
||||
@Autowired
|
||||
private InteractionService interactionService;
|
||||
|
||||
@Override
|
||||
public CommandResult reportExceptionToContext(Throwable throwable, CommandContext context, Command command) {
|
||||
if(command != null) {
|
||||
@@ -60,11 +67,34 @@ public class ExceptionServiceBean implements ExceptionService {
|
||||
return CommandResult.fromReportedError();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportExceptionToInteraction(Throwable exception, ButtonClickedListenerModel interactionContext, ButtonClickedListener executedListener) {
|
||||
if(executedListener != null) {
|
||||
log.info("Reporting generic exception {} of listener {} towards channel {} in server {}.",
|
||||
exception.getClass().getSimpleName(), executedListener.getClass().getSimpleName(), interactionContext.getEvent().getChannel().getIdLong(),
|
||||
interactionContext.getEvent().getGuild().getIdLong());
|
||||
} else {
|
||||
log.info("Reporting generic exception {} towards channel {} in server {}.",
|
||||
exception.getClass().getSimpleName(), interactionContext.getEvent().getChannel().getIdLong(),
|
||||
interactionContext.getEvent().getGuild().getIdLong());
|
||||
}
|
||||
try {
|
||||
reportGenericInteractionException(exception, interactionContext);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to notify about exception.", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void reportGenericException(Throwable throwable, CommandContext context) {
|
||||
GenericExceptionModel exceptionModel = buildCommandModel(throwable, context);
|
||||
channelService.sendEmbedTemplateInTextChannelList("generic_command_exception", exceptionModel, context.getChannel());
|
||||
}
|
||||
|
||||
private void reportGenericInteractionException(Throwable throwable, ButtonClickedListenerModel interactionContext) {
|
||||
GenericInteractionExceptionModel exceptionModel = buildInteractionExceptionModel(throwable, interactionContext);
|
||||
interactionService.sendMessageToInteraction("generic_interaction_exception", exceptionModel, interactionContext.getEvent().getInteraction().getHook());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportExceptionToGuildMessageReceivedContext(Throwable exception, GuildMessageReceivedEvent event) {
|
||||
if(exception instanceof Templatable){
|
||||
@@ -98,6 +128,14 @@ public class ExceptionServiceBean implements ExceptionService {
|
||||
}
|
||||
}
|
||||
|
||||
private GenericInteractionExceptionModel buildInteractionExceptionModel(Throwable throwable, ButtonClickedListenerModel context) {
|
||||
return GenericInteractionExceptionModel
|
||||
.builder()
|
||||
.member(context.getEvent().getMember())
|
||||
.user(context.getEvent().getUser())
|
||||
.throwable(throwable)
|
||||
.build();
|
||||
}
|
||||
private GenericExceptionModel buildCommandModel(Throwable throwable, CommandContext context) {
|
||||
FullUserInServer fullUser = FullUserInServer.builder().member(context.getAuthor()).aUserInAServer(userInServerManagementService.loadUserOptional(context.getGuild().getIdLong(), context.getAuthor().getIdLong()).orElse(null)).build();
|
||||
return GenericExceptionModel
|
||||
|
||||
@@ -52,6 +52,11 @@ public class ListenerExecutorConfig {
|
||||
return executorService.setupExecutorFor("emoteCreatedListener");
|
||||
}
|
||||
|
||||
@Bean(name = "buttonClickedExecutor")
|
||||
public TaskExecutor buttonClickedExecutor() {
|
||||
return executorService.setupExecutorFor("buttonClickedListener");
|
||||
}
|
||||
|
||||
@Bean(name = "emoteDeletedExecutor")
|
||||
public TaskExecutor emoteDeletedExecutor() {
|
||||
return executorService.setupExecutorFor("emoteDeletedListener");
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package dev.sheldan.abstracto.core.interaction;
|
||||
|
||||
import dev.sheldan.abstracto.core.command.service.ExceptionService;
|
||||
import dev.sheldan.abstracto.core.listener.async.jda.ButtonClickedListener;
|
||||
import dev.sheldan.abstracto.core.models.listener.ButtonClickedListenerModel;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@Slf4j
|
||||
public class ExceptionPostInteractionExecution implements PostInteractionExecution {
|
||||
|
||||
@Autowired
|
||||
private ExceptionService exceptionService;
|
||||
|
||||
@Override
|
||||
public void execute(ButtonClickedListenerModel interActionContext, InteractionResult interactionResult, ButtonClickedListener executedListener) {
|
||||
InteractionResultState result = interactionResult.getResult();
|
||||
if(result.equals(InteractionResultState.ERROR)) {
|
||||
Throwable throwable = interactionResult.getThrowable();
|
||||
if(throwable != null) {
|
||||
log.info("Exception handling in interaction for exception {}.", throwable.getClass().getSimpleName());
|
||||
exceptionService.reportExceptionToInteraction(throwable, interActionContext, executedListener);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
package dev.sheldan.abstracto.core.listener.sync.jda;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import dev.sheldan.abstracto.core.config.FeatureConfig;
|
||||
import dev.sheldan.abstracto.core.interaction.InteractionResult;
|
||||
import dev.sheldan.abstracto.core.interaction.PostInteractionExecution;
|
||||
import dev.sheldan.abstracto.core.listener.ListenerService;
|
||||
import dev.sheldan.abstracto.core.listener.async.jda.ButtonClickedListener;
|
||||
import dev.sheldan.abstracto.core.models.database.ComponentPayload;
|
||||
import dev.sheldan.abstracto.core.models.listener.ButtonClickedListenerModel;
|
||||
import dev.sheldan.abstracto.core.models.template.button.ButtonPayload;
|
||||
import dev.sheldan.abstracto.core.service.FeatureConfigService;
|
||||
import dev.sheldan.abstracto.core.service.FeatureFlagService;
|
||||
import dev.sheldan.abstracto.core.service.FeatureModeService;
|
||||
import dev.sheldan.abstracto.core.service.management.ComponentPayloadManagementService;
|
||||
import dev.sheldan.abstracto.core.utils.BeanUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.dv8tion.jda.api.events.interaction.ButtonClickEvent;
|
||||
import net.dv8tion.jda.api.hooks.ListenerAdapter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.core.task.TaskExecutor;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Propagation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Component
|
||||
@Slf4j
|
||||
public class SyncButtonClickedListenerBean extends ListenerAdapter {
|
||||
|
||||
@Autowired(required = false)
|
||||
private List<ButtonClickedListener> listenerList;
|
||||
|
||||
@Autowired(required = false)
|
||||
private List<PostInteractionExecution> postInteractionExecutions;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("buttonClickedExecutor")
|
||||
private TaskExecutor buttonClickedExecutor;
|
||||
|
||||
@Autowired
|
||||
private ListenerService listenerService;
|
||||
|
||||
@Autowired
|
||||
private FeatureConfigService featureConfigService;
|
||||
|
||||
@Autowired
|
||||
private FeatureFlagService featureFlagService;
|
||||
|
||||
@Autowired
|
||||
private FeatureModeService featureModeService;
|
||||
|
||||
@Autowired
|
||||
private SyncButtonClickedListenerBean self;
|
||||
|
||||
@Autowired
|
||||
private ComponentPayloadManagementService componentPayloadManagementService;
|
||||
|
||||
@Autowired
|
||||
private Gson gson;
|
||||
|
||||
@Override
|
||||
public void onButtonClick(@NotNull ButtonClickEvent event) {
|
||||
if(listenerList == null) return;
|
||||
if(event.getGuild() != null) {
|
||||
event.deferEdit().queue();
|
||||
CompletableFuture.runAsync(() -> self.executeListenerLogic(event), buttonClickedExecutor).exceptionally(throwable -> {
|
||||
log.error("Failed to execute listener logic in async button event.", throwable);
|
||||
return null;
|
||||
});
|
||||
} else {
|
||||
log.warn("Received button clicked event outside of guild with id {}.", event.getComponentId());
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void executeListenerLogic(@NotNull ButtonClickEvent event) {
|
||||
ButtonClickedListenerModel model = null;
|
||||
ButtonClickedListener listener = null;
|
||||
try {
|
||||
Optional<ComponentPayload> callbackInformation = componentPayloadManagementService.findPayload(event.getComponentId());
|
||||
if(callbackInformation.isPresent()) {
|
||||
model = getModel(event, callbackInformation.get());
|
||||
List<ButtonClickedListener> validListener = filterFeatureAwareListener(listenerList, model);
|
||||
Optional<ButtonClickedListener> listenerOptional = findListener(validListener, model);
|
||||
if(listenerOptional.isPresent()) {
|
||||
listener = listenerOptional.get();
|
||||
log.info("Executing button listener {} for event for id {}.", listener.getClass().getSimpleName(), event.getComponentId());
|
||||
listener.execute(model);
|
||||
InteractionResult result = InteractionResult.fromSuccess();
|
||||
for (PostInteractionExecution postInteractionExecution : postInteractionExecutions) {
|
||||
postInteractionExecution.execute(model, result, listener);
|
||||
}
|
||||
} else {
|
||||
log.warn("No listener found for button event for id {}.", event.getComponentId());
|
||||
}
|
||||
} else {
|
||||
log.warn("No callback found for id {}.", event.getComponentId());
|
||||
}
|
||||
} catch (Exception exception) {
|
||||
log.error("Button clicked listener failed with exception in server {} and channel {}.", event.getGuild().getIdLong(),
|
||||
event.getGuildChannel().getIdLong(), exception);
|
||||
if(model != null && listener != null) {
|
||||
InteractionResult result = InteractionResult.fromError("Failed to execute interaction.", exception);
|
||||
if(postInteractionExecutions != null) {
|
||||
for (PostInteractionExecution postInteractionExecution : postInteractionExecutions) {
|
||||
postInteractionExecution.execute(model, result, listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional(propagation = Propagation.REQUIRES_NEW)
|
||||
public void executeFeatureListenerInTransaction(ButtonClickedListener listener, ButtonClickedListenerModel model) {
|
||||
listener.execute(model);
|
||||
}
|
||||
|
||||
|
||||
private Optional<ButtonClickedListener> findListener(List<ButtonClickedListener> featureAwareListeners, ButtonClickedListenerModel model) {
|
||||
return featureAwareListeners.stream().filter(asyncButtonClickedListener -> asyncButtonClickedListener.handlesEvent(model)).findFirst();
|
||||
}
|
||||
|
||||
private List<ButtonClickedListener> filterFeatureAwareListener(List<ButtonClickedListener> featureAwareListeners, ButtonClickedListenerModel model) {
|
||||
return featureAwareListeners.stream().filter(trFeatureAwareListener -> {
|
||||
FeatureConfig feature = featureConfigService.getFeatureDisplayForFeature(trFeatureAwareListener.getFeature());
|
||||
if (!featureFlagService.isFeatureEnabled(feature, model.getServerId())) {
|
||||
return false;
|
||||
}
|
||||
return featureModeService.necessaryFeatureModesMet(trFeatureAwareListener, model.getServerId());
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private ButtonClickedListenerModel getModel(ButtonClickEvent event, ComponentPayload componentPayload) throws ClassNotFoundException {
|
||||
ButtonPayload payload = null;
|
||||
if(componentPayload.getPayloadType() != null && componentPayload.getPayload() != null) {
|
||||
payload = (ButtonPayload) gson.fromJson(componentPayload.getPayload(), Class.forName(componentPayload.getPayloadType()));
|
||||
}
|
||||
return ButtonClickedListenerModel
|
||||
.builder()
|
||||
.event(event)
|
||||
.deserializedPayload(payload)
|
||||
.payload(componentPayload.getPayload())
|
||||
.origin(componentPayload.getOrigin())
|
||||
.build();
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void postConstruct() {
|
||||
BeanUtils.sortPrioritizedListeners(listenerList);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package dev.sheldan.abstracto.core.repository;
|
||||
|
||||
import dev.sheldan.abstracto.core.models.database.AServer;
|
||||
import dev.sheldan.abstracto.core.models.database.ComponentPayload;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Repository
|
||||
public interface ComponentPayloadRepository extends JpaRepository<ComponentPayload, String> {
|
||||
List<ComponentPayload> findByServerAndOrigin(AServer server, String buttonOrigin);
|
||||
}
|
||||
@@ -4,8 +4,12 @@ import dev.sheldan.abstracto.core.config.AllowedMentionConfig;
|
||||
import dev.sheldan.abstracto.core.models.database.AllowedMention;
|
||||
import dev.sheldan.abstracto.core.exception.UnknownMentionTypeException;
|
||||
import dev.sheldan.abstracto.core.service.management.AllowedMentionManagementService;
|
||||
import dev.sheldan.abstracto.core.templating.model.MessageConfig;
|
||||
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.dv8tion.jda.api.entities.GuildChannel;
|
||||
import net.dv8tion.jda.api.entities.Message;
|
||||
import net.dv8tion.jda.api.entities.MessageChannel;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@@ -113,6 +117,27 @@ public class AllowedMentionServiceBean implements AllowedMentionService {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Message.MentionType> getAllowedMentionsFor(MessageChannel channel, MessageToSend messageToSend) {
|
||||
Set<Message.MentionType> allowedMentions = new HashSet<>();
|
||||
if(channel instanceof GuildChannel) {
|
||||
allowedMentions.addAll(getAllowedMentionTypesForServer(((GuildChannel) channel).getGuild().getIdLong()));
|
||||
}
|
||||
if(messageToSend != null && messageToSend.getMessageConfig() != null) {
|
||||
MessageConfig messageConfig = messageToSend.getMessageConfig();
|
||||
if(messageConfig.isAllowsEveryoneMention()) {
|
||||
allowedMentions.add(Message.MentionType.EVERYONE);
|
||||
}
|
||||
if(messageConfig.isAllowsUserMention()) {
|
||||
allowedMentions.add(Message.MentionType.USER);
|
||||
}
|
||||
if(messageConfig.isAllowsRoleMention()) {
|
||||
allowedMentions.add(Message.MentionType.ROLE);
|
||||
}
|
||||
}
|
||||
return allowedMentions;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void postConstruct() {
|
||||
ALL_MENTION_TYPES.put(EVERYONE_MENTION_KEY, Message.MentionType.EVERYONE);
|
||||
|
||||
@@ -6,13 +6,15 @@ import dev.sheldan.abstracto.core.metric.service.MetricService;
|
||||
import dev.sheldan.abstracto.core.metric.service.MetricTag;
|
||||
import dev.sheldan.abstracto.core.models.database.AChannel;
|
||||
import dev.sheldan.abstracto.core.models.database.AServer;
|
||||
import dev.sheldan.abstracto.core.templating.model.MessageConfig;
|
||||
import dev.sheldan.abstracto.core.service.management.ComponentPayloadManagementService;
|
||||
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
|
||||
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
|
||||
import dev.sheldan.abstracto.core.templating.service.TemplateService;
|
||||
import dev.sheldan.abstracto.core.utils.FileService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.dv8tion.jda.api.EmbedBuilder;
|
||||
import net.dv8tion.jda.api.entities.*;
|
||||
import net.dv8tion.jda.api.interactions.components.ActionRow;
|
||||
import net.dv8tion.jda.api.requests.RestAction;
|
||||
import net.dv8tion.jda.api.requests.restaction.MessageAction;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
@@ -52,6 +54,12 @@ public class ChannelServiceBean implements ChannelService {
|
||||
@Autowired
|
||||
private MetricService metricService;
|
||||
|
||||
@Autowired
|
||||
private ComponentPayloadManagementService componentPayloadManagementService;
|
||||
|
||||
@Autowired
|
||||
private ServerManagementService serverManagementService;
|
||||
|
||||
public static final CounterMetric CHANNEL_CREATE_METRIC = CounterMetric
|
||||
.builder()
|
||||
.name(DISCORD_API_INTERACTION_METRIC)
|
||||
@@ -124,34 +132,14 @@ public class ChannelServiceBean implements ChannelService {
|
||||
log.debug("Sending message {} from channel {} and server {} to channel {}.",
|
||||
message.getId(), message.getChannel().getId(), message.getGuild().getId(), channel.getId());
|
||||
metricService.incrementCounter(MESSAGE_SEND_METRIC);
|
||||
return channel.sendMessage(message).allowedMentions(getAllowedMentionsFor(channel, null)).submit();
|
||||
}
|
||||
|
||||
private Set<Message.MentionType> getAllowedMentionsFor(MessageChannel channel, MessageToSend messageToSend) {
|
||||
Set<Message.MentionType> allowedMentions = new HashSet<>();
|
||||
if(channel instanceof GuildChannel) {
|
||||
allowedMentions.addAll(allowedMentionService.getAllowedMentionTypesForServer(((GuildChannel) channel).getGuild().getIdLong()));
|
||||
}
|
||||
if(messageToSend != null && messageToSend.getMessageConfig() != null) {
|
||||
MessageConfig messageConfig = messageToSend.getMessageConfig();
|
||||
if(messageConfig.isAllowsEveryoneMention()) {
|
||||
allowedMentions.add(Message.MentionType.EVERYONE);
|
||||
}
|
||||
if(messageConfig.isAllowsUserMention()) {
|
||||
allowedMentions.add(Message.MentionType.USER);
|
||||
}
|
||||
if(messageConfig.isAllowsRoleMention()) {
|
||||
allowedMentions.add(Message.MentionType.ROLE);
|
||||
}
|
||||
}
|
||||
return allowedMentions;
|
||||
return channel.sendMessage(message).allowedMentions(allowedMentionService.getAllowedMentionsFor(channel, null)).submit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Message> sendTextToChannel(String text, MessageChannel channel) {
|
||||
log.debug("Sending text to channel {}.", channel.getId());
|
||||
metricService.incrementCounter(MESSAGE_SEND_METRIC);
|
||||
return channel.sendMessage(text).allowedMentions(getAllowedMentionsFor(channel, null)).submit();
|
||||
return channel.sendMessage(text).allowedMentions(allowedMentionService.getAllowedMentionsFor(channel, null)).submit();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -180,7 +168,7 @@ public class ChannelServiceBean implements ChannelService {
|
||||
@Override
|
||||
public MessageAction sendEmbedToChannelInComplete(MessageEmbed embed, MessageChannel channel) {
|
||||
metricService.incrementCounter(MESSAGE_SEND_METRIC);
|
||||
return channel.sendMessageEmbeds(embed).allowedMentions(getAllowedMentionsFor(channel, null));
|
||||
return channel.sendMessageEmbeds(embed).allowedMentions(allowedMentionService.getAllowedMentionsFor(channel, null));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -211,6 +199,9 @@ public class ChannelServiceBean implements ChannelService {
|
||||
|
||||
@Override
|
||||
public List<CompletableFuture<Message>> sendMessageToSendToChannel(MessageToSend messageToSend, MessageChannel textChannel) {
|
||||
if(messageToSend.getEphemeral()) {
|
||||
throw new IllegalArgumentException("Ephemeral messages are only supported in interaction context.");
|
||||
}
|
||||
List<CompletableFuture<Message>> futures = new ArrayList<>();
|
||||
List<MessageAction> allMessageActions = new ArrayList<>();
|
||||
int iterations = Math.min(messageToSend.getMessages().size(), messageToSend.getEmbeds().size());
|
||||
@@ -234,6 +225,21 @@ public class ChannelServiceBean implements ChannelService {
|
||||
MessageAction messageAction = textChannel.sendMessageEmbeds(embed);
|
||||
allMessageActions.add(messageAction);
|
||||
}
|
||||
|
||||
List<ActionRow> actionRows = messageToSend.getActionRows();
|
||||
if(!actionRows.isEmpty() && textChannel instanceof GuildChannel) {
|
||||
GuildChannel channel = (GuildChannel) textChannel;
|
||||
AServer server = serverManagementService.loadServer(channel.getGuild());
|
||||
allMessageActions.set(0, allMessageActions.get(0).setActionRows(actionRows));
|
||||
actionRows.forEach(components -> components.forEach(component -> {
|
||||
String id = component.getId();
|
||||
MessageToSend.ComponentConfig payload = messageToSend.getComponentPayloads().get(id);
|
||||
if(payload.getPersistCallback()) {
|
||||
componentPayloadManagementService.createPayload(id, payload.getPayload(), payload.getPayloadType(), payload.getComponentOrigin(), server);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
if(messageToSend.hasFileToSend()) {
|
||||
if(!allMessageActions.isEmpty()) {
|
||||
// in case there has not been a message, we need to increment it
|
||||
@@ -243,7 +249,7 @@ public class ChannelServiceBean implements ChannelService {
|
||||
allMessageActions.add(textChannel.sendFile(messageToSend.getFileToSend()));
|
||||
}
|
||||
}
|
||||
Set<Message.MentionType> allowedMentions = getAllowedMentionsFor(textChannel, messageToSend);
|
||||
Set<Message.MentionType> allowedMentions = allowedMentionService.getAllowedMentionsFor(textChannel, messageToSend);
|
||||
allMessageActions.forEach(messageAction -> {
|
||||
if(messageToSend.getReferencedMessageId() != null) {
|
||||
messageAction = messageAction.referenceById(messageToSend.getReferencedMessageId());
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package dev.sheldan.abstracto.core.service;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@Component
|
||||
public class ComponentServiceBean implements ComponentService {
|
||||
@Override
|
||||
public String generateComponentId(Long serverId) {
|
||||
return generateComponentId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String generateComponentId() {
|
||||
return UUID.randomUUID().toString();
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import dev.sheldan.abstracto.core.service.management.DefaultFeatureModeManagemen
|
||||
import dev.sheldan.abstracto.core.service.management.FeatureFlagManagementService;
|
||||
import dev.sheldan.abstracto.core.service.management.FeatureModeManagementService;
|
||||
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
|
||||
import net.dv8tion.jda.api.entities.Guild;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@@ -92,6 +93,11 @@ public class FeatureModeServiceBean implements FeatureModeService {
|
||||
return featureModeActive(featureDefinition, server, mode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean featureModeActive(FeatureDefinition featureDefinition, Guild guild, FeatureMode mode) {
|
||||
return featureModeActive(featureDefinition, guild.getIdLong(), mode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateActiveFeatureMode(Long serverId, FeatureDefinition featureDefinition, FeatureMode mode) {
|
||||
boolean featureModeActive = featureModeActive(featureDefinition, serverId, mode);
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
package dev.sheldan.abstracto.core.service;
|
||||
|
||||
import dev.sheldan.abstracto.core.interaction.InteractionService;
|
||||
import dev.sheldan.abstracto.core.metric.service.CounterMetric;
|
||||
import dev.sheldan.abstracto.core.metric.service.MetricService;
|
||||
import dev.sheldan.abstracto.core.metric.service.MetricTag;
|
||||
import dev.sheldan.abstracto.core.models.database.AServer;
|
||||
import dev.sheldan.abstracto.core.service.management.ComponentPayloadManagementService;
|
||||
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
|
||||
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
|
||||
import dev.sheldan.abstracto.core.templating.service.TemplateService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.dv8tion.jda.api.entities.Message;
|
||||
import net.dv8tion.jda.api.entities.MessageEmbed;
|
||||
import net.dv8tion.jda.api.interactions.Interaction;
|
||||
import net.dv8tion.jda.api.interactions.InteractionHook;
|
||||
import net.dv8tion.jda.api.interactions.components.ActionRow;
|
||||
import net.dv8tion.jda.api.requests.restaction.WebhookMessageAction;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import static dev.sheldan.abstracto.core.config.MetricConstants.DISCORD_API_INTERACTION_METRIC;
|
||||
import static dev.sheldan.abstracto.core.config.MetricConstants.INTERACTION_TYPE;
|
||||
import static dev.sheldan.abstracto.core.service.MessageServiceBean.MESSAGE_SEND_METRIC;
|
||||
|
||||
@Component
|
||||
@Slf4j
|
||||
public class InteractionServiceBean implements InteractionService {
|
||||
|
||||
@Autowired
|
||||
private MetricService metricService;
|
||||
|
||||
@Autowired
|
||||
private ServerManagementService serverManagementService;
|
||||
|
||||
@Autowired
|
||||
private AllowedMentionService allowedMentionService;
|
||||
|
||||
@Autowired
|
||||
private ComponentPayloadManagementService componentPayloadManagementService;
|
||||
|
||||
@Autowired
|
||||
private TemplateService templateService;
|
||||
|
||||
public static final CounterMetric EPHEMERAL_MESSAGES_SEND = CounterMetric
|
||||
.builder()
|
||||
.name(DISCORD_API_INTERACTION_METRIC)
|
||||
.tagList(Arrays.asList(MetricTag.getTag(INTERACTION_TYPE, "message.ephemeral.send")))
|
||||
.build();
|
||||
|
||||
@Override
|
||||
public List<CompletableFuture<Message>> sendMessageToInteraction(MessageToSend messageToSend, InteractionHook interactionHook){
|
||||
List<CompletableFuture<Message>> futures = new ArrayList<>();
|
||||
List<WebhookMessageAction<Message>> allMessageActions = new ArrayList<>();
|
||||
int iterations = Math.min(messageToSend.getMessages().size(), messageToSend.getEmbeds().size());
|
||||
for (int i = 0; i < iterations; i++) {
|
||||
metricService.incrementCounter(MESSAGE_SEND_METRIC);
|
||||
String text = messageToSend.getMessages().get(i);
|
||||
MessageEmbed embed = messageToSend.getEmbeds().get(i);
|
||||
WebhookMessageAction<Message> messageAction = interactionHook.sendMessage(text).addEmbeds(embed);
|
||||
allMessageActions.add(messageAction);
|
||||
}
|
||||
// one of these loops will get additional iterations, if the number is different, not both
|
||||
for (int i = iterations; i < messageToSend.getMessages().size(); i++) {
|
||||
metricService.incrementCounter(MESSAGE_SEND_METRIC);
|
||||
String text = messageToSend.getMessages().get(i);
|
||||
WebhookMessageAction<Message> messageAction = interactionHook.sendMessage(text);
|
||||
allMessageActions.add(messageAction);
|
||||
}
|
||||
for (int i = iterations; i < messageToSend.getEmbeds().size(); i++) {
|
||||
metricService.incrementCounter(MESSAGE_SEND_METRIC);
|
||||
MessageEmbed embed = messageToSend.getEmbeds().get(i);
|
||||
WebhookMessageAction<Message> messageAction = interactionHook.sendMessageEmbeds(embed);
|
||||
allMessageActions.add(messageAction);
|
||||
}
|
||||
|
||||
List<ActionRow> actionRows = messageToSend.getActionRows();
|
||||
if(!actionRows.isEmpty()) {
|
||||
AServer server = serverManagementService.loadServer(interactionHook.getInteraction().getGuild());
|
||||
allMessageActions.set(0, allMessageActions.get(0).addActionRows(actionRows));
|
||||
actionRows.forEach(components -> components.forEach(component -> {
|
||||
String id = component.getId();
|
||||
MessageToSend.ComponentConfig payload = messageToSend.getComponentPayloads().get(id);
|
||||
if(payload.getPersistCallback()) {
|
||||
componentPayloadManagementService.createPayload(id, payload.getPayload(), payload.getPayloadType(), payload.getComponentOrigin(), server);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
if(messageToSend.getEphemeral()) {
|
||||
Interaction interaction = interactionHook.getInteraction();
|
||||
log.info("Sending ephemeral message to inter action in guild {} in channel {} for user {}.",
|
||||
interaction.getGuild().getIdLong(), interaction.getChannel(),
|
||||
interaction.getMember().getIdLong());
|
||||
metricService.incrementCounter(EPHEMERAL_MESSAGES_SEND);
|
||||
}
|
||||
|
||||
if(messageToSend.hasFileToSend()) {
|
||||
if(!allMessageActions.isEmpty()) {
|
||||
// in case there has not been a message, we need to increment it
|
||||
allMessageActions.set(0, allMessageActions.get(0).addFile(messageToSend.getFileToSend()));
|
||||
} else {
|
||||
metricService.incrementCounter(MESSAGE_SEND_METRIC);
|
||||
allMessageActions.add(interactionHook.sendFile(messageToSend.getFileToSend()));
|
||||
}
|
||||
}
|
||||
Set<Message.MentionType> allowedMentions = allowedMentionService.getAllowedMentionsFor(interactionHook.getInteraction().getMessageChannel(), messageToSend);
|
||||
allMessageActions.forEach(messageAction -> futures.add(messageAction.allowedMentions(allowedMentions).setEphemeral(messageToSend.getEphemeral()).submit()));
|
||||
return futures;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CompletableFuture<Message>> sendMessageToInteraction(String templateKey, Object model, InteractionHook interactionHook) {
|
||||
MessageToSend messageToSend = templateService.renderEmbedTemplate(templateKey, model, interactionHook.getInteraction().getGuild().getIdLong());
|
||||
return sendMessageToInteraction(messageToSend, interactionHook);
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void postConstruct() {
|
||||
metricService.registerCounter(EPHEMERAL_MESSAGES_SEND, "Ephemeral messages send");
|
||||
}
|
||||
}
|
||||
@@ -228,6 +228,11 @@ public class MessageServiceBean implements MessageService {
|
||||
return deleteMessageWithAction(message).submit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> clearButtons(Message message) {
|
||||
return message.editMessage(message).setActionRows().submit().thenApply(message1 -> null);
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void postConstruct() {
|
||||
metricService.registerCounter(MESSAGE_SEND_METRIC, "Messages send to discord");
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
package dev.sheldan.abstracto.core.service.management;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import dev.sheldan.abstracto.core.models.database.AServer;
|
||||
import dev.sheldan.abstracto.core.models.database.ComponentPayload;
|
||||
import dev.sheldan.abstracto.core.models.template.button.ButtonConfigModel;
|
||||
import dev.sheldan.abstracto.core.repository.ComponentPayloadRepository;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Component
|
||||
@Slf4j
|
||||
public class ComponentPayloadManagementServiceBean implements ComponentPayloadManagementService {
|
||||
|
||||
@Autowired
|
||||
private ComponentPayloadRepository repository;
|
||||
|
||||
@Autowired
|
||||
private Gson gson;
|
||||
|
||||
@Override
|
||||
public ComponentPayload createPayload(String id, String payload, Class payloadType, String buttonOrigin, AServer server) {
|
||||
ComponentPayload componentPayload = ComponentPayload
|
||||
.builder()
|
||||
.origin(buttonOrigin)
|
||||
.id(id)
|
||||
.payload(payload)
|
||||
.payloadType(payloadType.getTypeName())
|
||||
.server(server)
|
||||
.build();
|
||||
return repository.save(componentPayload);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ComponentPayload createPayload(ButtonConfigModel buttonConfigModel, AServer server) {
|
||||
String payload = gson.toJson(buttonConfigModel.getButtonPayload());
|
||||
return createPayload(buttonConfigModel.getButtonId(), payload, buttonConfigModel.getPayloadType(), buttonConfigModel.getOrigin(), server);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ComponentPayload> findPayload(String id) {
|
||||
return repository.findById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ComponentPayload> findPayloadsOfOriginInServer(String buttonOrigin, AServer server) {
|
||||
return repository.findByServerAndOrigin(server, buttonOrigin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deletePayload(String id) {
|
||||
repository.deleteById(id);
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package dev.sheldan.abstracto.core.templating.config;
|
||||
import dev.sheldan.abstracto.core.templating.loading.DatabaseTemplateLoader;
|
||||
import dev.sheldan.abstracto.core.templating.method.DateMethod;
|
||||
import dev.sheldan.abstracto.core.templating.method.DurationMethod;
|
||||
import dev.sheldan.abstracto.core.templating.method.JSONMethod;
|
||||
import dev.sheldan.abstracto.core.templating.model.database.AutoLoadMacro;
|
||||
import dev.sheldan.abstracto.core.templating.service.management.AutoLoadMacroManagementService;
|
||||
import freemarker.template.Configuration;
|
||||
@@ -28,6 +29,9 @@ public class FreemarkerConfiguration {
|
||||
@Autowired
|
||||
private DurationMethod durationMethod;
|
||||
|
||||
@Autowired
|
||||
private JSONMethod jsonMethod;
|
||||
|
||||
@Autowired
|
||||
private DateMethod instantMethod;
|
||||
|
||||
@@ -49,6 +53,7 @@ public class FreemarkerConfiguration {
|
||||
Configuration configuration = factory.createConfiguration();
|
||||
configuration.setSharedVariable("fmtDuration", durationMethod);
|
||||
configuration.setSharedVariable("formatDate", instantMethod);
|
||||
configuration.setSharedVariable("toJSON", jsonMethod);
|
||||
// 10 minutes template cache
|
||||
configuration.setTemplateUpdateDelayMilliseconds(600000);
|
||||
List<String> macrosToLoad = macroManagementService.loadAllMacros().stream()
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package dev.sheldan.abstracto.core.templating.method;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import freemarker.ext.beans.StringModel;
|
||||
import freemarker.template.TemplateMethodModelEx;
|
||||
import freemarker.template.TemplateModelException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Formats the passed {@link Instant} or {@link OffsetDateTime} object with the given Formatter. The format will be directly passed to {@link DateTimeFormatter}.
|
||||
*/
|
||||
@Component
|
||||
public class JSONMethod implements TemplateMethodModelEx {
|
||||
|
||||
@Autowired
|
||||
private Gson gson;
|
||||
|
||||
/**
|
||||
* Renders the given {@link Instant} object with the given String. Internally {@link DateTimeFormatter} will be used.
|
||||
* @param arguments The list of arguments, first element must be an {@link Instant} or {@link OffsetDateTime} and the second one must be a {@link String}.
|
||||
* @return The formatted {@link Instant} as a string.
|
||||
* @throws TemplateModelException If there are less or more arguments in the list and if the first element is not a {@link Instant} of {@link OffsetDateTime}
|
||||
*/
|
||||
@Override
|
||||
public Object exec(List arguments) throws TemplateModelException {
|
||||
if (arguments.size() != 1) {
|
||||
throw new TemplateModelException("Incorrect parameters passed.");
|
||||
}
|
||||
Object o = arguments.get(0);
|
||||
Object wrappedObject = ((StringModel) o).getWrappedObject();
|
||||
return gson.toJson(wrappedObject);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package dev.sheldan.abstracto.core.templating.model;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
public class ButtonConfig {
|
||||
private String label;
|
||||
private String id;
|
||||
private String url;
|
||||
private Boolean disabled;
|
||||
private String emoteMarkdown;
|
||||
private ButtonStyleConfig buttonStyle;
|
||||
private String buttonPayload;
|
||||
private String payloadType;
|
||||
private ButtonMetaConfig metaConfig;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package dev.sheldan.abstracto.core.templating.model;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
public class ButtonMetaConfig {
|
||||
private Boolean forceNewRow;
|
||||
private Boolean generateRandomUUID;
|
||||
private String buttonOrigin;
|
||||
private Boolean persistCallback;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package dev.sheldan.abstracto.core.templating.model;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import net.dv8tion.jda.api.interactions.components.ButtonStyle;
|
||||
|
||||
public enum ButtonStyleConfig {
|
||||
@SerializedName("primary")
|
||||
PRIMARY,
|
||||
@SerializedName("secondary")
|
||||
SECONDARY,
|
||||
@SerializedName("success")
|
||||
SUCCESS,
|
||||
@SerializedName("danger")
|
||||
DANGER,
|
||||
@SerializedName("link")
|
||||
LINK;
|
||||
|
||||
public static ButtonStyle getStyle(ButtonStyleConfig config) {
|
||||
switch (config) {
|
||||
case PRIMARY: return ButtonStyle.PRIMARY;
|
||||
case SECONDARY: return ButtonStyle.SECONDARY;
|
||||
case SUCCESS: return ButtonStyle.SUCCESS;
|
||||
case DANGER: return ButtonStyle.DANGER;
|
||||
case LINK: default: return ButtonStyle.LINK;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -57,4 +57,5 @@ public class EmbedConfiguration {
|
||||
*/
|
||||
private String additionalMessage;
|
||||
private MetaEmbedConfiguration metaConfig;
|
||||
private List<ButtonConfig> buttons;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,8 @@ public class MetaEmbedConfiguration {
|
||||
private Integer descriptionMessageLengthLimit;
|
||||
private Integer messageLimit;
|
||||
|
||||
@Builder.Default
|
||||
private boolean ephemeral = false;
|
||||
|
||||
private boolean preventEmptyEmbed;
|
||||
private boolean allowsRoleMention;
|
||||
|
||||
@@ -3,6 +3,7 @@ package dev.sheldan.abstracto.core.templating.service;
|
||||
import com.google.gson.Gson;
|
||||
import dev.sheldan.abstracto.core.command.config.features.CoreFeatureConfig;
|
||||
import dev.sheldan.abstracto.core.config.ServerContext;
|
||||
import dev.sheldan.abstracto.core.exception.AbstractoRunTimeException;
|
||||
import dev.sheldan.abstracto.core.service.ConfigService;
|
||||
import dev.sheldan.abstracto.core.templating.Templatable;
|
||||
import dev.sheldan.abstracto.core.templating.exception.TemplatingException;
|
||||
@@ -15,8 +16,11 @@ import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.dv8tion.jda.api.EmbedBuilder;
|
||||
import net.dv8tion.jda.api.entities.Emoji;
|
||||
import net.dv8tion.jda.api.entities.Message;
|
||||
import net.dv8tion.jda.api.entities.MessageEmbed;
|
||||
import net.dv8tion.jda.api.interactions.components.ActionRow;
|
||||
import net.dv8tion.jda.api.interactions.components.Button;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@@ -95,6 +99,57 @@ public class TemplateServiceBean implements TemplateService {
|
||||
if (author != null) {
|
||||
firstBuilder.setAuthor(author.getName(), author.getUrl(), author.getAvatar());
|
||||
}
|
||||
List<ActionRow> buttons = new ArrayList<>();
|
||||
Map<String, MessageToSend.ComponentConfig> componentPayloads = new HashMap<>();
|
||||
if(embedConfiguration.getButtons() != null) {
|
||||
ActionRow currentRow = null;
|
||||
for (ButtonConfig buttonConfig : embedConfiguration.getButtons()) {
|
||||
ButtonMetaConfig metaConfig = buttonConfig.getMetaConfig() != null ? buttonConfig.getMetaConfig() : null;
|
||||
String id = metaConfig != null && Boolean.TRUE.equals(metaConfig.getGenerateRandomUUID()) ?
|
||||
UUID.randomUUID().toString() : buttonConfig.getId();
|
||||
String componentOrigin = metaConfig != null ? metaConfig.getButtonOrigin() : null;
|
||||
MessageToSend.ComponentConfig componentConfig = null;
|
||||
try {
|
||||
componentConfig = MessageToSend.ComponentConfig
|
||||
.builder()
|
||||
.componentOrigin(componentOrigin)
|
||||
.persistCallback(metaConfig != null && Boolean.TRUE.equals(metaConfig.getPersistCallback()))
|
||||
.payload(buttonConfig.getButtonPayload())
|
||||
.payloadType(buttonConfig.getPayloadType() != null ? Class.forName(buttonConfig.getPayloadType()) : null)
|
||||
.build();
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new AbstractoRunTimeException("Referenced class in button config could not be found: " + buttonConfig.getPayloadType(), e);
|
||||
}
|
||||
componentPayloads.put(id, componentConfig);
|
||||
Button createdButton = Button.primary(id, buttonConfig.getLabel());
|
||||
if (buttonConfig.getUrl() != null) {
|
||||
createdButton = createdButton.withUrl(buttonConfig.getUrl());
|
||||
}
|
||||
if (buttonConfig.getDisabled() != null) {
|
||||
createdButton = createdButton.withDisabled(buttonConfig.getDisabled());
|
||||
}
|
||||
if (buttonConfig.getEmoteMarkdown() != null) {
|
||||
createdButton = createdButton.withEmoji(Emoji.fromMarkdown(buttonConfig.getEmoteMarkdown()));
|
||||
}
|
||||
createdButton = createdButton.withStyle(ButtonStyleConfig.getStyle(buttonConfig.getButtonStyle()));
|
||||
if(currentRow == null) {
|
||||
currentRow = ActionRow.of(createdButton);
|
||||
} else if (
|
||||
(
|
||||
metaConfig != null &&
|
||||
Boolean.TRUE.equals(metaConfig.getForceNewRow())
|
||||
)
|
||||
|| currentRow.getComponents().size() == 5) {
|
||||
buttons.add(currentRow);
|
||||
currentRow = ActionRow.of(createdButton);
|
||||
} else {
|
||||
currentRow.getComponents().add(createdButton);
|
||||
}
|
||||
}
|
||||
if(currentRow != null) {
|
||||
buttons.add(currentRow);
|
||||
}
|
||||
}
|
||||
|
||||
String thumbnail = embedConfiguration.getThumbnail();
|
||||
if (thumbnail != null) {
|
||||
@@ -137,6 +192,10 @@ public class TemplateServiceBean implements TemplateService {
|
||||
embedConfiguration.setAdditionalMessage(embedConfiguration.getAdditionalMessage().substring(0, embedConfiguration.getMetaConfig().getAdditionalMessageLengthLimit()));
|
||||
}
|
||||
|
||||
boolean isEphemeral = false;
|
||||
if(embedConfiguration.getMetaConfig() != null) {
|
||||
isEphemeral = Boolean.TRUE.equals(embedConfiguration.getMetaConfig().isEphemeral());
|
||||
}
|
||||
|
||||
String additionalMessage = embedConfiguration.getAdditionalMessage();
|
||||
if(additionalMessage != null) {
|
||||
@@ -187,6 +246,9 @@ public class TemplateServiceBean implements TemplateService {
|
||||
.embeds(embeds)
|
||||
.messageConfig(createMessageConfig(embedConfiguration.getMetaConfig()))
|
||||
.messages(messages)
|
||||
.ephemeral(isEphemeral)
|
||||
.actionRows(buttons)
|
||||
.componentPayloads(componentPayloads)
|
||||
.referencedMessageId(referencedMessageId)
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
|
||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
|
||||
xmlns:pro="http://www.liquibase.org/xml/ns/pro"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog dbchangelog.xsd
|
||||
http://www.liquibase.org/xml/ns/dbchangelog-ext dbchangelog.xsd
|
||||
http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" >
|
||||
<include file="tables/tables.xml" relativeToChangelogFile="true"/>
|
||||
</databaseChangeLog>
|
||||
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
|
||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
|
||||
xmlns:pro="http://www.liquibase.org/xml/ns/pro"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog dbchangelog.xsd
|
||||
http://www.liquibase.org/xml/ns/dbchangelog-ext dbchangelog.xsd
|
||||
http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" >
|
||||
<changeSet author="Sheldan" id="component_payload-table">
|
||||
<createTable tableName="component_payload">
|
||||
<column name="id" type="VARCHAR(100)">
|
||||
<constraints nullable="false" primaryKey="true" primaryKeyName="component_payload_pkey"/>
|
||||
</column>
|
||||
<column name="payload" type="TEXT" />
|
||||
<column name="origin" type="VARCHAR(128)" />
|
||||
<column name="payload_type" type="VARCHAR(255)" />
|
||||
<column name="server_id" type="BIGINT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="created" type="TIMESTAMP WITHOUT TIME ZONE">
|
||||
<constraints nullable="true"/>
|
||||
</column>
|
||||
</createTable>
|
||||
<addForeignKeyConstraint baseColumnNames="server_id" baseTableName="component_payload" constraintName="fk_component_payload_server"
|
||||
deferrable="false" initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION"
|
||||
referencedColumnNames="id" referencedTableName="server" validate="true"/>
|
||||
<sql>
|
||||
DROP TRIGGER IF EXISTS component_payload_insert_trigger ON component_payload;
|
||||
CREATE TRIGGER component_payload_insert_trigger BEFORE INSERT ON component_payload FOR EACH ROW EXECUTE PROCEDURE insert_trigger_procedure();
|
||||
</sql>
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
|
||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
|
||||
xmlns:pro="http://www.liquibase.org/xml/ns/pro"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog dbchangelog.xsd
|
||||
http://www.liquibase.org/xml/ns/dbchangelog-ext dbchangelog.xsd
|
||||
http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" >
|
||||
<include file="component_payload.xml" relativeToChangelogFile="true"/>
|
||||
</databaseChangeLog>
|
||||
@@ -16,4 +16,5 @@
|
||||
<include file="1.2.9-core/collection.xml" relativeToChangelogFile="true"/>
|
||||
<include file="1.2.11-core/collection.xml" relativeToChangelogFile="true"/>
|
||||
<include file="1.2.12/collection.xml" relativeToChangelogFile="true"/>
|
||||
<include file="1.3.0/collection.xml" relativeToChangelogFile="true"/>
|
||||
</databaseChangeLog>
|
||||
@@ -3,6 +3,8 @@ package dev.sheldan.abstracto.core.command.service;
|
||||
import dev.sheldan.abstracto.core.command.Command;
|
||||
import dev.sheldan.abstracto.core.command.execution.CommandContext;
|
||||
import dev.sheldan.abstracto.core.command.execution.CommandResult;
|
||||
import dev.sheldan.abstracto.core.listener.async.jda.ButtonClickedListener;
|
||||
import dev.sheldan.abstracto.core.models.listener.ButtonClickedListenerModel;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
import net.dv8tion.jda.api.entities.MessageChannel;
|
||||
import net.dv8tion.jda.api.events.message.guild.GuildMessageReceivedEvent;
|
||||
@@ -10,6 +12,7 @@ import net.dv8tion.jda.api.events.message.priv.PrivateMessageReceivedEvent;
|
||||
|
||||
public interface ExceptionService {
|
||||
CommandResult reportExceptionToContext(Throwable exception, CommandContext context, Command command);
|
||||
void reportExceptionToInteraction(Throwable exception, ButtonClickedListenerModel interActionContext, ButtonClickedListener executedListener);
|
||||
void reportExceptionToGuildMessageReceivedContext(Throwable exception, GuildMessageReceivedEvent event);
|
||||
void reportExceptionToPrivateMessageReceivedContext(Throwable exception, PrivateMessageReceivedEvent event);
|
||||
void reportExceptionToChannel(Throwable exception, MessageChannel channel, Member member);
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package dev.sheldan.abstracto.core.interaction;
|
||||
|
||||
import dev.sheldan.abstracto.core.templating.Templatable;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
import net.dv8tion.jda.api.entities.User;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
public class GenericInteractionExceptionModel {
|
||||
private Member member;
|
||||
private User user;
|
||||
private Throwable throwable;
|
||||
|
||||
public Templatable getTemplate() {
|
||||
Throwable current = throwable;
|
||||
while(!(current instanceof Templatable) && (current.getCause() != null && !current.getCause().equals(current))) {
|
||||
current = current.getCause();
|
||||
}
|
||||
if(current instanceof Templatable) {
|
||||
return (Templatable) current;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package dev.sheldan.abstracto.core.interaction;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
public class InteractionResult {
|
||||
private InteractionResultState result;
|
||||
private String message;
|
||||
private Throwable throwable;
|
||||
|
||||
public static InteractionResult fromSuccess() {
|
||||
return InteractionResult.builder().result(InteractionResultState.SUCCESSFUL).build();
|
||||
}
|
||||
|
||||
public static InteractionResult fromError(String message){
|
||||
return InteractionResult.builder().result(InteractionResultState.ERROR).message(message).build();
|
||||
}
|
||||
|
||||
public static InteractionResult fromError(String message, Throwable throwable) {
|
||||
return InteractionResult.builder().result(InteractionResultState.ERROR).message(message).throwable(throwable).build();
|
||||
}
|
||||
|
||||
public static InteractionResult fromIgnored() {
|
||||
return InteractionResult.builder().result(InteractionResultState.IGNORED).build();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package dev.sheldan.abstracto.core.interaction;
|
||||
|
||||
public enum InteractionResultState {
|
||||
ERROR, SUCCESSFUL, IGNORED
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package dev.sheldan.abstracto.core.interaction;
|
||||
|
||||
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
|
||||
import net.dv8tion.jda.api.entities.Message;
|
||||
import net.dv8tion.jda.api.interactions.InteractionHook;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public interface InteractionService {
|
||||
List<CompletableFuture<Message>> sendMessageToInteraction(MessageToSend messageToSend, InteractionHook interactionHook);
|
||||
List<CompletableFuture<Message>> sendMessageToInteraction(String templateKey, Object model, InteractionHook interactionHook);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package dev.sheldan.abstracto.core.interaction;
|
||||
|
||||
import dev.sheldan.abstracto.core.listener.async.jda.ButtonClickedListener;
|
||||
import dev.sheldan.abstracto.core.models.listener.ButtonClickedListenerModel;
|
||||
|
||||
public interface PostInteractionExecution {
|
||||
void execute(ButtonClickedListenerModel interActionContext, InteractionResult interactionResult, ButtonClickedListener executedListener);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package dev.sheldan.abstracto.core.listener;
|
||||
|
||||
public enum ButtonClickedListenerResult implements ListenerExecutionResult {
|
||||
ACKNOWLEDGED, IGNORED
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package dev.sheldan.abstracto.core.listener.async.jda;
|
||||
|
||||
import dev.sheldan.abstracto.core.Prioritized;
|
||||
import dev.sheldan.abstracto.core.listener.ButtonClickedListenerResult;
|
||||
import dev.sheldan.abstracto.core.listener.FeatureAwareListener;
|
||||
import dev.sheldan.abstracto.core.models.listener.ButtonClickedListenerModel;
|
||||
|
||||
public interface ButtonClickedListener extends FeatureAwareListener<ButtonClickedListenerModel, ButtonClickedListenerResult>, Prioritized {
|
||||
Boolean handlesEvent(ButtonClickedListenerModel model);
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package dev.sheldan.abstracto.core.models.database;
|
||||
|
||||
import lombok.*;
|
||||
|
||||
import javax.persistence.*;
|
||||
import java.time.Instant;
|
||||
|
||||
@Entity
|
||||
@Table(name = "component_payload")
|
||||
@Getter
|
||||
@Builder
|
||||
@Setter
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@EqualsAndHashCode
|
||||
public class ComponentPayload {
|
||||
@Id
|
||||
@Column(name = "id", nullable = false, length = 100)
|
||||
private String id;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@Getter
|
||||
@Setter
|
||||
@JoinColumn(name = "server_id", nullable = false)
|
||||
private AServer server;
|
||||
|
||||
@Lob
|
||||
@org.hibernate.annotations.Type(type = "org.hibernate.type.TextType")
|
||||
@Column(name = "payload")
|
||||
private String payload;
|
||||
|
||||
@Column(name = "payload_type", length = 255)
|
||||
private String payloadType;
|
||||
|
||||
@Column(name = "origin", length = 128)
|
||||
private String origin;
|
||||
|
||||
@Column(name = "created", nullable = false, insertable = false, updatable = false)
|
||||
private Instant created;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package dev.sheldan.abstracto.core.models.listener;
|
||||
|
||||
import dev.sheldan.abstracto.core.listener.FeatureAwareListenerModel;
|
||||
import dev.sheldan.abstracto.core.models.template.button.ButtonPayload;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import net.dv8tion.jda.api.events.interaction.ButtonClickEvent;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
public class ButtonClickedListenerModel implements FeatureAwareListenerModel {
|
||||
|
||||
private ButtonClickEvent event;
|
||||
private String payload;
|
||||
private String origin;
|
||||
private ButtonPayload deserializedPayload;
|
||||
|
||||
@Override
|
||||
public Long getServerId() {
|
||||
return event.getGuild().getIdLong();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package dev.sheldan.abstracto.core.models.template.button;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
public class ButtonConfigModel {
|
||||
private String buttonId;
|
||||
private ButtonPayload buttonPayload;
|
||||
private Class payloadType;
|
||||
private String origin;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package dev.sheldan.abstracto.core.models.template.button;
|
||||
|
||||
public interface ButtonPayload {
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
package dev.sheldan.abstracto.core.service;
|
||||
|
||||
import dev.sheldan.abstracto.core.models.database.AllowedMention;
|
||||
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
|
||||
import net.dv8tion.jda.api.entities.Message;
|
||||
import net.dv8tion.jda.api.entities.MessageChannel;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
@@ -13,4 +15,5 @@ public interface AllowedMentionService {
|
||||
AllowedMention getDefaultAllowedMention();
|
||||
AllowedMention getEffectiveAllowedMention(Long serverId);
|
||||
Message.MentionType getMentionTypeFromString(String input);
|
||||
Set<Message.MentionType> getAllowedMentionsFor(MessageChannel channel, MessageToSend messageToSend);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
package dev.sheldan.abstracto.core.service;
|
||||
|
||||
public interface ComponentService {
|
||||
String generateComponentId(Long serverId);
|
||||
String generateComponentId();
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import dev.sheldan.abstracto.core.config.FeatureMode;
|
||||
import dev.sheldan.abstracto.core.models.database.AFeature;
|
||||
import dev.sheldan.abstracto.core.models.database.AServer;
|
||||
import dev.sheldan.abstracto.core.models.template.commands.FeatureModeDisplay;
|
||||
import net.dv8tion.jda.api.entities.Guild;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -16,6 +17,7 @@ public interface FeatureModeService {
|
||||
void disableFeatureModeForFeature(FeatureDefinition featureDefinition, AServer server, FeatureMode mode);
|
||||
boolean featureModeActive(FeatureDefinition featureDefinition, AServer server, FeatureMode mode);
|
||||
boolean featureModeActive(FeatureDefinition featureDefinition, Long serverId, FeatureMode mode);
|
||||
boolean featureModeActive(FeatureDefinition featureDefinition, Guild guild, FeatureMode mode);
|
||||
void validateActiveFeatureMode(Long serverId, FeatureDefinition featureDefinition, FeatureMode mode);
|
||||
FeatureMode getFeatureModeForKey(String key);
|
||||
List<FeatureMode> getAllAvailableFeatureModes();
|
||||
|
||||
@@ -39,4 +39,5 @@ public interface MessageService {
|
||||
MessageAction editMessage(Message message, String text, MessageEmbed messageEmbed);
|
||||
AuditableRestAction<Void> deleteMessageWithAction(Message message);
|
||||
CompletableFuture<Void> deleteMessage(Message message);
|
||||
CompletableFuture<Void> clearButtons(Message message);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package dev.sheldan.abstracto.core.service.management;
|
||||
|
||||
import dev.sheldan.abstracto.core.models.database.AServer;
|
||||
import dev.sheldan.abstracto.core.models.database.ComponentPayload;
|
||||
import dev.sheldan.abstracto.core.models.template.button.ButtonConfigModel;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface ComponentPayloadManagementService {
|
||||
ComponentPayload createPayload(String id, String payload, Class payloadType, String buttonOrigin, AServer server);
|
||||
ComponentPayload createPayload(ButtonConfigModel buttonConfigModel, AServer server);
|
||||
Optional<ComponentPayload> findPayload(String id);
|
||||
List<ComponentPayload> findPayloadsOfOriginInServer(String buttonOrigin, AServer server);
|
||||
void deletePayload(String id);
|
||||
}
|
||||
@@ -4,10 +4,13 @@ import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import net.dv8tion.jda.api.entities.MessageEmbed;
|
||||
import net.dv8tion.jda.api.interactions.components.ActionRow;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A full message which is ready to be send. This message can contain an arbitrary amount of embeds and a string message.
|
||||
@@ -32,8 +35,25 @@ public class MessageToSend {
|
||||
private File fileToSend;
|
||||
private MessageConfig messageConfig;
|
||||
private Long referencedMessageId;
|
||||
@Builder.Default
|
||||
private List<ActionRow> actionRows = new ArrayList<>();
|
||||
@Builder.Default
|
||||
private Map<String, ComponentConfig> componentPayloads = new HashMap<>();
|
||||
|
||||
@Builder.Default
|
||||
private Boolean ephemeral = false;
|
||||
|
||||
public boolean hasFileToSend() {
|
||||
return fileToSend != null;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
public static class ComponentConfig {
|
||||
private String payload;
|
||||
private String componentOrigin;
|
||||
private Class payloadType;
|
||||
private Boolean persistCallback;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user