[AB-296] adding support for buttons

adding buttons for message embed via feature mode
This commit is contained in:
Sheldan
2021-07-03 10:40:27 +02:00
parent bbc5918d88
commit 61eefd53c3
63 changed files with 1379 additions and 104 deletions

View File

@@ -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

View File

@@ -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");

View File

@@ -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);
}
}
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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());

View File

@@ -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();
}
}

View File

@@ -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);

View File

@@ -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");
}
}

View File

@@ -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");

View File

@@ -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);
}
}

View File

@@ -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()

View File

@@ -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);
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}
}
}

View File

@@ -57,4 +57,5 @@ public class EmbedConfiguration {
*/
private String additionalMessage;
private MetaEmbedConfiguration metaConfig;
private List<ButtonConfig> buttons;
}

View File

@@ -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;

View File

@@ -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();
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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);

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -0,0 +1,5 @@
package dev.sheldan.abstracto.core.interaction;
public enum InteractionResultState {
ERROR, SUCCESSFUL, IGNORED
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -0,0 +1,5 @@
package dev.sheldan.abstracto.core.listener;
public enum ButtonClickedListenerResult implements ListenerExecutionResult {
ACKNOWLEDGED, IGNORED
}

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -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();
}
}

View File

@@ -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;
}

View File

@@ -0,0 +1,4 @@
package dev.sheldan.abstracto.core.models.template.button;
public interface ButtonPayload {
}

View File

@@ -1,20 +0,0 @@
package dev.sheldan.abstracto.core.models.template.listener;
import dev.sheldan.abstracto.core.models.cache.CachedMessage;
import dev.sheldan.abstracto.core.models.context.UserInitiatedServerContext;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.SuperBuilder;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.TextChannel;
import net.dv8tion.jda.api.entities.User;
@Getter
@Setter
@SuperBuilder
public class MessageEmbeddedModel extends UserInitiatedServerContext {
private CachedMessage embeddedMessage;
private User author;
private TextChannel sourceChannel;
private Member embeddingUser;
}

View File

@@ -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);
}

View File

@@ -0,0 +1,6 @@
package dev.sheldan.abstracto.core.service;
public interface ComponentService {
String generateComponentId(Long serverId);
String generateComponentId();
}

View File

@@ -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();

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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;
}
}