diff --git a/abstracto-application/abstracto-modules/assignable-roles/assignable-roles-impl/src/main/java/dev/sheldan/abstracto/assignableroles/service/AssignableRolePlaceServiceBean.java b/abstracto-application/abstracto-modules/assignable-roles/assignable-roles-impl/src/main/java/dev/sheldan/abstracto/assignableroles/service/AssignableRolePlaceServiceBean.java index 789d8e59c..bd053558f 100644 --- a/abstracto-application/abstracto-modules/assignable-roles/assignable-roles-impl/src/main/java/dev/sheldan/abstracto/assignableroles/service/AssignableRolePlaceServiceBean.java +++ b/abstracto-application/abstracto-modules/assignable-roles/assignable-roles-impl/src/main/java/dev/sheldan/abstracto/assignableroles/service/AssignableRolePlaceServiceBean.java @@ -31,7 +31,7 @@ import net.dv8tion.jda.api.entities.channel.middleman.GuildChannel; import net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel; import net.dv8tion.jda.api.entities.emoji.CustomEmoji; import net.dv8tion.jda.api.entities.emoji.Emoji; -import net.dv8tion.jda.api.interactions.components.buttons.ButtonStyle; +import net.dv8tion.jda.api.components.buttons.ButtonStyle; import org.apache.commons.lang3.BooleanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; diff --git a/abstracto-application/abstracto-modules/link-embed/link-embed-impl/src/main/java/dev/sheldan/abstracto/linkembed/service/MessageEmbedServiceBean.java b/abstracto-application/abstracto-modules/link-embed/link-embed-impl/src/main/java/dev/sheldan/abstracto/linkembed/service/MessageEmbedServiceBean.java index 3e9c5c9b7..412ccd6fd 100644 --- a/abstracto-application/abstracto-modules/link-embed/link-embed-impl/src/main/java/dev/sheldan/abstracto/linkembed/service/MessageEmbedServiceBean.java +++ b/abstracto-application/abstracto-modules/link-embed/link-embed-impl/src/main/java/dev/sheldan/abstracto/linkembed/service/MessageEmbedServiceBean.java @@ -357,7 +357,9 @@ public class MessageEmbedServiceBean implements MessageEmbedService { private Boolean shouldMentionReferencedAuthor(Message message) { if(message.getReferencedMessage() != null) { - return message.getMentions().getMentions(Message.MentionType.USER).contains(message.getReferencedMessage().getAuthor()); + return message.getMentions().getMentions(Message.MentionType.USER) + .stream() + .anyMatch(user -> message.getReferencedMessage().getAuthor().getIdLong() == user.getIdLong()); } return false; } diff --git a/abstracto-application/abstracto-modules/starboard/starboard-impl/src/main/java/dev/sheldan/abstracto/starboard/listener/StarboardListener.java b/abstracto-application/abstracto-modules/starboard/starboard-impl/src/main/java/dev/sheldan/abstracto/starboard/listener/StarboardListener.java index 5f3747e89..2e333f1e7 100644 --- a/abstracto-application/abstracto-modules/starboard/starboard-impl/src/main/java/dev/sheldan/abstracto/starboard/listener/StarboardListener.java +++ b/abstracto-application/abstracto-modules/starboard/starboard-impl/src/main/java/dev/sheldan/abstracto/starboard/listener/StarboardListener.java @@ -138,8 +138,12 @@ public abstract class StarboardListener { protected void updateStarboardPost(CachedMessage message, AUserInAServer userReacting, boolean adding, StarboardPost starboardPost, List userExceptAuthor) { starboardPost.setIgnored(false); - // TODO handle futures correctly - starboardService.updateStarboardPost(starboardPost, message, userExceptAuthor); + starboardService.updateStarboardPost(starboardPost, message, userExceptAuthor) + .thenAccept(unused -> log.info("Updated starboard post.")) + .exceptionally(throwable -> { + log.error("Failed to update starboard post.", throwable); + return null; + }); if(adding) { log.debug("Adding reactor {} from message {}", userReacting.getUserReference().getId(), message.getMessageId()); starboardPostReactorManagementService.addReactor(starboardPost, userReacting); diff --git a/abstracto-application/core/core-impl/pom.xml b/abstracto-application/core/core-impl/pom.xml index bb0893a34..5452403e8 100644 --- a/abstracto-application/core/core-impl/pom.xml +++ b/abstracto-application/core/core-impl/pom.xml @@ -84,7 +84,7 @@ - net.dv8tion + io.github.freya022 JDA @@ -131,6 +131,11 @@ gson + + org.danilopianini + gson-extras + + com.google.guava guava diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/config/CoreConfig.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/config/CoreConfig.java index e7c6f8998..690641793 100644 --- a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/config/CoreConfig.java +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/config/CoreConfig.java @@ -3,9 +3,25 @@ package dev.sheldan.abstracto.core.config; import ch.qos.logback.core.net.ssl.SecureRandomFactoryBean; import com.google.gson.Gson; import com.google.gson.GsonBuilder; +import com.google.gson.typeadapters.RuntimeTypeAdapterFactory; import dev.sheldan.abstracto.core.logging.OkHttpLogger; import dev.sheldan.abstracto.core.metric.OkHttpMetrics; import dev.sheldan.abstracto.core.service.BotService; +import dev.sheldan.abstracto.core.templating.model.messagecomponents.ActionRowButtonConfig; +import dev.sheldan.abstracto.core.templating.model.messagecomponents.ActionRowItemConfig; +import dev.sheldan.abstracto.core.templating.model.messagecomponents.ComponentConfig; +import dev.sheldan.abstracto.core.templating.model.messagecomponents.SectionAccessoryConfig; +import dev.sheldan.abstracto.core.templating.model.messagecomponents.SectionButton; +import dev.sheldan.abstracto.core.templating.model.messagecomponents.SectionComponentConfig; +import dev.sheldan.abstracto.core.templating.model.messagecomponents.SectionTextDisplay; +import dev.sheldan.abstracto.core.templating.model.messagecomponents.SectionThumbnail; +import dev.sheldan.abstracto.core.templating.model.messagecomponents.TopLevelActionRowConfig; +import dev.sheldan.abstracto.core.templating.model.messagecomponents.TopLevelContainerConfig; +import dev.sheldan.abstracto.core.templating.model.messagecomponents.TopLevelFileConfig; +import dev.sheldan.abstracto.core.templating.model.messagecomponents.TopLevelMediaGalleryConfig; +import dev.sheldan.abstracto.core.templating.model.messagecomponents.TopLevelSectionConfig; +import dev.sheldan.abstracto.core.templating.model.messagecomponents.TopLevelSeperatorConfig; +import dev.sheldan.abstracto.core.templating.model.messagecomponents.TopLevelTextDisplay; import okhttp3.OkHttpClient; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; @@ -41,6 +57,27 @@ public class CoreConfig { GsonBuilder builder = new GsonBuilder() .registerTypeAdapter(OffsetDateTime.class, new OffsetDateTimeAdapter()) .registerTypeAdapter(Instant.class, new InstantAdapter()) + .registerTypeAdapterFactory(RuntimeTypeAdapterFactory + .of(ComponentConfig.class, "type") + .registerSubtype(TopLevelActionRowConfig.class, "actionRow") + .registerSubtype(TopLevelSectionConfig.class, "section") + .registerSubtype(TopLevelFileConfig.class, "fileDisplay") + .registerSubtype(TopLevelMediaGalleryConfig.class, "mediaGallery") + .registerSubtype(TopLevelSeperatorConfig.class, "separator") + .registerSubtype(TopLevelContainerConfig.class, "container") + .registerSubtype(TopLevelTextDisplay.class, "textDisplay")) + .registerTypeAdapterFactory(RuntimeTypeAdapterFactory + .of(ActionRowItemConfig.class, "type") + .registerSubtype(ActionRowButtonConfig.class, "button")) + .registerTypeAdapterFactory(RuntimeTypeAdapterFactory + .of(SectionAccessoryConfig.class, "type") + .registerSubtype(SectionButton.class, "button") + .registerSubtype(SectionThumbnail.class, "thumbnail") + ) + .registerTypeAdapterFactory(RuntimeTypeAdapterFactory + .of(SectionComponentConfig.class, "type") + .registerSubtype(SectionTextDisplay.class, "textDisplay") + ) .setPrettyPrinting(); if(customJsonDeSerializers != null) { customJsonDeSerializers.forEach(customJsonSerializer -> diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/ComponentServiceBean.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/ComponentServiceBean.java index 2b0857766..214f72b5e 100644 --- a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/ComponentServiceBean.java +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/ComponentServiceBean.java @@ -6,10 +6,10 @@ import dev.sheldan.abstracto.core.service.MessageService; import net.dv8tion.jda.api.entities.Message; import net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel; import net.dv8tion.jda.api.entities.emoji.Emoji; -import net.dv8tion.jda.api.interactions.components.ActionComponent; -import net.dv8tion.jda.api.interactions.components.ActionRow; -import net.dv8tion.jda.api.interactions.components.buttons.Button; -import net.dv8tion.jda.api.interactions.components.buttons.ButtonStyle; +import net.dv8tion.jda.api.components.ActionComponent; +import net.dv8tion.jda.api.components.actionrow.ActionRow; +import net.dv8tion.jda.api.components.buttons.Button; +import net.dv8tion.jda.api.components.buttons.ButtonStyle; import org.apache.commons.collections4.ListUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -50,7 +50,7 @@ public class ComponentServiceBean implements ComponentService { } else { ActionRow lastRow = message.getActionRows().get(message.getActionRows().size() - 1); if(lastRow.getComponents().size() < MAX_BUTTONS_PER_ROW) { - lastRow.getComponents().add(button); + lastRow.getButtons().add(button); actionRows = message.getActionRows(); } else { List currentActionRows = new ArrayList<>(message.getActionRows()); @@ -86,7 +86,7 @@ public class ComponentServiceBean implements ComponentService { public CompletableFuture removeComponentWithId(Message message, String componentId, Boolean rearrange) { List actionRows = new ArrayList<>(); if(Boolean.TRUE.equals(rearrange)) { - List components = new ArrayList<>(); + List components = new ArrayList<>(); message.getActionRows().forEach(row -> row .getComponents() @@ -112,8 +112,8 @@ public class ComponentServiceBean implements ComponentService { } @Override - public List splitIntoActionRowsMax(List allComponents) { - List> actionRows = ListUtils.partition(allComponents, MAX_BUTTONS_PER_ROW); + public List splitIntoActionRowsMax(List allComponents) { + List> actionRows = ListUtils.partition(allComponents, MAX_BUTTONS_PER_ROW); return actionRows.stream().map(ActionRow::of).collect(Collectors.toList()); } diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/InteractionServiceBean.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/InteractionServiceBean.java index f829be291..c58d7e2b4 100644 --- a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/InteractionServiceBean.java +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/InteractionServiceBean.java @@ -17,8 +17,8 @@ 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.callbacks.IReplyCallback; -import net.dv8tion.jda.api.interactions.components.ActionComponent; -import net.dv8tion.jda.api.interactions.components.ActionRow; +import net.dv8tion.jda.api.components.ActionComponent; +import net.dv8tion.jda.api.components.actionrow.ActionRow; import net.dv8tion.jda.api.requests.restaction.WebhookMessageCreateAction; import net.dv8tion.jda.api.requests.restaction.WebhookMessageEditAction; import net.dv8tion.jda.api.requests.restaction.interactions.ReplyCallbackAction; @@ -260,56 +260,57 @@ public class InteractionServiceBean implements InteractionService { public CompletableFuture replyMessageToSend(MessageToSend messageToSend, IReplyCallback callback) { ReplyCallbackAction action = null; - if(messageToSend.getMessages() != null && !messageToSend.getMessages().isEmpty()) { - metricService.incrementCounter(MESSAGE_SEND_METRIC); - action = callback.reply(messageToSend.getMessages().get(0)); - } - - if(messageToSend.getEmbeds() != null && !messageToSend.getEmbeds().isEmpty()) { - if(action != null) { - action = action.addEmbeds(messageToSend.getEmbeds().subList(0, Math.min(10, messageToSend.getEmbeds().size()))); - } else { - action = callback.replyEmbeds(messageToSend.getEmbeds()); + if(messageToSend.getUseComponentsV2()) { + action = callback.replyComponents(messageToSend.getComponents()).useComponentsV2(); + } else { + if(messageToSend.getMessages() != null && !messageToSend.getMessages().isEmpty()) { + metricService.incrementCounter(MESSAGE_SEND_METRIC); + action = callback.reply(messageToSend.getMessages().get(0)); } - } - - if(messageToSend.hasFilesToSend()) { - List attachedFiles = messageToSend + if(messageToSend.getEmbeds() != null && !messageToSend.getEmbeds().isEmpty()) { + if(action != null) { + action = action.addEmbeds(messageToSend.getEmbeds().subList(0, Math.min(10, messageToSend.getEmbeds().size()))); + } else { + action = callback.replyEmbeds(messageToSend.getEmbeds()); + } + } + if(messageToSend.hasFilesToSend()) { + List attachedFiles = messageToSend .getAttachedFiles() .stream() .map(AttachedFile::convertToFileUpload) .collect(Collectors.toList()); - if(action != null) { - action.setFiles(attachedFiles); - } else { - metricService.incrementCounter(MESSAGE_SEND_METRIC); - action = callback.replyFiles(attachedFiles); - } - } - - // this should be last, because we are "faking" a message, by inserting a ., in case there has not been a reply yet - // we could also throw an exception, but we are allowing this to go through - List actionRows = messageToSend.getActionRows(); - if(actionRows != null && !actionRows.isEmpty()) { - if(action == null) { - action = callback.reply("."); - } - action = action.setComponents(actionRows); - AServer server; - if(ContextUtils.isGuildKnown(callback)) { - server = serverManagementService.loadServer(callback.getGuild().getIdLong()); - } else { - server = null; - } - actionRows.forEach(components -> components.forEach(component -> { - if(component instanceof ActionComponent) { - String id = ((ActionComponent)component).getId(); - MessageToSend.ComponentConfig payload = messageToSend.getComponentPayloads().get(id); - if(payload != null && payload.getPersistCallback()) { - componentPayloadManagementService.createPayload(id, payload.getPayload(), payload.getPayloadType(), payload.getComponentOrigin(), server, payload.getComponentType()); - } + if(action != null) { + action.setFiles(attachedFiles); + } else { + metricService.incrementCounter(MESSAGE_SEND_METRIC); + action = callback.replyFiles(attachedFiles); } - })); + } + // this should be last, because we are "faking" a message, by inserting a ., in case there has not been a reply yet + // we could also throw an exception, but we are allowing this to go through + List actionRows = messageToSend.getActionRows(); + if(actionRows != null && !actionRows.isEmpty()) { + if(action == null) { + action = callback.reply("."); + } + action = action.setComponents(actionRows); + AServer server; + if(ContextUtils.isGuildKnown(callback)) { + server = serverManagementService.loadServer(callback.getGuild().getIdLong()); + } else { + server = null; + } + actionRows.forEach(components -> components.forEach(component -> { + if(component instanceof ActionComponent) { + String id = ((ActionComponent)component).getId(); + MessageToSend.ComponentConfig payload = messageToSend.getComponentPayloads().get(id); + if(payload != null && payload.getPersistCallback()) { + componentPayloadManagementService.createPayload(id, payload.getPayload(), payload.getPayloadType(), payload.getComponentOrigin(), server, payload.getComponentType()); + } + } + })); + } } if(messageToSend.getEphemeral()) { @@ -328,7 +329,7 @@ public class InteractionServiceBean implements InteractionService { if(ContextUtils.isGuildKnown(callback)) { Set allowedMentions = allowedMentionService.getAllowedMentionsFor(callback.getMessageChannel(), messageToSend); if (action != null) { - action.setAllowedMentions(allowedMentions); + action = action.setAllowedMentions(allowedMentions); } } diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/modal/ModalServiceBean.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/modal/ModalServiceBean.java index addaaf42d..0c6a47838 100644 --- a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/modal/ModalServiceBean.java +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/modal/ModalServiceBean.java @@ -8,9 +8,9 @@ import dev.sheldan.abstracto.core.interaction.modal.config.TextInputComponentSty import dev.sheldan.abstracto.core.templating.service.TemplateService; import net.dv8tion.jda.api.events.interaction.command.GenericCommandInteractionEvent; import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent; -import net.dv8tion.jda.api.interactions.components.ActionRow; +import net.dv8tion.jda.api.components.actionrow.ActionRow; import net.dv8tion.jda.api.interactions.components.ItemComponent; -import net.dv8tion.jda.api.interactions.components.text.TextInput; +import net.dv8tion.jda.api.components.textinput.TextInput; import net.dv8tion.jda.api.interactions.modals.Modal; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/modal/config/TextInputComponentStyle.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/modal/config/TextInputComponentStyle.java index 4af7eb43a..fc087b543 100644 --- a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/modal/config/TextInputComponentStyle.java +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interaction/modal/config/TextInputComponentStyle.java @@ -1,7 +1,7 @@ package dev.sheldan.abstracto.core.interaction.modal.config; import com.google.gson.annotations.SerializedName; -import net.dv8tion.jda.api.interactions.components.text.TextInputStyle; +import net.dv8tion.jda.api.components.textinput.TextInputStyle; public enum TextInputComponentStyle { @SerializedName("short") diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/AllowedMentionServiceBean.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/AllowedMentionServiceBean.java index b9d2b7721..b09df4ecf 100644 --- a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/AllowedMentionServiceBean.java +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/AllowedMentionServiceBean.java @@ -125,14 +125,26 @@ public class AllowedMentionServiceBean implements AllowedMentionService { } if(messageToSend != null && messageToSend.getMessageConfig() != null) { MessageConfig messageConfig = messageToSend.getMessageConfig(); - if(messageConfig.isAllowsEveryoneMention()) { - allowedMentions.add(Message.MentionType.EVERYONE); + if(messageConfig.getAllowsEveryoneMention() != null) { + if(messageConfig.getAllowsEveryoneMention()) { + allowedMentions.add(Message.MentionType.EVERYONE); + } else { + allowedMentions.remove(Message.MentionType.EVERYONE); + } } - if(messageConfig.isAllowsUserMention()) { - allowedMentions.add(Message.MentionType.USER); + if(messageConfig.getAllowsUserMention() != null) { + if(messageConfig.getAllowsUserMention()) { + allowedMentions.add(Message.MentionType.USER); + } else { + allowedMentions.remove(Message.MentionType.USER); + } } - if(messageConfig.isAllowsRoleMention()) { - allowedMentions.add(Message.MentionType.ROLE); + if(messageConfig.getAllowsRoleMention() != null) { + if(messageConfig.getAllowsRoleMention()) { + allowedMentions.add(Message.MentionType.ROLE); + } else { + allowedMentions.remove(Message.MentionType.ROLE); + } } } return allowedMentions; diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/CacheEntityServiceBean.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/CacheEntityServiceBean.java index bc179ba55..c423a20dd 100644 --- a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/CacheEntityServiceBean.java +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/CacheEntityServiceBean.java @@ -74,6 +74,7 @@ public class CacheEntityServiceBean implements CacheEntityService { .fileName(attachment.getFileName()) .height(attachment.getHeight()) .spoiler(attachment.isSpoiler()) + .contentType(attachment.getContentType()) .proxyUrl(attachment.getProxyUrl()) .size(attachment.getSize()) .url(attachment.getUrl()) diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/ChannelServiceBean.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/ChannelServiceBean.java index a21d099bd..9d16f7e29 100644 --- a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/ChannelServiceBean.java +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/ChannelServiceBean.java @@ -17,6 +17,7 @@ import dev.sheldan.abstracto.core.utils.FileService; import lombok.extern.slf4j.Slf4j; import net.dv8tion.jda.api.EmbedBuilder; import net.dv8tion.jda.api.Permission; +import net.dv8tion.jda.api.components.actionrow.ActionRowChildComponent; import net.dv8tion.jda.api.entities.*; import net.dv8tion.jda.api.entities.channel.attribute.IThreadContainer; import net.dv8tion.jda.api.entities.channel.concrete.Category; @@ -25,9 +26,8 @@ import net.dv8tion.jda.api.entities.channel.concrete.ThreadChannel; import net.dv8tion.jda.api.entities.channel.middleman.GuildChannel; import net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; -import net.dv8tion.jda.api.interactions.components.ActionComponent; -import net.dv8tion.jda.api.interactions.components.ActionRow; -import net.dv8tion.jda.api.interactions.components.ItemComponent; +import net.dv8tion.jda.api.components.ActionComponent; +import net.dv8tion.jda.api.components.actionrow.ActionRow; import net.dv8tion.jda.api.requests.restaction.MessageCreateAction; import net.dv8tion.jda.api.requests.restaction.MessageEditAction; import net.dv8tion.jda.api.utils.FileUpload; @@ -221,91 +221,96 @@ public class ChannelServiceBean implements ChannelService { } List> futures = new ArrayList<>(); List allMessageActions = new ArrayList<>(); - Iterator embedIterator = messageToSend.getEmbeds().iterator(); - for (int i = 0; i < messageToSend.getMessages().size(); i++) { + if(messageToSend.getUseComponentsV2() && messageToSend.getComponents() != null && !messageToSend.getComponents().isEmpty()) { metricService.incrementCounter(MESSAGE_SEND_METRIC); - String text = messageToSend.getMessages().get(i); + allMessageActions.add(textChannel.sendMessageComponents(messageToSend.getComponents()).useComponentsV2()); + } else { + Iterator embedIterator = messageToSend.getEmbeds().iterator(); + for (int i = 0; i < messageToSend.getMessages().size(); i++) { + metricService.incrementCounter(MESSAGE_SEND_METRIC); + String text = messageToSend.getMessages().get(i); + List messageEmbeds = new ArrayList<>(); + while(embedIterator.hasNext()) { + MessageEmbed embedToAdd = embedIterator.next(); + if((currentEmbedLength(messageEmbeds) + embedToAdd.getLength()) >= MessageEmbed.EMBED_MAX_LENGTH_BOT) { + break; + } + messageEmbeds.add(embedToAdd); + embedIterator.remove(); + } + MessageCreateAction messageAction = textChannel.sendMessage(text); + if(!messageEmbeds.isEmpty()) { + messageAction.setEmbeds(messageEmbeds); + } + allMessageActions.add(messageAction); + } List messageEmbeds = new ArrayList<>(); + // reset the iterator, because if the if in the above while iterator loop applied, we already took it out from the iterator + // but we didnt add it yet, so it would be lost + embedIterator = messageToSend.getEmbeds().iterator(); while(embedIterator.hasNext()) { MessageEmbed embedToAdd = embedIterator.next(); - if((currentEmbedLength(messageEmbeds) + embedToAdd.getLength()) >= MessageEmbed.EMBED_MAX_LENGTH_BOT) { - break; + if((currentEmbedLength(messageEmbeds) + embedToAdd.getLength()) >= MessageEmbed.EMBED_MAX_LENGTH_BOT && !messageEmbeds.isEmpty()) { + allMessageActions.add(textChannel.sendMessageEmbeds(messageEmbeds)); + metricService.incrementCounter(MESSAGE_SEND_METRIC); + messageEmbeds = new ArrayList<>(); } messageEmbeds.add(embedToAdd); - embedIterator.remove(); } - MessageCreateAction messageAction = textChannel.sendMessage(text); + if(!messageEmbeds.isEmpty()) { - messageAction.setEmbeds(messageEmbeds); - } - allMessageActions.add(messageAction); - } - List messageEmbeds = new ArrayList<>(); - // reset the iterator, because if the if in the above while iterator loop applied, we already took it out from the iterator - // but we didnt add it yet, so it would be lost - embedIterator = messageToSend.getEmbeds().iterator(); - while(embedIterator.hasNext()) { - MessageEmbed embedToAdd = embedIterator.next(); - if((currentEmbedLength(messageEmbeds) + embedToAdd.getLength()) >= MessageEmbed.EMBED_MAX_LENGTH_BOT && !messageEmbeds.isEmpty()) { allMessageActions.add(textChannel.sendMessageEmbeds(messageEmbeds)); metricService.incrementCounter(MESSAGE_SEND_METRIC); - messageEmbeds = new ArrayList<>(); } - messageEmbeds.add(embedToAdd); - } - if(!messageEmbeds.isEmpty()) { - allMessageActions.add(textChannel.sendMessageEmbeds(messageEmbeds)); - metricService.incrementCounter(MESSAGE_SEND_METRIC); - } - - List actionRows = messageToSend.getActionRows(); - if(!actionRows.isEmpty()) { - List> groupedActionRows = ListUtils.partition(actionRows, ComponentService.MAX_BUTTONS_PER_ROW); - for (int i = 0; i < allMessageActions.size(); i++) { - allMessageActions.set(i, allMessageActions.get(i).setComponents(groupedActionRows.get(i))); - } - for (int i = allMessageActions.size(); i < groupedActionRows.size(); i++) { - // TODO maybe possible nicer - allMessageActions.add(textChannel.sendMessage(".").setComponents(groupedActionRows.get(i))); - } - AServer server = null; - if(textChannel instanceof GuildChannel) { - GuildChannel channel = (GuildChannel) textChannel; - server = serverManagementService.loadServer(channel.getGuild()); - } - for (ActionRow components : actionRows) { - for (ItemComponent component : components) { - if (component instanceof ActionComponent) { - String id = ((ActionComponent) component).getId(); - MessageToSend.ComponentConfig payload = messageToSend.getComponentPayloads().get(id); - if (payload != null && payload.getPersistCallback()) { - componentPayloadManagementService.createPayload(id, payload.getPayload(), payload.getPayloadType(), payload.getComponentOrigin(), server, payload.getComponentType()); + List actionRows = messageToSend.getActionRows(); + if(!actionRows.isEmpty()) { + List> groupedActionRows = ListUtils.partition(actionRows, ComponentService.MAX_BUTTONS_PER_ROW); + for (int i = 0; i < allMessageActions.size(); i++) { + allMessageActions.set(i, allMessageActions.get(i).setComponents(groupedActionRows.get(i))); + } + for (int i = allMessageActions.size(); i < groupedActionRows.size(); i++) { + // TODO maybe possible nicer + allMessageActions.add(textChannel.sendMessage(".").setComponents(groupedActionRows.get(i))); + } + AServer server = null; + if(textChannel instanceof GuildChannel) { + GuildChannel channel = (GuildChannel) textChannel; + server = serverManagementService.loadServer(channel.getGuild()); + } + for (ActionRow row : actionRows) { + for (ActionRowChildComponent component : row) { + if (component instanceof ActionComponent) { + String id = ((ActionComponent) component).getId(); + MessageToSend.ComponentConfig payload = messageToSend.getComponentPayloads().get(id); + if (payload != null && payload.getPersistCallback()) { + componentPayloadManagementService.createPayload(id, payload.getPayload(), payload.getPayloadType(), payload.getComponentOrigin(), server, payload.getComponentType()); + } } } } } - } - if(messageToSend.hasFilesToSend()) { - List attachedFiles = messageToSend + if(messageToSend.hasFilesToSend()) { + List attachedFiles = messageToSend .getAttachedFiles() .stream() .map(AttachedFile::convertToFileUpload) .collect(Collectors.toList()); - if(!allMessageActions.isEmpty()) { - // in case there has not been a message, we need to increment it - allMessageActions.set(0, allMessageActions.get(0).addFiles(attachedFiles)); - } else { - metricService.incrementCounter(MESSAGE_SEND_METRIC); - allMessageActions.add(textChannel.sendFiles(attachedFiles)); + if(!allMessageActions.isEmpty()) { + // in case there has not been a message, we need to increment it + allMessageActions.set(0, allMessageActions.get(0).addFiles(attachedFiles)); + } else { + metricService.incrementCounter(MESSAGE_SEND_METRIC); + allMessageActions.add(textChannel.sendFiles(attachedFiles)); + } } } Set allowedMentions = allowedMentionService.getAllowedMentionsFor(textChannel, messageToSend); allMessageActions.forEach(messageAction -> { if(messageToSend.getReferencedMessageId() != null) { messageAction = messageAction.setMessageReference(messageToSend.getReferencedMessageId()); - if(messageToSend.getMessageConfig() != null && !messageToSend.getMessageConfig().isMentionsReferencedMessage()) { + if(messageToSend.getMessageConfig() != null && !messageToSend.getMessageConfig().getMentionsReferencedMessage()) { messageAction = messageAction.mentionRepliedUser(false); } } @@ -337,6 +342,15 @@ public class ChannelServiceBean implements ChannelService { @Override public CompletableFuture editMessageInAChannelFuture(MessageToSend messageToSend, MessageChannel channel, Long messageId) { MessageEditAction messageAction; + if(messageToSend.getUseComponentsV2()) { + Set allowedMentions = allowedMentionService.getAllowedMentionsFor(channel, messageToSend); + return channel + .editMessageComponentsById(messageId, messageToSend.getComponents()) + .useComponentsV2() + .setReplace(true) + .setAllowedMentions(allowedMentions) + .submit(); + } if(!messageToSend.getMessages().isEmpty() && !StringUtils.isBlank(messageToSend.getMessages().get(0))) { log.debug("Editing message {} with new text content.", messageId); messageAction = channel.editMessageById(messageId, messageToSend.getMessages().get(0)); diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/MessageServiceBean.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/MessageServiceBean.java index 2547f4223..ad26789e6 100644 --- a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/MessageServiceBean.java +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/MessageServiceBean.java @@ -16,7 +16,7 @@ import net.dv8tion.jda.api.entities.*; import net.dv8tion.jda.api.entities.channel.concrete.PrivateChannel; import net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; -import net.dv8tion.jda.api.interactions.components.ActionRow; +import net.dv8tion.jda.api.components.actionrow.ActionRow; import net.dv8tion.jda.api.requests.restaction.AuditableRestAction; import net.dv8tion.jda.api.requests.restaction.MessageEditAction; import org.springframework.beans.factory.annotation.Autowired; diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/PostTargetServiceBean.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/PostTargetServiceBean.java index 2cdae19da..987da8c7d 100644 --- a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/PostTargetServiceBean.java +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/PostTargetServiceBean.java @@ -2,8 +2,6 @@ package dev.sheldan.abstracto.core.service; import dev.sheldan.abstracto.core.config.FeatureConfig; import dev.sheldan.abstracto.core.config.PostTargetEnum; -import dev.sheldan.abstracto.core.exception.ChannelNotInGuildException; -import dev.sheldan.abstracto.core.exception.GuildNotFoundException; import dev.sheldan.abstracto.core.exception.PostTargetNotUsableException; import dev.sheldan.abstracto.core.exception.PostTargetNotValidException; import dev.sheldan.abstracto.core.models.database.AServer; @@ -11,6 +9,7 @@ import dev.sheldan.abstracto.core.models.database.PostTarget; import dev.sheldan.abstracto.core.service.management.DefaultPostTargetManagementService; import dev.sheldan.abstracto.core.service.management.PostTargetManagement; import dev.sheldan.abstracto.core.templating.model.MessageToSend; +import dev.sheldan.abstracto.core.utils.FutureUtils; import lombok.extern.slf4j.Slf4j; import net.dv8tion.jda.api.entities.*; import net.dv8tion.jda.api.entities.channel.middleman.GuildChannel; @@ -181,40 +180,65 @@ public class PostTargetServiceBean implements PostTargetService { return getMessageChannelForPostTarget(target).map(messageChannel -> { CompletableFuture messageEditFuture = new CompletableFuture<>(); futures.add(messageEditFuture); - if (StringUtils.isBlank(messageToSend.getMessages().get(0).trim())) { + if(messageToSend.getUseComponentsV2()) { channelService.retrieveMessageInChannel(messageChannel, messageId).thenAccept(message -> { log.debug("Editing existing message {} when upserting message embeds in channel {} in server {}.", - messageId, messageChannel.getIdLong(), messageChannel.getGuild().getId()); - messageService.editMessage(message, messageToSend.getEmbeds().get(0)) - .queue(messageEditFuture::complete, messageEditFuture::completeExceptionally); + messageId, messageChannel.getIdLong(), messageChannel.getGuild().getId()); + channelService.editMessageInAChannelFuture(messageToSend, messageChannel, messageId) + .thenAccept(messageEditFuture::complete) + .exceptionally(throwable -> { + messageEditFuture.completeExceptionally(throwable); + return null; + }); }).exceptionally(throwable -> { log.debug("Creating new message when upserting message embeds for message {} in channel {} in server {}.", - messageId, messageChannel.getIdLong(), messageChannel.getGuild().getId()); - sendEmbedInPostTarget(messageToSend, target).get(0) - .thenAccept(messageEditFuture::complete).exceptionally(innerThrowable -> { - log.error("Failed to send message to create a message.", innerThrowable); - messageEditFuture.completeExceptionally(innerThrowable); - return null; - }); + messageId, messageChannel.getIdLong(), messageChannel.getGuild().getId()); + List> messageFutures = channelService.sendMessageToSendToChannel(messageToSend, messageChannel); + FutureUtils.toSingleFutureGeneric(messageFutures) + .thenAccept(unused -> messageEditFuture.complete(futures.get(0).join())) + .exceptionally(innerThrowable -> { + log.error("Failed to send message to create a message.", innerThrowable); + messageEditFuture.completeExceptionally(innerThrowable); + return null; + }); return null; }); } else { - channelService.retrieveMessageInChannel(messageChannel, messageId).thenAccept(message -> { - log.debug("Editing existing message {} when upserting message in channel {} in server {}.", + if (StringUtils.isBlank(messageToSend.getMessages().get(0).trim())) { + channelService.retrieveMessageInChannel(messageChannel, messageId).thenAccept(message -> { + log.debug("Editing existing message {} when upserting message embeds in channel {} in server {}.", messageId, messageChannel.getIdLong(), messageChannel.getGuild().getId()); - messageService.editMessage(message, messageToSend.getMessages().get(0), messageToSend.getEmbeds().get(0)) + messageService.editMessage(message, messageToSend.getEmbeds().get(0)) .queue(messageEditFuture::complete, messageEditFuture::completeExceptionally); - }).exceptionally(throwable -> { - log.debug("Creating new message when trying to upsert a message {} in channel {} in server {}.", + }).exceptionally(throwable -> { + log.debug("Creating new message when upserting message embeds for message {} in channel {} in server {}.", messageId, messageChannel.getIdLong(), messageChannel.getGuild().getId()); - sendEmbedInPostTarget(messageToSend, target).get(0) + sendEmbedInPostTarget(messageToSend, target).get(0) .thenAccept(messageEditFuture::complete).exceptionally(innerThrowable -> { log.error("Failed to send message to create a message.", innerThrowable); messageEditFuture.completeExceptionally(innerThrowable); return null; }); - return null; - }); + return null; + }); + } else { + channelService.retrieveMessageInChannel(messageChannel, messageId).thenAccept(message -> { + log.debug("Editing existing message {} when upserting message in channel {} in server {}.", + messageId, messageChannel.getIdLong(), messageChannel.getGuild().getId()); + messageService.editMessage(message, messageToSend.getMessages().get(0), messageToSend.getEmbeds().get(0)) + .queue(messageEditFuture::complete, messageEditFuture::completeExceptionally); + }).exceptionally(throwable -> { + log.debug("Creating new message when trying to upsert a message {} in channel {} in server {}.", + messageId, messageChannel.getIdLong(), messageChannel.getGuild().getId()); + sendEmbedInPostTarget(messageToSend, target).get(0) + .thenAccept(messageEditFuture::complete).exceptionally(innerThrowable -> { + log.error("Failed to send message to create a message.", innerThrowable); + messageEditFuture.completeExceptionally(innerThrowable); + return null; + }); + return null; + }); + } } return futures; }).orElse(Arrays.asList(CompletableFuture.completedFuture(null))); diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/MessageConfiguration.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/MessageConfiguration.java index 68c4fe6b6..ecfb163a2 100644 --- a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/MessageConfiguration.java +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/MessageConfiguration.java @@ -1,5 +1,7 @@ package dev.sheldan.abstracto.core.templating.model; +import dev.sheldan.abstracto.core.templating.model.messagecomponents.ButtonConfig; +import dev.sheldan.abstracto.core.templating.model.messagecomponents.ComponentConfig; import lombok.Builder; import lombok.Getter; import lombok.Setter; @@ -15,6 +17,7 @@ import java.util.List; @Builder public class MessageConfiguration { private List embeds; + private List components; private Long referencedMessageId; /** * The message which is posted along the {@link net.dv8tion.jda.api.entities.MessageEmbed} as a normal message. diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/MetaMessageConfiguration.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/MetaMessageConfiguration.java index 5d928bb5b..b13ee34f2 100644 --- a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/MetaMessageConfiguration.java +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/MetaMessageConfiguration.java @@ -17,6 +17,8 @@ public class MetaMessageConfiguration { @Builder.Default private boolean allowsUserMention = true; @Builder.Default + private boolean useComponentsV2 = false; + @Builder.Default private boolean mentionsReferencedMessage = true; private Integer messageLimit; diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/ActionRowButtonConfig.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/ActionRowButtonConfig.java new file mode 100644 index 000000000..2c266e058 --- /dev/null +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/ActionRowButtonConfig.java @@ -0,0 +1,14 @@ +package dev.sheldan.abstracto.core.templating.model.messagecomponents; + +import lombok.Getter; +import lombok.experimental.SuperBuilder; + +@Getter +@SuperBuilder +public class ActionRowButtonConfig extends ButtonConfig implements ActionRowItemConfig{ + + @Override + public ActionRowItemType getType() { + return ActionRowItemType.BUTTON; + } +} diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/ActionRowConfig.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/ActionRowConfig.java new file mode 100644 index 000000000..5484b8c2e --- /dev/null +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/ActionRowConfig.java @@ -0,0 +1,12 @@ +package dev.sheldan.abstracto.core.templating.model.messagecomponents; + +import java.util.List; +import lombok.Getter; +import lombok.experimental.SuperBuilder; + +@SuperBuilder +@Getter +public class ActionRowConfig { + protected List actionRowItems; + +} diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/ActionRowItemConfig.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/ActionRowItemConfig.java new file mode 100644 index 000000000..c06464ce7 --- /dev/null +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/ActionRowItemConfig.java @@ -0,0 +1,18 @@ +package dev.sheldan.abstracto.core.templating.model.messagecomponents; + +import com.google.gson.annotations.SerializedName; + +public interface ActionRowItemConfig { + ActionRowItemType getType(); + + enum ActionRowItemType { + @SerializedName("button") + BUTTON, + @SerializedName("stringSelectMenu") + STRING_SELECT_MENU, + @SerializedName("entitySelectMenu") + ENTITY_SELECT_MENU + } + +} + diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/ButtonConfig.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/ButtonConfig.java similarity index 76% rename from abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/ButtonConfig.java rename to abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/ButtonConfig.java index f242b0f11..5f1e18cdd 100644 --- a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/ButtonConfig.java +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/ButtonConfig.java @@ -1,12 +1,13 @@ -package dev.sheldan.abstracto.core.templating.model; +package dev.sheldan.abstracto.core.templating.model.messagecomponents; + -import lombok.Builder; import lombok.Getter; import lombok.Setter; +import lombok.experimental.SuperBuilder; @Getter @Setter -@Builder +@SuperBuilder public class ButtonConfig { private String label; private String id; diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/ButtonMetaConfig.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/ButtonMetaConfig.java similarity index 79% rename from abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/ButtonMetaConfig.java rename to abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/ButtonMetaConfig.java index 60e7cf329..250a021f2 100644 --- a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/ButtonMetaConfig.java +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/ButtonMetaConfig.java @@ -1,4 +1,4 @@ -package dev.sheldan.abstracto.core.templating.model; +package dev.sheldan.abstracto.core.templating.model.messagecomponents; import lombok.Builder; import lombok.Getter; diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/ButtonStyleConfig.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/ButtonStyleConfig.java similarity index 84% rename from abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/ButtonStyleConfig.java rename to abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/ButtonStyleConfig.java index 8df1ce060..2ba75a9f1 100644 --- a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/ButtonStyleConfig.java +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/ButtonStyleConfig.java @@ -1,7 +1,7 @@ -package dev.sheldan.abstracto.core.templating.model; +package dev.sheldan.abstracto.core.templating.model.messagecomponents; import com.google.gson.annotations.SerializedName; -import net.dv8tion.jda.api.interactions.components.buttons.ButtonStyle; +import net.dv8tion.jda.api.components.buttons.ButtonStyle; public enum ButtonStyleConfig { @SerializedName("primary") diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/ComponentConfig.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/ComponentConfig.java new file mode 100644 index 000000000..cabaf4000 --- /dev/null +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/ComponentConfig.java @@ -0,0 +1,27 @@ +package dev.sheldan.abstracto.core.templating.model.messagecomponents; + + +import com.google.gson.annotations.SerializedName; + +public interface ComponentConfig { + ComponentTypeConfig getType(); + + enum ComponentTypeConfig { + @SerializedName("actionRow") + ACTION_ROW, + @SerializedName("section") + SECTION, + @SerializedName("textDisplay") + TEXT_DISPLAY, + @SerializedName("mediaGallery") + MEDIA_GALLERY, + @SerializedName("separator") + SEPARATOR, + @SerializedName("fileDisplay") + FILE_DISPLAY, + @SerializedName("container") + CONTAINER + } +} + + diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/ContainerConfig.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/ContainerConfig.java new file mode 100644 index 000000000..16ccfcef0 --- /dev/null +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/ContainerConfig.java @@ -0,0 +1,15 @@ +package dev.sheldan.abstracto.core.templating.model.messagecomponents; + +import java.util.List; +import lombok.Getter; +import lombok.experimental.SuperBuilder; + +@SuperBuilder +@Getter +public class ContainerConfig implements ComponentConfig { + private List components; + @Override + public ComponentTypeConfig getType() { + return ComponentTypeConfig.CONTAINER; + } +} diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/FileConfig.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/FileConfig.java new file mode 100644 index 000000000..44893bac6 --- /dev/null +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/FileConfig.java @@ -0,0 +1,25 @@ +package dev.sheldan.abstracto.core.templating.model.messagecomponents; + +import java.io.File; +import lombok.Builder; +import lombok.Getter; +import lombok.experimental.SuperBuilder; +import net.dv8tion.jda.api.utils.FileUpload; + +@SuperBuilder +@Getter +public class FileConfig { + private String fileName; + // only used for plaintext files + private String fileContent; + @Builder.Default + private Boolean spoiler = false; + + public FileUpload convertToFileUpload(File file) { + FileUpload fileUpload = FileUpload.fromData(file, fileName); + if(spoiler != null && spoiler) { + fileUpload = fileUpload.asSpoiler(); + } + return fileUpload; + } +} diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/ImageConfig.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/ImageConfig.java new file mode 100644 index 000000000..971951c2d --- /dev/null +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/ImageConfig.java @@ -0,0 +1,14 @@ +package dev.sheldan.abstracto.core.templating.model.messagecomponents; + +import lombok.Builder; +import lombok.Getter; +import lombok.experimental.SuperBuilder; + +@SuperBuilder +@Getter +public class ImageConfig { + private String url; + private String description; + @Builder.Default + private Boolean spoiler = false; +} diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/MediaGalleryConfig.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/MediaGalleryConfig.java new file mode 100644 index 000000000..015a572c4 --- /dev/null +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/MediaGalleryConfig.java @@ -0,0 +1,15 @@ +package dev.sheldan.abstracto.core.templating.model.messagecomponents; + +import java.util.List; +import lombok.Getter; +import lombok.experimental.SuperBuilder; + +@SuperBuilder +@Getter +public class MediaGalleryConfig implements ComponentConfig { + private List images; + @Override + public ComponentTypeConfig getType() { + return ComponentTypeConfig.MEDIA_GALLERY; + } +} diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/SectionAccessoryConfig.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/SectionAccessoryConfig.java new file mode 100644 index 000000000..7d9ba54a7 --- /dev/null +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/SectionAccessoryConfig.java @@ -0,0 +1,16 @@ +package dev.sheldan.abstracto.core.templating.model.messagecomponents; + +import com.google.gson.annotations.SerializedName; + +public interface SectionAccessoryConfig { + SectionAccessoryType getType(); + + enum SectionAccessoryType { + @SerializedName("button") + BUTTON, + @SerializedName("thumbnail") + THUMBNAIL + } +} + + diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/SectionButton.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/SectionButton.java new file mode 100644 index 000000000..7699a58dc --- /dev/null +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/SectionButton.java @@ -0,0 +1,16 @@ +package dev.sheldan.abstracto.core.templating.model.messagecomponents; + +import lombok.Builder; +import lombok.Getter; +import lombok.experimental.SuperBuilder; + +@SuperBuilder +@Builder +@Getter +public class SectionButton extends ButtonConfig implements SectionAccessoryConfig { + @Override + public SectionAccessoryType getType() { + return SectionAccessoryType.BUTTON; + } +} + diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/SectionComponentConfig.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/SectionComponentConfig.java new file mode 100644 index 000000000..f0d6999ae --- /dev/null +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/SectionComponentConfig.java @@ -0,0 +1,12 @@ +package dev.sheldan.abstracto.core.templating.model.messagecomponents; + +import com.google.gson.annotations.SerializedName; + +public interface SectionComponentConfig { + SectionComponentType getType(); + + enum SectionComponentType { + @SerializedName("textDisplay") + TEXT_DISPLAY + } +} diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/SectionConfig.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/SectionConfig.java new file mode 100644 index 000000000..66eb8a027 --- /dev/null +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/SectionConfig.java @@ -0,0 +1,13 @@ +package dev.sheldan.abstracto.core.templating.model.messagecomponents; + +import java.util.List; +import lombok.Getter; +import lombok.experimental.SuperBuilder; + +@SuperBuilder +@Getter +public class SectionConfig { + protected SectionAccessoryConfig accessory; + protected List components; + +} diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/SectionTextDisplay.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/SectionTextDisplay.java new file mode 100644 index 000000000..9f1aa0802 --- /dev/null +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/SectionTextDisplay.java @@ -0,0 +1,13 @@ +package dev.sheldan.abstracto.core.templating.model.messagecomponents; + +import lombok.Getter; +import lombok.experimental.SuperBuilder; + +@SuperBuilder +@Getter +public class SectionTextDisplay extends TextDisplayConfig implements SectionComponentConfig { + @Override + public SectionComponentType getType() { + return SectionComponentType.TEXT_DISPLAY; + } +} diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/SectionThumbnail.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/SectionThumbnail.java new file mode 100644 index 000000000..acdfeac2a --- /dev/null +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/SectionThumbnail.java @@ -0,0 +1,20 @@ +package dev.sheldan.abstracto.core.templating.model.messagecomponents; + +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class SectionThumbnail implements SectionAccessoryConfig { + + private String url; + @Builder.Default + private Boolean spoiler = false; + private String description; + private Integer uniqueId; + + @Override + public SectionAccessoryType getType() { + return SectionAccessoryType.THUMBNAIL; + } +} diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/SeparatorConfig.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/SeparatorConfig.java new file mode 100644 index 000000000..385269dd4 --- /dev/null +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/SeparatorConfig.java @@ -0,0 +1,16 @@ +package dev.sheldan.abstracto.core.templating.model.messagecomponents; + +import lombok.Getter; +import lombok.experimental.SuperBuilder; + +@SuperBuilder +@Getter +public class SeparatorConfig implements ComponentConfig { + private Boolean divider; + private Spacing spacing; + @Override + public ComponentTypeConfig getType() { + return ComponentTypeConfig.SEPARATOR; + } + +} diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/Spacing.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/Spacing.java new file mode 100644 index 000000000..4b00c04bb --- /dev/null +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/Spacing.java @@ -0,0 +1,19 @@ +package dev.sheldan.abstracto.core.templating.model.messagecomponents; + +import com.google.gson.annotations.SerializedName; +import net.dv8tion.jda.api.components.separator.Separator; + +public enum Spacing { + @SerializedName("small") + SMALL, + @SerializedName("large") + LARGE; + + public static Separator.Spacing toSpacing(Spacing spacing) { + return switch (spacing) { + case LARGE -> Separator.Spacing.LARGE; + case SMALL -> Separator.Spacing.SMALL; + default -> Separator.Spacing.UNKNOWN; + }; + } +} diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/TextDisplayConfig.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/TextDisplayConfig.java new file mode 100644 index 000000000..ae2e2e26e --- /dev/null +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/TextDisplayConfig.java @@ -0,0 +1,11 @@ +package dev.sheldan.abstracto.core.templating.model.messagecomponents; + +import lombok.Getter; +import lombok.experimental.SuperBuilder; + +@SuperBuilder +@Getter +public class TextDisplayConfig { + private Integer uniqueId; + private String content; +} diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/TopLevelActionRowConfig.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/TopLevelActionRowConfig.java new file mode 100644 index 000000000..8f2170633 --- /dev/null +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/TopLevelActionRowConfig.java @@ -0,0 +1,13 @@ +package dev.sheldan.abstracto.core.templating.model.messagecomponents; + +import lombok.Getter; +import lombok.experimental.SuperBuilder; + +@SuperBuilder +@Getter +public class TopLevelActionRowConfig extends ActionRowConfig implements ComponentConfig { + @Override + public ComponentTypeConfig getType() { + return ComponentTypeConfig.ACTION_ROW; + } +} diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/TopLevelContainerConfig.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/TopLevelContainerConfig.java new file mode 100644 index 000000000..06d2bd80a --- /dev/null +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/TopLevelContainerConfig.java @@ -0,0 +1,13 @@ +package dev.sheldan.abstracto.core.templating.model.messagecomponents; + +import lombok.Getter; +import lombok.experimental.SuperBuilder; + +@SuperBuilder +@Getter +public class TopLevelContainerConfig extends ContainerConfig implements ComponentConfig { + @Override + public ComponentTypeConfig getType() { + return ComponentTypeConfig.SECTION; + } +} diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/TopLevelFileConfig.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/TopLevelFileConfig.java new file mode 100644 index 000000000..c46e1ee94 --- /dev/null +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/TopLevelFileConfig.java @@ -0,0 +1,13 @@ +package dev.sheldan.abstracto.core.templating.model.messagecomponents; + +import lombok.Getter; +import lombok.experimental.SuperBuilder; + +@SuperBuilder +@Getter +public class TopLevelFileConfig extends FileConfig implements ComponentConfig { + @Override + public ComponentTypeConfig getType() { + return ComponentTypeConfig.FILE_DISPLAY; + } +} diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/TopLevelMediaGalleryConfig.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/TopLevelMediaGalleryConfig.java new file mode 100644 index 000000000..69a9b9555 --- /dev/null +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/TopLevelMediaGalleryConfig.java @@ -0,0 +1,13 @@ +package dev.sheldan.abstracto.core.templating.model.messagecomponents; + +import lombok.Getter; +import lombok.experimental.SuperBuilder; + +@SuperBuilder +@Getter +public class TopLevelMediaGalleryConfig extends MediaGalleryConfig implements ComponentConfig { + @Override + public ComponentTypeConfig getType() { + return ComponentTypeConfig.MEDIA_GALLERY; + } +} diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/TopLevelSectionConfig.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/TopLevelSectionConfig.java new file mode 100644 index 000000000..cdb25f843 --- /dev/null +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/TopLevelSectionConfig.java @@ -0,0 +1,13 @@ +package dev.sheldan.abstracto.core.templating.model.messagecomponents; + +import lombok.Getter; +import lombok.experimental.SuperBuilder; + +@SuperBuilder +@Getter +public class TopLevelSectionConfig extends SectionConfig implements ComponentConfig { + @Override + public ComponentTypeConfig getType() { + return ComponentTypeConfig.SECTION; + } +} diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/TopLevelSeperatorConfig.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/TopLevelSeperatorConfig.java new file mode 100644 index 000000000..72ae8e904 --- /dev/null +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/TopLevelSeperatorConfig.java @@ -0,0 +1,13 @@ +package dev.sheldan.abstracto.core.templating.model.messagecomponents; + +import lombok.Getter; +import lombok.experimental.SuperBuilder; + +@SuperBuilder +@Getter +public class TopLevelSeperatorConfig extends SeparatorConfig implements ComponentConfig { + @Override + public ComponentTypeConfig getType() { + return ComponentTypeConfig.SEPARATOR; + } +} diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/TopLevelTextDisplay.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/TopLevelTextDisplay.java new file mode 100644 index 000000000..c75c794f4 --- /dev/null +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/messagecomponents/TopLevelTextDisplay.java @@ -0,0 +1,13 @@ +package dev.sheldan.abstracto.core.templating.model.messagecomponents; + +import lombok.Getter; +import lombok.experimental.SuperBuilder; + +@SuperBuilder +@Getter +public class TopLevelTextDisplay extends TextDisplayConfig implements ComponentConfig { + @Override + public ComponentTypeConfig getType() { + return ComponentTypeConfig.TEXT_DISPLAY; + } +} diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/service/TemplateServiceBean.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/service/TemplateServiceBean.java index c8d0c9228..4d4b1a3f8 100644 --- a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/service/TemplateServiceBean.java +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/service/TemplateServiceBean.java @@ -10,6 +10,24 @@ import dev.sheldan.abstracto.core.service.ConfigService; import dev.sheldan.abstracto.core.templating.Templatable; import dev.sheldan.abstracto.core.templating.exception.TemplatingException; import dev.sheldan.abstracto.core.templating.model.*; +import dev.sheldan.abstracto.core.templating.model.messagecomponents.ButtonConfig; +import dev.sheldan.abstracto.core.templating.model.messagecomponents.ActionRowItemConfig; +import dev.sheldan.abstracto.core.templating.model.messagecomponents.ButtonMetaConfig; +import dev.sheldan.abstracto.core.templating.model.messagecomponents.ButtonStyleConfig; +import dev.sheldan.abstracto.core.templating.model.messagecomponents.ComponentConfig; +import dev.sheldan.abstracto.core.templating.model.messagecomponents.SectionAccessoryConfig; +import dev.sheldan.abstracto.core.templating.model.messagecomponents.SectionButton; +import dev.sheldan.abstracto.core.templating.model.messagecomponents.SectionComponentConfig; +import dev.sheldan.abstracto.core.templating.model.messagecomponents.SectionThumbnail; +import dev.sheldan.abstracto.core.templating.model.messagecomponents.Spacing; +import dev.sheldan.abstracto.core.templating.model.messagecomponents.TextDisplayConfig; +import dev.sheldan.abstracto.core.templating.model.messagecomponents.TopLevelActionRowConfig; +import dev.sheldan.abstracto.core.templating.model.messagecomponents.TopLevelContainerConfig; +import dev.sheldan.abstracto.core.templating.model.messagecomponents.TopLevelFileConfig; +import dev.sheldan.abstracto.core.templating.model.messagecomponents.TopLevelMediaGalleryConfig; +import dev.sheldan.abstracto.core.templating.model.messagecomponents.TopLevelSectionConfig; +import dev.sheldan.abstracto.core.templating.model.messagecomponents.TopLevelSeperatorConfig; +import dev.sheldan.abstracto.core.templating.model.messagecomponents.TopLevelTextDisplay; import dev.sheldan.abstracto.core.utils.FileService; import freemarker.template.Configuration; import freemarker.template.Template; @@ -19,16 +37,28 @@ import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; import net.dv8tion.jda.api.EmbedBuilder; +import net.dv8tion.jda.api.components.MessageTopLevelComponent; +import net.dv8tion.jda.api.components.actionrow.ActionRowChildComponent; +import net.dv8tion.jda.api.components.container.Container; +import net.dv8tion.jda.api.components.container.ContainerChildComponent; +import net.dv8tion.jda.api.components.filedisplay.FileDisplay; +import net.dv8tion.jda.api.components.mediagallery.MediaGallery; +import net.dv8tion.jda.api.components.mediagallery.MediaGalleryItem; +import net.dv8tion.jda.api.components.section.Section; +import net.dv8tion.jda.api.components.section.SectionAccessoryComponent; +import net.dv8tion.jda.api.components.section.SectionContentComponent; +import net.dv8tion.jda.api.components.separator.Separator; +import net.dv8tion.jda.api.components.textdisplay.TextDisplay; +import net.dv8tion.jda.api.components.thumbnail.Thumbnail; import net.dv8tion.jda.api.entities.Message; import net.dv8tion.jda.api.entities.MessageEmbed; import net.dv8tion.jda.api.entities.channel.ChannelType; -import net.dv8tion.jda.api.entities.emoji.Emoji; -import net.dv8tion.jda.api.interactions.components.ActionRow; +import net.dv8tion.jda.api.components.actionrow.ActionRow; import net.dv8tion.jda.api.interactions.components.ItemComponent; -import net.dv8tion.jda.api.interactions.components.buttons.Button; -import net.dv8tion.jda.api.interactions.components.selections.EntitySelectMenu; -import net.dv8tion.jda.api.interactions.components.selections.SelectOption; -import net.dv8tion.jda.api.interactions.components.selections.StringSelectMenu; +import net.dv8tion.jda.api.components.buttons.Button; +import net.dv8tion.jda.api.components.selections.EntitySelectMenu; +import net.dv8tion.jda.api.components.selections.SelectOption; +import net.dv8tion.jda.api.components.selections.StringSelectMenu; import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; @@ -68,7 +98,7 @@ public class TemplateServiceBean implements TemplateService { private FileService fileService; /** - * Formats the passed passed count with the embed used for formatting pages. + * Formats the passed count with the embed used for formatting pages. * * @param count The index of the page you want formatted. * @return The rendered template as a string object @@ -100,90 +130,103 @@ public class TemplateServiceBean implements TemplateService { } public MessageToSend convertEmbedConfigurationToMessageToSend(MessageConfiguration messageConfiguration) { - List embedBuilders = new ArrayList<>(); - if(messageConfiguration.getEmbeds() != null && !messageConfiguration.getEmbeds().isEmpty()) { - convertEmbeds(messageConfiguration, embedBuilders); - } - + List embeds = new ArrayList<>(); List actionRows = new ArrayList<>(); Map componentPayloads = new HashMap<>(); - if(messageConfiguration.getButtons() != null || messageConfiguration.getSelectionMenus() != null) { - // this basically preprocesses the buttons and select menus - // by getting the positions of the items first - // we only need this, because the current message config does not have them in the same item - // they are two distinct lists, but map to the same concept in discord: components - Set positions = new HashSet<>(); - HashMap buttonPositions = new HashMap<>(); - List buttonsWithoutPosition = new ArrayList<>(); - HashMap selectionMenuPositions = new HashMap<>(); - List selectionMenusWithoutPosition = new ArrayList<>(); - // we do this by getting all positions which are part of the config - // we also track which positions are buttons and which are select menus - if(messageConfiguration.getButtons() != null) { - messageConfiguration.getButtons().forEach(buttonConfig -> { - if(buttonConfig.getPosition() != null) { - positions.add(buttonConfig.getPosition()); - buttonPositions.put(buttonConfig.getPosition(), buttonConfig); - } else { - buttonsWithoutPosition.add(buttonConfig); + List discordComponents = new ArrayList<>(); + boolean useComponentsV2; + if(messageConfiguration.getMessageConfig() != null && messageConfiguration.getMessageConfig().isUseComponentsV2()) { + useComponentsV2 = true; + if(messageConfiguration.getComponents() != null) { + for (ComponentConfig componentConfig : messageConfiguration.getComponents()) { + net.dv8tion.jda.api.components.Component createdComponent = getComponent(componentConfig, componentPayloads); + if(createdComponent != null) { + discordComponents.add((MessageTopLevelComponent) createdComponent); } - }); - } - - if(messageConfiguration.getSelectionMenus() != null) { - messageConfiguration.getSelectionMenus().forEach(selectionMenuConfig -> { - if(selectionMenuConfig.getPosition() != null) { - positions.add(selectionMenuConfig.getPosition()); - selectionMenuPositions.put(selectionMenuConfig.getPosition(), selectionMenuConfig); - } else { - selectionMenusWithoutPosition.add(selectionMenuConfig); - } - }); - } - List positionsSorted = new ArrayList<>(positions); - Collections.sort(positionsSorted); - List currentButtons = new ArrayList<>(); - // we go over all positions, and if its part of the buttons, we only add it to a list of buttons - // this will then mean, that all buttons are processed as a group - // this is necessary, because we can only add buttons as part of an action row - // and in order to make it easier, we process the whole chunk of buttons at once, producing - // at least one or more action rows - for (Integer position : positionsSorted) { - if (buttonPositions.containsKey(position)) { - currentButtons.add(buttonPositions.get(position)); - } else { - // if we get interrupted by a selection menu, we process the buttons we have so far - // because those should be handled as a group - // and then process the selection menu, the selection menu will always represent one full action row - // it is not possible to have a button and a menu in the same row - if(!currentButtons.isEmpty()) { - addButtons(actionRows, componentPayloads, currentButtons); - currentButtons.clear(); - } - addSelectionMenu(actionRows, selectionMenuPositions.get(position)); } } - if(!currentButtons.isEmpty()) { - addButtons(actionRows, componentPayloads, currentButtons); - currentButtons.clear(); + } else { + useComponentsV2 = false; + List embedBuilders = new ArrayList<>(); + if(messageConfiguration.getEmbeds() != null && !messageConfiguration.getEmbeds().isEmpty()) { + convertEmbeds(messageConfiguration, embedBuilders); } - // all the rest without positions will be processed at the end (probably default case for most cases) - addButtons(actionRows, componentPayloads, buttonsWithoutPosition); - // selection menus are handled afterwards, that is just implied logic - // to have a select menu before a button, one would need to set accordingly, or only - // set the position for the selection menu, and not for the button - selectionMenusWithoutPosition.forEach(selectionMenuConfig -> addSelectionMenu(actionRows, selectionMenuConfig)); - } - setPagingFooters(embedBuilders); + if(messageConfiguration.getButtons() != null || messageConfiguration.getSelectionMenus() != null) { + // this basically preprocesses the buttons and select menus + // by getting the positions of the items first + // we only need this, because the current message config does not have them in the same item + // they are two distinct lists, but map to the same concept in discord: components + Set positions = new HashSet<>(); + HashMap buttonPositions = new HashMap<>(); + List buttonsWithoutPosition = new ArrayList<>(); + HashMap selectionMenuPositions = new HashMap<>(); + List selectionMenusWithoutPosition = new ArrayList<>(); + // we do this by getting all positions which are part of the config + // we also track which positions are buttons and which are select menus + if(messageConfiguration.getButtons() != null) { + messageConfiguration.getButtons().forEach(buttonConfig -> { + if(buttonConfig.getPosition() != null) { + positions.add(buttonConfig.getPosition()); + buttonPositions.put(buttonConfig.getPosition(), buttonConfig); + } else { + buttonsWithoutPosition.add(buttonConfig); + } + }); + } - List embeds = new ArrayList<>(); - if (!embedBuilders.isEmpty()) { - embeds = embedBuilders + if(messageConfiguration.getSelectionMenus() != null) { + messageConfiguration.getSelectionMenus().forEach(selectionMenuConfig -> { + if(selectionMenuConfig.getPosition() != null) { + positions.add(selectionMenuConfig.getPosition()); + selectionMenuPositions.put(selectionMenuConfig.getPosition(), selectionMenuConfig); + } else { + selectionMenusWithoutPosition.add(selectionMenuConfig); + } + }); + } + List positionsSorted = new ArrayList<>(positions); + Collections.sort(positionsSorted); + List currentButtons = new ArrayList<>(); + // we go over all positions, and if its part of the buttons, we only add it to a list of buttons + // this will then mean, that all buttons are processed as a group + // this is necessary, because we can only add buttons as part of an action row + // and in order to make it easier, we process the whole chunk of buttons at once, producing + // at least one or more action rows + for (Integer position : positionsSorted) { + if (buttonPositions.containsKey(position)) { + currentButtons.add(buttonPositions.get(position)); + } else { + // if we get interrupted by a selection menu, we process the buttons we have so far + // because those should be handled as a group + // and then process the selection menu, the selection menu will always represent one full action row + // it is not possible to have a button and a menu in the same row + if(!currentButtons.isEmpty()) { + addButtons(actionRows, componentPayloads, currentButtons); + currentButtons.clear(); + } + addSelectionMenu(actionRows, selectionMenuPositions.get(position)); + } + } + if(!currentButtons.isEmpty()) { + addButtons(actionRows, componentPayloads, currentButtons); + currentButtons.clear(); + } + // all the rest without positions will be processed at the end (probably default case for most cases) + addButtons(actionRows, componentPayloads, buttonsWithoutPosition); + // selection menus are handled afterwards, that is just implied logic + // to have a select menu before a button, one would need to set accordingly, or only + // set the position for the selection menu, and not for the button + selectionMenusWithoutPosition.forEach(selectionMenuConfig -> addSelectionMenu(actionRows, selectionMenuConfig)); + } + setPagingFooters(embedBuilders); + if (!embedBuilders.isEmpty()) { + embeds = embedBuilders .stream() .filter(embedBuilder -> !embedBuilder.isEmpty()) .map(EmbedBuilder::build) .collect(Collectors.toList()); + } } List messages = new ArrayList<>(); @@ -269,6 +312,8 @@ public class TemplateServiceBean implements TemplateService { .messageConfig(createMessageConfig(messageConfiguration.getMessageConfig())) .messages(messages) .ephemeral(isEphemeral) + .useComponentsV2(useComponentsV2) + .components(discordComponents) .attachedFiles(files) .actionRows(actionRows) .componentPayloads(componentPayloads) @@ -276,6 +321,105 @@ public class TemplateServiceBean implements TemplateService { .build(); } + private net.dv8tion.jda.api.components.Component getComponent(ComponentConfig componentConfig, Map componentPayloads) { + if(componentConfig instanceof TopLevelActionRowConfig actionRowConfig) { + List actionRowChildComponents = new ArrayList<>(); + for (ActionRowItemConfig actionRowItemConfig : actionRowConfig.getActionRowItems()) { + if(actionRowItemConfig instanceof ButtonConfig actionRowButtonConfig) { + Button createdButton = createButtonFromConfig(componentPayloads, actionRowButtonConfig); + actionRowChildComponents.add(createdButton); + } + } + return ActionRow.of(actionRowChildComponents); + } else if(componentConfig instanceof TopLevelTextDisplay textDisplayConfig) { + return createTextDisplay(textDisplayConfig); + } else if (componentConfig instanceof TopLevelSectionConfig sectionConfig) { + SectionAccessoryComponent accessory = null; + if(sectionConfig.getAccessory() != null) { + SectionAccessoryConfig sectionAccessoryConfig = sectionConfig.getAccessory(); + if(sectionAccessoryConfig instanceof SectionButton buttonConfig) { + accessory = createButtonFromConfig(componentPayloads, buttonConfig); + } else if (sectionAccessoryConfig instanceof SectionThumbnail sectionThumbnailConfig) { + accessory = createThumbnail(sectionThumbnailConfig);; + } + } + if(sectionConfig.getComponents() != null && !sectionConfig.getComponents().isEmpty()) { + List sectionComponents = new ArrayList<>(); + for (SectionComponentConfig sectionComponentConfig : sectionConfig.getComponents()) { + if (sectionComponentConfig instanceof TextDisplayConfig textDisplayConfig) { + sectionComponents.add(createTextDisplay(textDisplayConfig)); + } + } + if(accessory != null) { + return Section.of(accessory, sectionComponents); + } + } + } else if(componentConfig instanceof TopLevelFileConfig fileConfig) { + if(fileConfig.getFileContent() != null) { + File file = fileService.createTempFile(fileConfig.getFileName()); + try { + fileService.writeContentToFile(file, fileConfig.getFileContent()); + } catch (IOException e) { + log.error("Failed to write local temporary file.", e); + throw new AbstractoRunTimeException(e); + } + return FileDisplay.fromFile(fileConfig.convertToFileUpload(file)); + } else { + // NOT SUPPORTED ( right now) + } + } else if(componentConfig instanceof TopLevelMediaGalleryConfig mediaGalleryConfig) { + if(mediaGalleryConfig.getImages() != null) { + List galleryItems = new ArrayList<>(); + mediaGalleryConfig.getImages().forEach(imageConfig -> { + MediaGalleryItem item = MediaGalleryItem.fromUrl(imageConfig.getUrl()); + if(imageConfig.getSpoiler() != null) { + item = item.withSpoiler(imageConfig.getSpoiler()); + } + if(imageConfig.getDescription() != null) { + item = item.withDescription(imageConfig.getDescription()); + } + galleryItems.add(item); + }); + return MediaGallery.of(galleryItems); + } + } else if(componentConfig instanceof TopLevelSeperatorConfig seperatorConfig) { + return Separator.create(seperatorConfig.getDivider(), + Spacing.toSpacing(seperatorConfig.getSpacing())); + } else if(componentConfig instanceof TopLevelContainerConfig topLevelContainerConfig) { + List childComponents = new ArrayList<>(); + for (ComponentConfig containerComponent : topLevelContainerConfig.getComponents()) { + net.dv8tion.jda.api.components.Component createdComponent = getComponent(containerComponent, componentPayloads); + if(createdComponent != null) { + childComponents.add((ContainerChildComponent) createdComponent); + } + } + return Container.of(childComponents); + } + return null; + } + + private static Thumbnail createThumbnail(SectionThumbnail sectionThumbnailConfig) { + Thumbnail thumbnail = Thumbnail.fromUrl(sectionThumbnailConfig.getUrl()); + if(sectionThumbnailConfig.getSpoiler() != null) { + thumbnail.withSpoiler(sectionThumbnailConfig.getSpoiler()); + } + if(sectionThumbnailConfig.getDescription() != null) { + thumbnail.withDescription(sectionThumbnailConfig.getDescription()); + } + if(sectionThumbnailConfig.getUniqueId() != null) { + thumbnail.withUniqueId(sectionThumbnailConfig.getUniqueId()); + } + return thumbnail; + } + + private TextDisplay createTextDisplay(TextDisplayConfig textDisplayConfig) { + TextDisplay textDisplay = TextDisplay.of(textDisplayConfig.getContent()); + if(textDisplayConfig.getUniqueId() != null) { + textDisplay.withUniqueId(textDisplayConfig.getUniqueId()); + } + return textDisplay; + } + private void addSelectionMenu(List actionRows, SelectionMenuConfig selectionMenuConfig) { ItemComponent selectionMenu; if (selectionMenuConfig.getType() == SelectionMenuType.STRING) { @@ -346,53 +490,64 @@ public class TemplateServiceBean implements TemplateService { } private void addButtons(List actionRows, Map componentPayloads, List buttonConfigs) { - ActionRow currentRow = null; + List