From dc07b9287a414f70f1d0f89e23d3f927ee1576d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Spie=C3=9F?= Date: Fri, 9 Sep 2022 20:54:51 +0200 Subject: [PATCH] Add support for component-only messages (#2241) --- .../net/dv8tion/jda/api/entities/Message.java | 47 +++++++ .../jda/api/entities/MessageChannel.java | 91 +++++++++++++ .../jda/api/entities/WebhookClient.java | 56 ++++++++ .../callbacks/IReplyCallback.java | 120 +++++++++--------- .../messages/AbstractMessageBuilder.java | 2 +- .../utils/messages/MessageCreateBuilder.java | 6 +- .../entities/AbstractWebhookClient.java | 8 ++ 7 files changed, 267 insertions(+), 63 deletions(-) diff --git a/src/main/java/net/dv8tion/jda/api/entities/Message.java b/src/main/java/net/dv8tion/jda/api/entities/Message.java index 6b9225173a..4803ab60d0 100644 --- a/src/main/java/net/dv8tion/jda/api/entities/Message.java +++ b/src/main/java/net/dv8tion/jda/api/entities/Message.java @@ -1240,6 +1240,53 @@ default MessageCreateAction replyEmbeds(@Nonnull Collection components = new ArrayList<>(1 + other.length); + components.add(component); + Collections.addAll(components, other); + return replyComponents(components); + } + + /** + * Shortcut for {@code getChannel().sendMessageComponents(components).setMessageReference(this)}. + * + * @param components + * The {@link LayoutComponent LayoutComponents} to send + * + * @throws InsufficientPermissionException + * If {@link MessageChannel#sendMessageComponents(Collection)} throws + * @throws IllegalArgumentException + * If {@link MessageChannel#sendMessageComponents(Collection)} throws + * + * @return {@link MessageCreateAction} + */ + @Nonnull + @CheckReturnValue + default MessageCreateAction replyComponents(@Nonnull Collection components) + { + return getChannel().sendMessageComponents(components).setMessageReference(this); + } + /** * Shortcut for {@code getChannel().sendMessageFormat(format, args).setMessageReference(this)}- * diff --git a/src/main/java/net/dv8tion/jda/api/entities/MessageChannel.java b/src/main/java/net/dv8tion/jda/api/entities/MessageChannel.java index d4d5eee6e7..a9e09eebf7 100644 --- a/src/main/java/net/dv8tion/jda/api/entities/MessageChannel.java +++ b/src/main/java/net/dv8tion/jda/api/entities/MessageChannel.java @@ -506,6 +506,97 @@ default MessageCreateAction sendMessageEmbeds(@Nonnull CollectionPossible {@link net.dv8tion.jda.api.requests.ErrorResponse ErrorResponses} include: + * + * + * @param component + * {@link LayoutComponent} to send + * @param other + * Additional {@link LayoutComponent LayoutComponents} to use (up to {@value Message#MAX_COMPONENT_COUNT}) + * + * @throws UnsupportedOperationException + * If this is a {@link PrivateChannel} and the recipient is a bot + * @throws IllegalArgumentException + * If any of the components is null or more than {@value Message#MAX_COMPONENT_COUNT} component layouts are provided + * @throws net.dv8tion.jda.api.exceptions.InsufficientPermissionException + * If this is a {@link net.dv8tion.jda.api.entities.GuildMessageChannel GuildMessageChannel} and this account does not have + * {@link net.dv8tion.jda.api.Permission#VIEW_CHANNEL Permission.VIEW_CHANNEL} or {@link net.dv8tion.jda.api.Permission#MESSAGE_SEND Permission.MESSAGE_SEND} + * + * @return {@link MessageCreateAction} + */ + @Nonnull + @CheckReturnValue + default MessageCreateAction sendMessageComponents(@Nonnull LayoutComponent component, @Nonnull LayoutComponent... other) + { + Checks.notNull(component, "LayoutComponents"); + Checks.noneNull(other, "LayoutComponents"); + List components = new ArrayList<>(1 + other.length); + components.add(component); + Collections.addAll(components, other); + return new MessageCreateActionImpl(this).setComponents(components); + } + + /** + * Send a message to this channel. + * + *

Possible {@link net.dv8tion.jda.api.requests.ErrorResponse ErrorResponses} include: + *

+ * + *

Example: Attachment Images + *

{@code
+     * // Make a file upload instance which refers to a local file called "myFile.png"
+     * // The second parameter "image.png" is the filename we tell discord to use for the attachment
+     * FileUpload file = FileUpload.fromData(new File("myFile.png"), "image.png");
+     *
+     * // Build a message embed which refers to this attachment by the given name.
+     * // Note that this must be the same name as configured for the attachment, not your local filename.
+     * MessageEmbed embed = new EmbedBuilder()
+     *   .setDescription("This is my cute cat :)")
+     *   .setImage("attachment://image.png") // refer to the file by using the "attachment://" schema with the filename we gave it above
+     *   .build();
+     *
+     * channel.sendMessageEmbeds(Collections.singleton(embed)) // send the embeds
+     *        .addFiles(file) // add the file as attachment
+     *        .queue();
+     * }
+ * + * @param components + * {@link LayoutComponent LayoutComponents} to use (up to {@value Message#MAX_COMPONENT_COUNT}) + * + * @throws UnsupportedOperationException + * If this is a {@link PrivateChannel} and the recipient is a bot + * @throws IllegalArgumentException + * If any of the components is null or more than {@value Message#MAX_COMPONENT_COUNT} component layouts are provided + * @throws net.dv8tion.jda.api.exceptions.InsufficientPermissionException + * If this is a {@link net.dv8tion.jda.api.entities.GuildMessageChannel GuildMessageChannel} and this account does not have + * {@link net.dv8tion.jda.api.Permission#VIEW_CHANNEL Permission.VIEW_CHANNEL} or {@link net.dv8tion.jda.api.Permission#MESSAGE_SEND Permission.MESSAGE_SEND} + * + * @return {@link MessageCreateAction} + */ + @Nonnull + @CheckReturnValue + default MessageCreateAction sendMessageComponents(@Nonnull Collection components) + { + return new MessageCreateActionImpl(this).setComponents(components); + } + /** * Send a message to this channel. * diff --git a/src/main/java/net/dv8tion/jda/api/entities/WebhookClient.java b/src/main/java/net/dv8tion/jda/api/entities/WebhookClient.java index ebf501eba8..860f32d9b8 100644 --- a/src/main/java/net/dv8tion/jda/api/entities/WebhookClient.java +++ b/src/main/java/net/dv8tion/jda/api/entities/WebhookClient.java @@ -213,6 +213,62 @@ default WebhookMessageCreateAction sendMessageEmbeds(@Nonnull MessageEmbed em return sendMessageEmbeds(embedList); } + /** + * Send a message to this webhook. + * + *

If this is an {@link net.dv8tion.jda.api.interactions.InteractionHook InteractionHook} this method will be delayed until the interaction is acknowledged. + * + *

Possible {@link net.dv8tion.jda.api.requests.ErrorResponse ErrorResponses} include: + *

    + *
  • {@link net.dv8tion.jda.api.requests.ErrorResponse#UNKNOWN_WEBHOOK UNKNOWN_WEBHOOK} + *
    The webhook is no longer available, either it was deleted or in case of interactions it expired.
  • + *
+ * + * @param components + * {@link LayoutComponent LayoutComponents} to use (up to {@value Message#MAX_COMPONENT_COUNT}) + * + * @throws IllegalArgumentException + * If any of the components are null or more than {@value Message#MAX_COMPONENT_COUNT} component layouts are provided + * + * @return {@link net.dv8tion.jda.api.requests.restaction.WebhookMessageCreateAction} + */ + @Nonnull + @CheckReturnValue + WebhookMessageCreateAction sendMessageComponents(@Nonnull Collection components); + + /** + * Send a message to this webhook. + * + *

If this is an {@link net.dv8tion.jda.api.interactions.InteractionHook InteractionHook} this method will be delayed until the interaction is acknowledged. + * + *

Possible {@link net.dv8tion.jda.api.requests.ErrorResponse ErrorResponses} include: + *

    + *
  • {@link net.dv8tion.jda.api.requests.ErrorResponse#UNKNOWN_WEBHOOK UNKNOWN_WEBHOOK} + *
    The webhook is no longer available, either it was deleted or in case of interactions it expired.
  • + *
+ * + * @param component + * {@link LayoutComponent} to use + * @param other + * Additional {@link LayoutComponent LayoutComponents} to use (up to {@value Message#MAX_COMPONENT_COUNT} in total) + * + * @throws IllegalArgumentException + * If any of the components are null or more than {@value Message#MAX_COMPONENT_COUNT} component layouts are provided + * + * @return {@link net.dv8tion.jda.api.requests.restaction.WebhookMessageCreateAction} + */ + @Nonnull + @CheckReturnValue + default WebhookMessageCreateAction sendMessageComponents(@Nonnull LayoutComponent component, @Nonnull LayoutComponent... other) + { + Checks.notNull(component, "LayoutComponents"); + Checks.noneNull(other, "LayoutComponents"); + List embedList = new ArrayList<>(); + embedList.add(component); + Collections.addAll(embedList, other); + return sendMessageComponents(embedList); + } + /** * Send a message to this webhook. * diff --git a/src/main/java/net/dv8tion/jda/api/interactions/callbacks/IReplyCallback.java b/src/main/java/net/dv8tion/jda/api/interactions/callbacks/IReplyCallback.java index ac9392cfed..e74fefe044 100644 --- a/src/main/java/net/dv8tion/jda/api/interactions/callbacks/IReplyCallback.java +++ b/src/main/java/net/dv8tion/jda/api/interactions/callbacks/IReplyCallback.java @@ -19,6 +19,8 @@ import net.dv8tion.jda.api.entities.Message; import net.dv8tion.jda.api.entities.MessageEmbed; import net.dv8tion.jda.api.interactions.InteractionHook; +import net.dv8tion.jda.api.interactions.components.ActionRow; +import net.dv8tion.jda.api.interactions.components.LayoutComponent; import net.dv8tion.jda.api.requests.RestAction; import net.dv8tion.jda.api.requests.restaction.interactions.ReplyCallbackAction; import net.dv8tion.jda.api.utils.FileUpload; @@ -30,7 +32,10 @@ import javax.annotation.Nonnull; import java.io.File; import java.io.InputStream; +import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; +import java.util.List; /** * Interactions which allow message replies in the channel they were used in. @@ -209,65 +214,62 @@ default ReplyCallbackAction replyEmbeds(@Nonnull MessageEmbed embed, @Nonnull Me return deferReply().addEmbeds(embed).addEmbeds(embeds); } -// /** -// * Reply to this interaction and acknowledge it. -// *
This will send a reply message for this interaction. -// * You can use {@link ReplyAction#setEphemeral(boolean) setEphemeral(true)} to only let the target user see the message. -// * Replies are non-ephemeral by default. -// * -// *

You only have 3 seconds to acknowledge an interaction! -// *
When the acknowledgement is sent after the interaction expired, you will receive {@link net.dv8tion.jda.api.requests.ErrorResponse#UNKNOWN_INTERACTION ErrorResponse.UNKNOWN_INTERACTION}. -// *

If your handling can take longer than 3 seconds, due to various rate limits or other conditions, you should use {@link #deferReply()} instead. -// * -// * @param components -// * The {@link LayoutComponent LayoutComponents} to send, such as {@link ActionRow} -// * -// * @throws IllegalArgumentException -// * If null is provided -// * -// * @return {@link ReplyAction} -// */ -// @Nonnull -// @CheckReturnValue -// default ReplyAction replyComponents(@Nonnull Collection components) -// { -// if (components.stream().anyMatch(it -> !(it instanceof ActionRow))) -// throw new UnsupportedOperationException("Only ActionRow layouts are currently supported."); -// List rows = components.stream() -// .map(ActionRow.class::cast) -// .collect(Collectors.toList()); -// return deferReply().addActionRows(rows); -// } -// -// /** -// * Reply to this interaction and acknowledge it. -// *
This will send a reply message for this interaction. -// * You can use {@link ReplyAction#setEphemeral(boolean) setEphemeral(true)} to only let the target user see the message. -// * Replies are non-ephemeral by default. -// * -// *

You only have 3 seconds to acknowledge an interaction! -// *
When the acknowledgement is sent after the interaction expired, you will receive {@link net.dv8tion.jda.api.requests.ErrorResponse#UNKNOWN_INTERACTION ErrorResponse.UNKNOWN_INTERACTION}. -// *

If your handling can take longer than 3 seconds, due to various rate limits or other conditions, you should use {@link #deferReply()} instead. -// * -// * @param components -// * The {@link LayoutComponent LayoutComponents} to send, such as {@link ActionRow} -// * -// * @throws IllegalArgumentException -// * If null is provided -// * -// * @return {@link ReplyAction} -// */ -// @Nonnull -// @CheckReturnValue -// default ReplyAction replyComponents(@Nonnull LayoutComponent component, @Nonnull LayoutComponent... components) -// { -// Checks.notNull(component, "LayoutComponents"); -// Checks.noneNull(components, "LayoutComponents"); -// List layouts = new ArrayList<>(); -// layouts.add(component); -// Collections.addAll(layouts, components); -// return replyComponents(layouts); -// } + /** + * Reply to this interaction and acknowledge it. + *
This will send a reply message for this interaction. + * You can use {@link ReplyCallbackAction#setEphemeral(boolean) setEphemeral(true)} to only let the target user see the message. + * Replies are non-ephemeral by default. + * + *

You only have 3 seconds to acknowledge an interaction! + *
When the acknowledgement is sent after the interaction expired, you will receive {@link net.dv8tion.jda.api.requests.ErrorResponse#UNKNOWN_INTERACTION ErrorResponse.UNKNOWN_INTERACTION}. + *

If your handling can take longer than 3 seconds, due to various rate limits or other conditions, you should use {@link #deferReply()} instead. + * + * @param components + * The {@link LayoutComponent LayoutComponents} to send, such as {@link ActionRow} + * + * @throws IllegalArgumentException + * If null is provided or more than {@value Message#MAX_COMPONENT_COUNT} component layouts are provided + * + * @return {@link ReplyCallbackAction} + */ + @Nonnull + @CheckReturnValue + default ReplyCallbackAction replyComponents(@Nonnull Collection components) + { + return deferReply().setComponents(components); + } + + /** + * Reply to this interaction and acknowledge it. + *
This will send a reply message for this interaction. + * You can use {@link ReplyCallbackAction#setEphemeral(boolean) setEphemeral(true)} to only let the target user see the message. + * Replies are non-ephemeral by default. + * + *

You only have 3 seconds to acknowledge an interaction! + *
When the acknowledgement is sent after the interaction expired, you will receive {@link net.dv8tion.jda.api.requests.ErrorResponse#UNKNOWN_INTERACTION ErrorResponse.UNKNOWN_INTERACTION}. + *

If your handling can take longer than 3 seconds, due to various rate limits or other conditions, you should use {@link #deferReply()} instead. + * + * @param component + * The {@link LayoutComponent} to send + * @param other + * Any addition {@link LayoutComponent LayoutComponents} to send + * + * @throws IllegalArgumentException + * If null is provided or more than {@value Message#MAX_COMPONENT_COUNT} component layouts are provided + * + * @return {@link ReplyCallbackAction} + */ + @Nonnull + @CheckReturnValue + default ReplyCallbackAction replyComponents(@Nonnull LayoutComponent component, @Nonnull LayoutComponent... other) + { + Checks.notNull(component, "LayoutComponents"); + Checks.noneNull(other, "LayoutComponents"); + List layouts = new ArrayList<>(1 + other.length); + layouts.add(component); + Collections.addAll(layouts, other); + return replyComponents(layouts); + } /** * Reply to this interaction and acknowledge it. diff --git a/src/main/java/net/dv8tion/jda/api/utils/messages/AbstractMessageBuilder.java b/src/main/java/net/dv8tion/jda/api/utils/messages/AbstractMessageBuilder.java index c7e0a93bfa..dcd119e946 100644 --- a/src/main/java/net/dv8tion/jda/api/utils/messages/AbstractMessageBuilder.java +++ b/src/main/java/net/dv8tion/jda/api/utils/messages/AbstractMessageBuilder.java @@ -201,7 +201,7 @@ public boolean isSuppressEmbeds() /** * Whether this builder is considered empty, this checks for all required fields of the request type. - *
On a create request, this checks for {@link #setContent(String) content}, {@link #setEmbeds(Collection) embeds}, and {@link #setFiles(Collection) files}. + *
On a create request, this checks for {@link #setContent(String) content}, {@link #setEmbeds(Collection) embeds}, {@link #setComponents(Collection) components}, and {@link #setFiles(Collection) files}. *
An edit request is only considered empty if no setters were called. And never empty, if the builder is a {@link MessageEditRequest#setReplace(boolean) replace request}. * * @return True, if the builder state is empty diff --git a/src/main/java/net/dv8tion/jda/api/utils/messages/MessageCreateBuilder.java b/src/main/java/net/dv8tion/jda/api/utils/messages/MessageCreateBuilder.java index cd9ff43ff9..d70374e520 100644 --- a/src/main/java/net/dv8tion/jda/api/utils/messages/MessageCreateBuilder.java +++ b/src/main/java/net/dv8tion/jda/api/utils/messages/MessageCreateBuilder.java @@ -211,7 +211,7 @@ public MessageCreateBuilder setTTS(boolean tts) @Override public boolean isEmpty() { - return Helpers.isBlank(content) && embeds.isEmpty() && files.isEmpty(); + return Helpers.isBlank(content) && embeds.isEmpty() && files.isEmpty() && components.isEmpty(); } @Override @@ -232,8 +232,8 @@ public MessageCreateData build() List components = new ArrayList<>(this.components); AllowedMentionsData mentions = this.mentions.copy(); - if (content.isEmpty() && embeds.isEmpty() && files.isEmpty()) - throw new IllegalStateException("Cannot build an empty message. You need at least one of content, embeds, or files"); + if (content.isEmpty() && embeds.isEmpty() && files.isEmpty() && components.isEmpty()) + throw new IllegalStateException("Cannot build an empty message. You need at least one of content, embeds, components, or files"); int length = Helpers.codePointLength(content); if (length > Message.MAX_CONTENT_LENGTH) diff --git a/src/main/java/net/dv8tion/jda/internal/entities/AbstractWebhookClient.java b/src/main/java/net/dv8tion/jda/internal/entities/AbstractWebhookClient.java index d936c59052..f15e1dac33 100644 --- a/src/main/java/net/dv8tion/jda/internal/entities/AbstractWebhookClient.java +++ b/src/main/java/net/dv8tion/jda/internal/entities/AbstractWebhookClient.java @@ -33,6 +33,7 @@ import net.dv8tion.jda.internal.requests.restaction.WebhookMessageCreateActionImpl; import net.dv8tion.jda.internal.requests.restaction.WebhookMessageEditActionImpl; import net.dv8tion.jda.internal.utils.Checks; +import org.jetbrains.annotations.NotNull; import javax.annotation.Nonnull; import java.util.Collection; @@ -69,6 +70,13 @@ public WebhookMessageCreateAction sendMessageEmbeds(@Nonnull Collection sendMessageComponents(@NotNull Collection components) + { + return sendRequest().setComponents(components); + } + @Nonnull @Override public WebhookMessageCreateAction sendMessage(@Nonnull MessageCreateData message)