mirror of
https://github.com/Sheldan/abstracto.git
synced 2026-01-26 05:44:42 +00:00
[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:
@@ -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");
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -17,4 +17,5 @@ public class ButtonConfig {
|
||||
private String buttonPayload;
|
||||
private String payloadType;
|
||||
private ButtonMetaConfig metaConfig;
|
||||
private Integer position;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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++) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package dev.sheldan.abstracto.core.interaction;
|
||||
|
||||
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;
|
||||
@@ -14,6 +15,8 @@ public interface ComponentPayloadManagementService {
|
||||
void updatePayload(String id, String payload);
|
||||
ComponentPayload createButtonPayload(ButtonConfigModel buttonConfigModel, AServer server);
|
||||
ComponentPayload createButtonPayload(ButtonConfigModel buttonConfigModel, Long serverId);
|
||||
ComponentPayload createStringSelectMenuPayload(SelectMenuConfigModel selectMenuConfigModel, Long serverId);
|
||||
ComponentPayload createStringSelectMenuPayload(SelectMenuConfigModel selectMenuConfigModel, AServer server);
|
||||
ComponentPayload createModalPayload(ModalConfigPayload payloadConfig, Long serverId);
|
||||
Optional<ComponentPayload> findPayload(String id);
|
||||
List<ComponentPayload> findPayloadsOfOriginInServer(String buttonOrigin, AServer server);
|
||||
|
||||
@@ -12,9 +12,10 @@ public interface InteractionService {
|
||||
List<CompletableFuture<Message>> sendMessageToInteraction(MessageToSend messageToSend, InteractionHook interactionHook);
|
||||
List<CompletableFuture<Message>> sendMessageToInteraction(String templateKey, Object model, InteractionHook interactionHook);
|
||||
CompletableFuture<InteractionHook> replyEmbed(String templateKey, Object model, IReplyCallback callback);
|
||||
CompletableFuture<InteractionHook> replyString(String text, IReplyCallback callback);
|
||||
CompletableFuture<InteractionHook> replyString(String text, IReplyCallback callback);
|
||||
CompletableFuture<InteractionHook> replyEmbed(String templateKey, IReplyCallback callback);
|
||||
CompletableFuture<Message> editOriginal(MessageToSend messageToSend, InteractionHook interactionHook);
|
||||
CompletableFuture<InteractionHook> replyMessageToSend(MessageToSend messageToSend, IReplyCallback callback);
|
||||
CompletableFuture<InteractionHook> replyMessage(String templateKey, Object model, IReplyCallback callback);
|
||||
CompletableFuture<Message> replyString(String content, InteractionHook interactionHook);
|
||||
}
|
||||
|
||||
@@ -6,4 +6,7 @@ import dev.sheldan.abstracto.core.listener.FeatureAwareListener;
|
||||
|
||||
public interface ButtonClickedListener extends FeatureAwareListener<ButtonClickedListenerModel, ButtonClickedListenerResult>, Prioritized, InteractionListener {
|
||||
Boolean handlesEvent(ButtonClickedListenerModel model);
|
||||
default Boolean autoAcknowledgeEvent() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package dev.sheldan.abstracto.core.interaction.menu;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
public class SelectMenuConfigModel {
|
||||
private String selectMenuId;
|
||||
private SelectMenuPayload selectMenuPayload;
|
||||
private Class payloadType;
|
||||
private String origin;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package dev.sheldan.abstracto.core.interaction.menu;
|
||||
|
||||
public interface SelectMenuPayload {
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package dev.sheldan.abstracto.core.interaction.menu.listener;
|
||||
|
||||
import dev.sheldan.abstracto.core.Prioritized;
|
||||
import dev.sheldan.abstracto.core.interaction.InteractionListener;
|
||||
import dev.sheldan.abstracto.core.listener.FeatureAwareListener;
|
||||
|
||||
public interface StringSelectMenuListener extends FeatureAwareListener<StringSelectMenuListenerModel, StringSelectMenuListenerResult>, Prioritized, InteractionListener {
|
||||
Boolean handlesEvent(StringSelectMenuListenerModel model);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package dev.sheldan.abstracto.core.interaction.menu.listener;
|
||||
|
||||
import dev.sheldan.abstracto.core.interaction.menu.SelectMenuPayload;
|
||||
import dev.sheldan.abstracto.core.listener.FeatureAwareListenerModel;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import net.dv8tion.jda.api.events.interaction.component.StringSelectInteractionEvent;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
public class StringSelectMenuListenerModel implements FeatureAwareListenerModel {
|
||||
|
||||
private StringSelectInteractionEvent event;
|
||||
private String payload;
|
||||
private String origin;
|
||||
private SelectMenuPayload deserializedPayload;
|
||||
|
||||
@Override
|
||||
public Long getServerId() {
|
||||
return event.isFromGuild() ? event.getGuild().getIdLong() : null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package dev.sheldan.abstracto.core.interaction.menu.listener;
|
||||
|
||||
import dev.sheldan.abstracto.core.listener.ListenerExecutionResult;
|
||||
|
||||
public enum StringSelectMenuListenerResult implements ListenerExecutionResult {
|
||||
ACKNOWLEDGED, IGNORED
|
||||
}
|
||||
@@ -1,11 +1,13 @@
|
||||
package dev.sheldan.abstracto.core.interaction.modal;
|
||||
|
||||
import net.dv8tion.jda.api.events.interaction.command.GenericCommandInteractionEvent;
|
||||
import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent;
|
||||
import net.dv8tion.jda.api.interactions.modals.Modal;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public interface ModalService {
|
||||
CompletableFuture<Void> replyModal(GenericCommandInteractionEvent event, String templateKey, Object model);
|
||||
CompletableFuture<Void> replyModal(ButtonInteractionEvent event, String templateKey, Object model);
|
||||
Modal createModalFromTemplate(String templateKey, Object model, Long serverId);
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ public interface ChannelService {
|
||||
CompletableFuture<Message> removeFieldFromMessage(MessageChannel channel, Long messageId, Integer index);
|
||||
CompletableFuture<Message> editFieldValueInMessage(MessageChannel channel, Long messageId, Integer index, String newValue);
|
||||
CompletableFuture<Message> removeFieldFromMessage(MessageChannel channel, Long messageId, Integer index, Integer embedIndex);
|
||||
CompletableFuture<Message> removeComponents(MessageChannel channel, Long messageId);
|
||||
CompletableFuture<Void> deleteTextChannel(AChannel channel);
|
||||
CompletableFuture<Void> deleteTextChannel(Long serverId, Long channelId);
|
||||
List<CompletableFuture<Message>> sendEmbedTemplateInTextChannelList(String templateKey, Object model, MessageChannel channel);
|
||||
|
||||
@@ -20,6 +20,10 @@ public interface PostTargetManagement {
|
||||
Optional<PostTarget> getPostTargetOptional(PostTargetEnum postTargetEnum, Long serverId);
|
||||
Boolean postTargetExists(String name, AServer server);
|
||||
boolean postTargetExists(String name, Long serverId);
|
||||
AChannel getPostTarget(Long serverId, String name);
|
||||
AChannel getPostTarget(Long serverId, PostTarget target);
|
||||
AChannel getPostTarget(AServer server, PostTarget target);
|
||||
AChannel getPostTarget(AServer server, String name);
|
||||
PostTarget updatePostTarget(PostTarget target, AChannel newTargetChannel);
|
||||
List<PostTarget> getPostTargetsInServer(AServer server);
|
||||
}
|
||||
|
||||
@@ -15,9 +15,5 @@ public interface ServerManagementService {
|
||||
void addChannelToServer(AServer server, AChannel channel);
|
||||
AUserInAServer addUserToServer(AServer server, AUser user);
|
||||
AUserInAServer addUserToServer(Long serverId, Long userId);
|
||||
AChannel getPostTarget(Long serverId, String name);
|
||||
AChannel getPostTarget(Long serverId, PostTarget target);
|
||||
AChannel getPostTarget(AServer server, PostTarget target);
|
||||
AChannel getPostTarget(AServer server, String name);
|
||||
List<AServer> getAllServers();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user