[AB-90] adding poll functionality

adding select menu functionality
not automatically acknowledging button interactions
adding ability to define positions for components
adding method to remove components to channel service
always replacing message contents with edit message in a channel
adding ability to reply a modal to a button interaction
moving post target specific methods from server management service to post target management
This commit is contained in:
Sheldan
2023-06-04 20:50:02 +02:00
parent efbcb5c84b
commit bac9832819
100 changed files with 3564 additions and 90 deletions

View File

@@ -72,6 +72,11 @@ public class ListenerExecutorConfig {
return executorService.setupExecutorFor("buttonClickedListener");
}
@Bean(name = "stringSelectMenuExecutor")
public TaskExecutor stringSelectMenuExecutor() {
return executorService.setupExecutorFor("stringSelectMenuListener");
}
@Bean(name = "modalInteractionExecutor")
public TaskExecutor modalInteractionExecutor() {
return executorService.setupExecutorFor("modalInteractionListener");

View File

@@ -101,7 +101,7 @@ public class InteractionServiceBean implements InteractionService {
if(component instanceof ActionComponent) {
String id = ((ActionComponent)component).getId();
MessageToSend.ComponentConfig payload = messageToSend.getComponentPayloads().get(id);
if(payload.getPersistCallback()) {
if(payload != null && payload.getPersistCallback()) {
componentPayloadManagementService.createPayload(id, payload.getPayload(), payload.getPayloadType(), payload.getComponentOrigin(), server, payload.getComponentType());
}
}
@@ -217,7 +217,7 @@ public class InteractionServiceBean implements InteractionService {
if(component instanceof ActionComponent) {
String id = ((ActionComponent)component).getId();
MessageToSend.ComponentConfig payload = messageToSend.getComponentPayloads().get(id);
if(payload.getPersistCallback()) {
if(payload != null && payload.getPersistCallback()) {
componentPayloadManagementService.createPayload(id, payload.getPayload(), payload.getPayloadType(), payload.getComponentOrigin(), server, payload.getComponentType());
}
}
@@ -273,7 +273,7 @@ public class InteractionServiceBean implements InteractionService {
if(component instanceof ActionComponent) {
String id = ((ActionComponent)component).getId();
MessageToSend.ComponentConfig payload = messageToSend.getComponentPayloads().get(id);
if(payload.getPersistCallback()) {
if(payload != null && payload.getPersistCallback()) {
componentPayloadManagementService.createPayload(id, payload.getPayload(), payload.getPayloadType(), payload.getComponentOrigin(), server, payload.getComponentType());
}
}
@@ -303,6 +303,11 @@ public class InteractionServiceBean implements InteractionService {
return replyMessageToSend(messageToSend, callback);
}
@Override
public CompletableFuture<Message> replyString(String text, InteractionHook interactionHook) {
return interactionHook.sendMessage(text).submit();
}
@PostConstruct
public void postConstruct() {
metricService.registerCounter(EPHEMERAL_MESSAGES_SEND, "Ephemeral messages send");

View File

@@ -62,8 +62,6 @@ public class SyncButtonClickedListenerBean extends ListenerAdapter {
@Override
public void onButtonInteraction(@Nonnull ButtonInteractionEvent event) {
if(listenerList == null) return;
// TODO remove this and make this configurable
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;
@@ -83,6 +81,9 @@ public class SyncButtonClickedListenerBean extends ListenerAdapter {
if(listenerOptional.isPresent()) {
listener = listenerOptional.get();
log.info("Executing button listener {} for event for id {}.", listener.getClass().getSimpleName(), event.getComponentId());
if(listener.autoAcknowledgeEvent()) {
event.deferEdit().queue();
}
listener.execute(model);
InteractionResult result = InteractionResult.fromSuccess();
for (ButtonPostInteractionExecution postInteractionExecution : postInteractionExecutions) {

View File

@@ -0,0 +1,135 @@
package dev.sheldan.abstracto.core.interaction.menu;
import com.google.gson.Gson;
import dev.sheldan.abstracto.core.config.FeatureConfig;
import dev.sheldan.abstracto.core.interaction.ComponentPayloadManagementService;
import dev.sheldan.abstracto.core.interaction.InteractionResult;
import dev.sheldan.abstracto.core.interaction.menu.listener.StringSelectMenuListener;
import dev.sheldan.abstracto.core.interaction.menu.listener.StringSelectMenuListenerModel;
import dev.sheldan.abstracto.core.models.database.ComponentPayload;
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.utils.BeanUtils;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.events.interaction.component.StringSelectInteractionEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
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.Transactional;
import javax.annotation.Nonnull;
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 StringSelectMenuListenerBean extends ListenerAdapter {
@Autowired(required = false)
private List<StringSelectMenuListener> listenerList;
@Autowired
@Qualifier("buttonClickedExecutor")
private TaskExecutor buttonClickedExecutor;
@Autowired
private StringSelectMenuListenerBean self;
@Autowired
private FeatureConfigService featureConfigService;
@Autowired
private FeatureFlagService featureFlagService;
@Autowired
private ComponentPayloadManagementService componentPayloadManagementService;
@Autowired
private FeatureModeService featureModeService;
@Autowired
private Gson gson;
@Override
public void onStringSelectInteraction(@Nonnull StringSelectInteractionEvent event) {
if(listenerList == null) return;
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;
});
}
@Transactional
public void executeListenerLogic(StringSelectInteractionEvent event) {
StringSelectMenuListenerModel model = null;
StringSelectMenuListener listener = null;
try {
Optional<ComponentPayload> callbackInformation = componentPayloadManagementService.findPayload(event.getComponentId());
if(callbackInformation.isPresent()) {
model = getModel(event, callbackInformation.get());
List<StringSelectMenuListener> validListener = filterFeatureAwareListener(listenerList, model);
Optional<StringSelectMenuListener> listenerOptional = findListener(validListener, model);
if(listenerOptional.isPresent()) {
listener = listenerOptional.get();
log.info("Executing string select menu listener {} for event for id {}.", listener.getClass().getSimpleName(), event.getComponentId());
listener.execute(model);
} else {
log.warn("No listener found for string select menu event for id {}.", event.getComponentId());
}
} else {
log.warn("No callback found for id {}.", event.getComponentId());
}
} catch (Exception exception) {
if(event.isFromGuild()) {
log.error("String select menu listener failed with exception in server {} and channel {}.", event.getGuild().getIdLong(),
event.getGuildChannel().getIdLong(), exception);
} else {
log.error("String select menu listener failed with exception outside of a guild.", exception);
}
}
}
private Optional<StringSelectMenuListener> findListener(List<StringSelectMenuListener> featureAwareListeners, StringSelectMenuListenerModel model) {
return featureAwareListeners.stream().filter(asyncButtonClickedListener -> asyncButtonClickedListener.handlesEvent(model)).findFirst();
}
private List<StringSelectMenuListener> filterFeatureAwareListener(List<StringSelectMenuListener> featureAwareListeners, StringSelectMenuListenerModel model) {
return featureAwareListeners.stream().filter(trFeatureAwareListener -> {
FeatureConfig feature = featureConfigService.getFeatureDisplayForFeature(trFeatureAwareListener.getFeature());
if(!model.getEvent().isFromGuild()) {
return true;
}
if (!featureFlagService.isFeatureEnabled(feature, model.getServerId())) {
return false;
}
return featureModeService.necessaryFeatureModesMet(trFeatureAwareListener, model.getServerId());
}).collect(Collectors.toList());
}
private StringSelectMenuListenerModel getModel(StringSelectInteractionEvent event, ComponentPayload componentPayload) throws ClassNotFoundException {
SelectMenuPayload payload = null;
if(componentPayload.getPayloadType() != null && componentPayload.getPayload() != null) {
payload = (SelectMenuPayload) gson.fromJson(componentPayload.getPayload(), Class.forName(componentPayload.getPayloadType()));
}
return StringSelectMenuListenerModel
.builder()
.event(event)
.deserializedPayload(payload)
.payload(componentPayload.getPayload())
.origin(componentPayload.getOrigin())
.build();
}
@PostConstruct
public void postConstruct() {
BeanUtils.sortPrioritizedListeners(listenerList);
}
}

View File

@@ -7,6 +7,7 @@ import dev.sheldan.abstracto.core.interaction.modal.config.TextInputComponent;
import dev.sheldan.abstracto.core.interaction.modal.config.TextInputComponentStyle;
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.interactions.components.ItemComponent;
import net.dv8tion.jda.api.interactions.components.text.TextInput;
@@ -34,6 +35,12 @@ public class ModalServiceBean implements ModalService {
return event.replyModal(modal).submit();
}
@Override
public CompletableFuture<Void> replyModal(ButtonInteractionEvent event, String templateKey, Object model) {
Modal modal = createModalFromTemplate(templateKey, model, event.getGuild().getIdLong());
return event.replyModal(modal).submit();
}
@Override
public Modal createModalFromTemplate(String templateKey, Object model, Long serverId) {
String modalConfigString = templateService.renderTemplate(templateKey + "_modal", model, serverId);
@@ -44,7 +51,7 @@ public class ModalServiceBean implements ModalService {
.sorted(Comparator.comparing(ModalComponent::getPosition))
.collect(Collectors.toList());
return Modal.create(modalConfig.getId(), modalConfig.getTitle())
.addActionRows(convertToActionRows(components))
.addComponents(convertToActionRows(components))
.build();
}

View File

@@ -123,12 +123,12 @@ public class SlashCommandParameterServiceBean implements SlashCommandParameterSe
@Override
public Object getCommandOption(String name, SlashCommandInteractionEvent event) {
return event.getOption(name);
return event.getOption(name.toLowerCase(Locale.ROOT));
}
@Override
public Boolean hasCommandOption(String name, SlashCommandInteractionEvent event) {
return event.getOption(name) != null;
return event.getOption(name.toLowerCase(Locale.ROOT)) != null;
}
@Override

View File

@@ -350,7 +350,7 @@ public class ChannelServiceBean implements ChannelService {
.collect(Collectors.toList());
messageAction = messageAction.setFiles(files);
}
messageAction = messageAction.setComponents(messageToSend.getActionRows());
messageAction = messageAction.setComponents(messageToSend.getActionRows()).setReplace(true);
metricService.incrementCounter(MESSAGE_EDIT_METRIC);
return messageAction.submit();
}
@@ -411,6 +411,11 @@ public class ChannelServiceBean implements ChannelService {
});
}
@Override
public CompletableFuture<Message> removeComponents(MessageChannel channel, Long messageId) {
return channel.editMessageComponentsById(messageId, new ArrayList<>()).submit();
}
@Override
public CompletableFuture<Void> deleteTextChannel(AChannel channel) {
return deleteTextChannel(channel.getServer().getId(), channel.getId());

View File

@@ -2,6 +2,7 @@ package dev.sheldan.abstracto.core.service.management;
import com.google.gson.Gson;
import dev.sheldan.abstracto.core.interaction.ComponentPayloadManagementService;
import dev.sheldan.abstracto.core.interaction.menu.SelectMenuConfigModel;
import dev.sheldan.abstracto.core.interaction.modal.ModalConfigPayload;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.ComponentPayload;
@@ -59,6 +60,18 @@ public class ComponentPayloadManagementServiceBean implements ComponentPayloadMa
return createButtonPayload(buttonConfigModel, server);
}
@Override
public ComponentPayload createStringSelectMenuPayload(SelectMenuConfigModel selectMenuConfigModel, Long serverId) {
AServer server = serverManagementService.loadOrCreate(serverId);
return createStringSelectMenuPayload(selectMenuConfigModel, server);
}
@Override
public ComponentPayload createStringSelectMenuPayload(SelectMenuConfigModel selectMenuConfigModel, AServer server) {
String payload = gson.toJson(selectMenuConfigModel.getSelectMenuPayload());
return createPayload(selectMenuConfigModel.getSelectMenuId(), payload, selectMenuConfigModel.getPayloadType(), selectMenuConfigModel.getOrigin(), server, ComponentType.SELECTION);
}
@Override
public ComponentPayload createModalPayload(ModalConfigPayload payloadConfig, Long serverId) {
String payload = gson.toJson(payloadConfig.getModalPayload());

View File

@@ -130,4 +130,27 @@ public class PostTargetManagementBean implements PostTargetManagement {
return postTargetRepository.findByServerReference(server);
}
@Override
public AChannel getPostTarget(Long serverId, String name) {
AServer server = serverManagementService.loadOrCreate(serverId);
return getPostTarget(server, name);
}
@Override
public AChannel getPostTarget(Long serverId, PostTarget target) {
AServer server = serverManagementService.loadOrCreate(serverId);
return getPostTarget(server, target);
}
@Override
public AChannel getPostTarget(AServer server, PostTarget target) {
return target.getChannelReference();
}
@Override
public AChannel getPostTarget(AServer server, String name) {
PostTarget target = getPostTarget(name, server);
return getPostTarget(server, target);
}
}

View File

@@ -20,9 +20,6 @@ public class ServerManagementServiceBean implements ServerManagementService {
@Autowired
private ServerRepository repository;
@Autowired
private PostTargetManagement postTargetManagement;
@Autowired
private UserManagementService userManagementService;
@@ -93,29 +90,6 @@ public class ServerManagementServiceBean implements ServerManagementService {
return aUserInAServer;
}
@Override
public AChannel getPostTarget(Long serverId, String name) {
AServer server = this.loadOrCreate(serverId);
return getPostTarget(server, name);
}
@Override
public AChannel getPostTarget(Long serverId, PostTarget target) {
AServer server = this.loadOrCreate(serverId);
return getPostTarget(server, target);
}
@Override
public AChannel getPostTarget(AServer server, PostTarget target) {
return target.getChannelReference();
}
@Override
public AChannel getPostTarget(AServer server, String name) {
PostTarget target = postTargetManagement.getPostTarget(name, server);
return getPostTarget(server, target);
}
@Override
public List<AServer> getAllServers() {
return repository.findAll();

View File

@@ -17,4 +17,5 @@ public class ButtonConfig {
private String buttonPayload;
private String payloadType;
private ButtonMetaConfig metaConfig;
private Integer position;
}

View File

@@ -22,5 +22,6 @@ public class MessageConfiguration {
private String additionalMessage;
private MetaMessageConfiguration messageConfig;
private List<ButtonConfig> buttons;
private List<SelectionMenuConfig> selectionMenus;
private List<FileConfig> files;
}

View File

@@ -0,0 +1,10 @@
package dev.sheldan.abstracto.core.templating.model;
import com.google.gson.annotations.SerializedName;
public enum SelectionMenuChannelType {
@SerializedName("TEXT")
TEXT,
@SerializedName("VOICE")
VOICE
}

View File

@@ -0,0 +1,23 @@
package dev.sheldan.abstracto.core.templating.model;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Getter
@Setter
@Builder
public class SelectionMenuConfig {
private String id;
private SelectionMenuType type;
private List<SelectionMenuTarget> targets;
private List<SelectionMenuChannelType> channelTypes;
private List<SelectionMenuEntry> menuEntries;
private Integer position;
private Integer minValues;
private Integer maxValues;
private Boolean disabled;
private String placeholder;
}

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 SelectionMenuEntry {
private String value;
private String label;
private Boolean isDefault;
private String description;
}

View File

@@ -0,0 +1,12 @@
package dev.sheldan.abstracto.core.templating.model;
import com.google.gson.annotations.SerializedName;
public enum SelectionMenuTarget {
@SerializedName("USER")
USER,
@SerializedName("ROLE")
ROLE,
@SerializedName("CHANNEL")
CHANNEL
}

View File

@@ -0,0 +1,10 @@
package dev.sheldan.abstracto.core.templating.model;
import com.google.gson.annotations.SerializedName;
public enum SelectionMenuType {
@SerializedName("STRING")
STRING,
@SerializedName("ENTITY")
ENTITY
}

View File

@@ -21,10 +21,16 @@ import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.EmbedBuilder;
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.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 org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@@ -99,54 +105,74 @@ public class TemplateServiceBean implements TemplateService {
convertEmbeds(messageConfiguration, embedBuilders);
}
List<ActionRow> buttons = new ArrayList<>();
List<ActionRow> actionRows = new ArrayList<>();
Map<String, MessageToSend.ComponentConfig> componentPayloads = new HashMap<>();
if(messageConfiguration.getButtons() != null) {
ActionRow currentRow = null;
for (ButtonConfig buttonConfig : messageConfiguration.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)
.componentType(ComponentType.BUTTON)
.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);
String idOrUl = buttonConfig.getUrl() == null ? buttonConfig.getId() : buttonConfig.getUrl();
Button createdButton = Button.of(ButtonStyleConfig.getStyle(buttonConfig.getButtonStyle()), idOrUl, buttonConfig.getLabel());
if (buttonConfig.getDisabled() != null) {
createdButton = createdButton.withDisabled(buttonConfig.getDisabled());
}
if (buttonConfig.getEmoteMarkdown() != null) {
createdButton = createdButton.withEmoji(Emoji.fromFormatted(buttonConfig.getEmoteMarkdown()));
}
if(currentRow == null) {
currentRow = ActionRow.of(createdButton);
} else if (
(
metaConfig != null &&
Boolean.TRUE.equals(metaConfig.getForceNewRow())
)
|| currentRow.getComponents().size() == ComponentServiceBean.MAX_BUTTONS_PER_ROW) {
buttons.add(currentRow);
currentRow = ActionRow.of(createdButton);
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<Integer> positions = new HashSet<>();
HashMap<Integer, ButtonConfig> buttonPositions = new HashMap<>();
List<ButtonConfig> buttonsWithoutPosition = new ArrayList<>();
HashMap<Integer, SelectionMenuConfig> selectionMenuPositions = new HashMap<>();
List<SelectionMenuConfig> 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);
}
});
}
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<Integer> positionsSorted = new ArrayList<>(positions);
Collections.sort(positionsSorted);
List<ButtonConfig> 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 {
currentRow.getComponents().add(createdButton);
// 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(currentRow != null) {
buttons.add(currentRow);
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);
@@ -244,12 +270,129 @@ public class TemplateServiceBean implements TemplateService {
.messages(messages)
.ephemeral(isEphemeral)
.attachedFiles(files)
.actionRows(buttons)
.actionRows(actionRows)
.componentPayloads(componentPayloads)
.referencedMessageId(referencedMessageId)
.build();
}
private void addSelectionMenu(List<ActionRow> actionRows, SelectionMenuConfig selectionMenuConfig) {
ItemComponent selectionMenu;
if (selectionMenuConfig.getType() == SelectionMenuType.STRING) {
List<SelectOption> selectOptions = selectionMenuConfig.getMenuEntries().stream().map(selectionMenuEntry -> {
SelectOption option = SelectOption.of(selectionMenuEntry.getLabel(), selectionMenuEntry.getValue());
if (StringUtils.isNotBlank(selectionMenuEntry.getDescription())) {
option = option.withDescription(selectionMenuEntry.getDescription());
}
if(Boolean.TRUE.equals(selectionMenuEntry.getIsDefault())) {
option = option.withDefault(true);
}
return option;
}).collect(Collectors.toList());
StringSelectMenu.Builder builder = StringSelectMenu
.create(selectionMenuConfig.getId())
.addOptions(selectOptions);
List<SelectOption> defaultOptions = selectOptions
.stream()
.filter(SelectOption::isDefault)
.collect(Collectors.toList());
builder.setDefaultOptions(defaultOptions);
if (selectionMenuConfig.getMaxValues() != null) {
builder.setMaxValues(selectionMenuConfig.getMaxValues());
}
if (selectionMenuConfig.getMinValues() != null) {
builder.setMinValues(selectionMenuConfig.getMinValues());
}
if (selectionMenuConfig.getPlaceholder() != null) {
builder.setPlaceholder(selectionMenuConfig.getPlaceholder());
}
selectionMenu = builder.build();
} else {
Set<EntitySelectMenu.SelectTarget> targets = new HashSet<>();
if(selectionMenuConfig.getTargets() != null) {
selectionMenuConfig.getTargets().forEach(selectionMenuTarget -> {
switch (selectionMenuTarget) {
case ROLE:
targets.add(EntitySelectMenu.SelectTarget.ROLE);
break;
case USER:
targets.add(EntitySelectMenu.SelectTarget.USER);
break;
case CHANNEL:
targets.add(EntitySelectMenu.SelectTarget.CHANNEL);
break;
}
});
}
Set<ChannelType> channelTypes = new HashSet<>();
if(selectionMenuConfig.getChannelTypes() != null) {
selectionMenuConfig.getChannelTypes().forEach(channelType -> {
switch (channelType) {
case TEXT:
channelTypes.add(ChannelType.TEXT);
break;
case VOICE:
channelTypes.add(ChannelType.VOICE);
break;
}
});
}
selectionMenu = EntitySelectMenu.create(selectionMenuConfig.getId(), targets)
.setChannelTypes(channelTypes)
.build();
}
actionRows.add(ActionRow.of(selectionMenu));
}
private void addButtons(List<ActionRow> actionRows, Map<String, MessageToSend.ComponentConfig> componentPayloads, List<ButtonConfig> buttonConfigs) {
ActionRow currentRow = null;
for (ButtonConfig buttonConfig : buttonConfigs) {
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)
.componentType(ComponentType.BUTTON)
.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);
String idOrUl = buttonConfig.getUrl() == null ? buttonConfig.getId() : buttonConfig.getUrl();
Button createdButton = Button.of(ButtonStyleConfig.getStyle(buttonConfig.getButtonStyle()), idOrUl, buttonConfig.getLabel());
if (buttonConfig.getDisabled() != null) {
createdButton = createdButton.withDisabled(buttonConfig.getDisabled());
}
if (buttonConfig.getEmoteMarkdown() != null) {
createdButton = createdButton.withEmoji(Emoji.fromFormatted(buttonConfig.getEmoteMarkdown()));
}
if(currentRow == null) {
currentRow = ActionRow.of(createdButton);
} else if (
(
metaConfig != null &&
Boolean.TRUE.equals(metaConfig.getForceNewRow())
)
|| currentRow.getComponents().size() == ComponentServiceBean.MAX_BUTTONS_PER_ROW) {
actionRows.add(currentRow);
currentRow = ActionRow.of(createdButton);
} else {
currentRow.getComponents().add(createdButton);
}
}
if(currentRow != null) {
actionRows.add(currentRow);
}
}
private void convertEmbeds(MessageConfiguration messageConfiguration, List<EmbedBuilder> embedBuilders) {
int currentEffectiveEmbed;
for (int embedIndex = 0; embedIndex < messageConfiguration.getEmbeds().size(); embedIndex++) {