Skip to content

Commit

Permalink
Add support for component-only messages (#2241)
Browse files Browse the repository at this point in the history
  • Loading branch information
MinnDevelopment committed Sep 9, 2022
1 parent 0de645a commit dc07b92
Show file tree
Hide file tree
Showing 7 changed files with 267 additions and 63 deletions.
47 changes: 47 additions & 0 deletions src/main/java/net/dv8tion/jda/api/entities/Message.java
Expand Up @@ -1240,6 +1240,53 @@ default MessageCreateAction replyEmbeds(@Nonnull Collection<? extends MessageEmb
return getChannel().sendMessageEmbeds(embeds).setMessageReference(this);
}

/**
* Shortcut for {@code getChannel().sendMessageComponents(component, other).setMessageReference(this)}.
*
* @param component
* The {@link LayoutComponent} to send
* @param other
* Any addition {@link LayoutComponent LayoutComponents} to send
*
* @throws InsufficientPermissionException
* If {@link MessageChannel#sendMessageComponents(LayoutComponent, LayoutComponent...)} throws
* @throws IllegalArgumentException
* If {@link MessageChannel#sendMessageComponents(LayoutComponent, LayoutComponent...)} throws
*
* @return {@link MessageCreateAction}
*/
@Nonnull
@CheckReturnValue
default MessageCreateAction replyComponents(@Nonnull LayoutComponent component, @Nonnull LayoutComponent... other)
{
Checks.notNull(component, "LayoutComponents");
Checks.noneNull(other, "LayoutComponents");
List<LayoutComponent> 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<? extends LayoutComponent> components)
{
return getChannel().sendMessageComponents(components).setMessageReference(this);
}

/**
* Shortcut for {@code getChannel().sendMessageFormat(format, args).setMessageReference(this)}-
*
Expand Down
91 changes: 91 additions & 0 deletions src/main/java/net/dv8tion/jda/api/entities/MessageChannel.java
Expand Up @@ -506,6 +506,97 @@ default MessageCreateAction sendMessageEmbeds(@Nonnull Collection<? extends Mess
return new MessageCreateActionImpl(this).setEmbeds(embeds);
}

/**
* Send a message to this channel.
*
* <p>Possible {@link net.dv8tion.jda.api.requests.ErrorResponse ErrorResponses} include:
* <ul>
* <li>{@link net.dv8tion.jda.api.requests.ErrorResponse#UNKNOWN_CHANNEL UNKNOWN_CHANNEL}
* <br>if this channel was deleted</li>
*
* <li>{@link net.dv8tion.jda.api.requests.ErrorResponse#CANNOT_SEND_TO_USER CANNOT_SEND_TO_USER}
* <br>If this is a {@link net.dv8tion.jda.api.entities.PrivateChannel PrivateChannel} and the currently logged in account
* does not share any Guilds with the recipient User</li>
* </ul>
*
* @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<LayoutComponent> 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.
*
* <p>Possible {@link net.dv8tion.jda.api.requests.ErrorResponse ErrorResponses} include:
* <ul>
* <li>{@link net.dv8tion.jda.api.requests.ErrorResponse#UNKNOWN_CHANNEL UNKNOWN_CHANNEL}
* <br>if this channel was deleted</li>
*
* <li>{@link net.dv8tion.jda.api.requests.ErrorResponse#CANNOT_SEND_TO_USER CANNOT_SEND_TO_USER}
* <br>If this is a {@link net.dv8tion.jda.api.entities.PrivateChannel PrivateChannel} and the currently logged in account
* does not share any Guilds with the recipient User</li>
* </ul>
*
* <p><b>Example: Attachment Images</b>
* <pre>{@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();
* }</pre>
*
* @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<? extends LayoutComponent> components)
{
return new MessageCreateActionImpl(this).setComponents(components);
}

/**
* Send a message to this channel.
*
Expand Down
56 changes: 56 additions & 0 deletions src/main/java/net/dv8tion/jda/api/entities/WebhookClient.java
Expand Up @@ -213,6 +213,62 @@ default WebhookMessageCreateAction<T> sendMessageEmbeds(@Nonnull MessageEmbed em
return sendMessageEmbeds(embedList);
}

/**
* Send a message to this webhook.
*
* <p>If this is an {@link net.dv8tion.jda.api.interactions.InteractionHook InteractionHook} this method will be delayed until the interaction is acknowledged.
*
* <p>Possible {@link net.dv8tion.jda.api.requests.ErrorResponse ErrorResponses} include:
* <ul>
* <li>{@link net.dv8tion.jda.api.requests.ErrorResponse#UNKNOWN_WEBHOOK UNKNOWN_WEBHOOK}
* <br>The webhook is no longer available, either it was deleted or in case of interactions it expired.</li>
* </ul>
*
* @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<T> sendMessageComponents(@Nonnull Collection<? extends LayoutComponent> components);

/**
* Send a message to this webhook.
*
* <p>If this is an {@link net.dv8tion.jda.api.interactions.InteractionHook InteractionHook} this method will be delayed until the interaction is acknowledged.
*
* <p>Possible {@link net.dv8tion.jda.api.requests.ErrorResponse ErrorResponses} include:
* <ul>
* <li>{@link net.dv8tion.jda.api.requests.ErrorResponse#UNKNOWN_WEBHOOK UNKNOWN_WEBHOOK}
* <br>The webhook is no longer available, either it was deleted or in case of interactions it expired.</li>
* </ul>
*
* @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<T> sendMessageComponents(@Nonnull LayoutComponent component, @Nonnull LayoutComponent... other)
{
Checks.notNull(component, "LayoutComponents");
Checks.noneNull(other, "LayoutComponents");
List<LayoutComponent> embedList = new ArrayList<>();
embedList.add(component);
Collections.addAll(embedList, other);
return sendMessageComponents(embedList);
}

/**
* Send a message to this webhook.
*
Expand Down
Expand Up @@ -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;
Expand All @@ -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.
Expand Down Expand Up @@ -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.
// * <br>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.
// *
// * <p><b>You only have 3 seconds to acknowledge an interaction!</b>
// * <br>When the acknowledgement is sent after the interaction expired, you will receive {@link net.dv8tion.jda.api.requests.ErrorResponse#UNKNOWN_INTERACTION ErrorResponse.UNKNOWN_INTERACTION}.
// * <p>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<? extends LayoutComponent> components)
// {
// if (components.stream().anyMatch(it -> !(it instanceof ActionRow)))
// throw new UnsupportedOperationException("Only ActionRow layouts are currently supported.");
// List<ActionRow> rows = components.stream()
// .map(ActionRow.class::cast)
// .collect(Collectors.toList());
// return deferReply().addActionRows(rows);
// }
//
// /**
// * Reply to this interaction and acknowledge it.
// * <br>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.
// *
// * <p><b>You only have 3 seconds to acknowledge an interaction!</b>
// * <br>When the acknowledgement is sent after the interaction expired, you will receive {@link net.dv8tion.jda.api.requests.ErrorResponse#UNKNOWN_INTERACTION ErrorResponse.UNKNOWN_INTERACTION}.
// * <p>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<LayoutComponent> layouts = new ArrayList<>();
// layouts.add(component);
// Collections.addAll(layouts, components);
// return replyComponents(layouts);
// }
/**
* Reply to this interaction and acknowledge it.
* <br>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.
*
* <p><b>You only have 3 seconds to acknowledge an interaction!</b>
* <br>When the acknowledgement is sent after the interaction expired, you will receive {@link net.dv8tion.jda.api.requests.ErrorResponse#UNKNOWN_INTERACTION ErrorResponse.UNKNOWN_INTERACTION}.
* <p>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<? extends LayoutComponent> components)
{
return deferReply().setComponents(components);
}

/**
* Reply to this interaction and acknowledge it.
* <br>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.
*
* <p><b>You only have 3 seconds to acknowledge an interaction!</b>
* <br>When the acknowledgement is sent after the interaction expired, you will receive {@link net.dv8tion.jda.api.requests.ErrorResponse#UNKNOWN_INTERACTION ErrorResponse.UNKNOWN_INTERACTION}.
* <p>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<LayoutComponent> layouts = new ArrayList<>(1 + other.length);
layouts.add(component);
Collections.addAll(layouts, other);
return replyComponents(layouts);
}

/**
* Reply to this interaction and acknowledge it.
Expand Down
Expand Up @@ -201,7 +201,7 @@ public boolean isSuppressEmbeds()

/**
* Whether this builder is considered empty, this checks for all <em>required</em> fields of the request type.
* <br>On a create request, this checks for {@link #setContent(String) content}, {@link #setEmbeds(Collection) embeds}, and {@link #setFiles(Collection) files}.
* <br>On a create request, this checks for {@link #setContent(String) content}, {@link #setEmbeds(Collection) embeds}, {@link #setComponents(Collection) components}, and {@link #setFiles(Collection) files}.
* <br>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
Expand Down
Expand Up @@ -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
Expand All @@ -232,8 +232,8 @@ public MessageCreateData build()
List<LayoutComponent> 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)
Expand Down

0 comments on commit dc07b92

Please sign in to comment.