diff --git a/build.gradle.kts b/build.gradle.kts
index 217f258..29fb88c 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -45,7 +45,7 @@ repositories {
val versions = mapOf(
"slf4j" to "1.7.32",
"okhttp" to "4.10.0",
- "json" to "20210307",
+ "json" to "20220924",
"jda" to "5.0.0-alpha.13",
"discord4j" to "3.2.2",
"javacord" to "3.4.0",
diff --git a/src/main/java/club/minnced/discord/webhook/DiscordEmoji.java b/src/main/java/club/minnced/discord/webhook/DiscordEmoji.java
new file mode 100644
index 0000000..d3b76b1
--- /dev/null
+++ b/src/main/java/club/minnced/discord/webhook/DiscordEmoji.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2018-2020 Florian Spieß
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package club.minnced.discord.webhook;
+
+import org.jetbrains.annotations.NotNull;
+import org.json.JSONObject;
+import org.json.JSONString;
+
+import java.util.Objects;
+
+/**
+ * Emoji class used for components.
+ *
+ *
The {@link #toString()} method returns the message content serialized format, e.g. {@code <:name:id>} for custom emoji.
+ */
+public class DiscordEmoji implements JSONString {
+ private final String name;
+ private final long id;
+ private final boolean animated;
+
+ private DiscordEmoji(@NotNull String name, long id, boolean animated) {
+ this.name = name;
+ this.id = id;
+ this.animated = animated;
+ }
+
+ /**
+ * Emoji instance for custom emoji, with an id and optional animations.
+ * Webhooks are limited to emoji from the same guild.
+ *
+ * @param name
+ * the name of the emoji
+ * @param id
+ * the id of the emoji
+ * @param animated
+ * true if the emoji is animated
+ *
+ * @throws java.lang.NullPointerException
+ * If null is provided
+ *
+ * @return An instance of emoji, usable in components
+ */
+ @NotNull
+ public static DiscordEmoji custom(@NotNull String name, long id, boolean animated) {
+ return new DiscordEmoji(Objects.requireNonNull(name), id, animated);
+ }
+
+ /**
+ * Emoji instance for unicode codepoints.
+ * This does not support aliases such as {@code :smiley:}.
+ * You must use the correct unicode characters.
+ *
+ * @param codepoints
+ * The unicode codepoints
+ *
+ * @return An instance of emoji, usable in components
+ */
+ @NotNull
+ public static DiscordEmoji unicode(@NotNull String codepoints) {
+ return new DiscordEmoji(Objects.requireNonNull(codepoints), 0, false);
+ }
+
+ /**
+ * The emoji name, or unicode codepoints
+ *
+ * @return The name of the emoji
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * The snowflake id of the custom emoji, or {@code 0} for unicode
+ *
+ * @return The snowflake id of the emoji
+ */
+ public long getId() {
+ return id;
+ }
+
+ /**
+ * Whether this is an animated custom emoji.
+ *
+ * @return true if the emoji is animated
+ */
+ public boolean isAnimated() {
+ return animated;
+ }
+
+ @Override
+ public String toJSONString() {
+ JSONObject json = new JSONObject();
+ json.put("name", this.name);
+ if (id != 0) {
+ json.put("id", this.id);
+ json.put("animated", this.animated);
+ }
+ return json.toString();
+ }
+
+ @Override
+ public String toString() {
+ if (id == 0)
+ return name;
+ else
+ return (animated ? "";
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/club/minnced/discord/webhook/WebhookClient.java b/src/main/java/club/minnced/discord/webhook/WebhookClient.java
index 00c110d..a4db7f5 100644
--- a/src/main/java/club/minnced/discord/webhook/WebhookClient.java
+++ b/src/main/java/club/minnced/discord/webhook/WebhookClient.java
@@ -23,6 +23,7 @@
import club.minnced.discord.webhook.send.WebhookEmbed;
import club.minnced.discord.webhook.send.WebhookMessage;
import club.minnced.discord.webhook.send.WebhookMessageBuilder;
+import club.minnced.discord.webhook.send.component.ComponentLayout;
import club.minnced.discord.webhook.util.ThreadPools;
import club.minnced.discord.webhook.util.WebhookErrorHandler;
import okhttp3.OkHttpClient;
@@ -406,8 +407,11 @@ public CompletableFuture send(@NotNull InputStream data, @NotNu
*
* @see #isWait()
* @see #send(club.minnced.discord.webhook.send.WebhookMessage)
+ *
+ * @deprecated Use {@link #sendEmbeds(club.minnced.discord.webhook.send.WebhookEmbed, club.minnced.discord.webhook.send.WebhookEmbed...)} instead
*/
@NotNull
+ @Deprecated
public CompletableFuture send(@NotNull WebhookEmbed first, @NotNull WebhookEmbed... embeds) {
return send(WebhookMessage.embeds(first, embeds));
}
@@ -424,12 +428,91 @@ public CompletableFuture send(@NotNull WebhookEmbed first, @Not
*
* @see #isWait()
* @see #send(club.minnced.discord.webhook.send.WebhookMessage)
+ *
+ * @deprecated Use {@link #sendEmbeds(java.util.Collection)} instead
*/
@NotNull
+ @Deprecated
public CompletableFuture send(@NotNull Collection embeds) {
return send(WebhookMessage.embeds(embeds));
}
+ /**
+ * Sends the provided {@link club.minnced.discord.webhook.send.WebhookEmbed} to the webhook.
+ * The returned future receives {@code null} if {@link club.minnced.discord.webhook.WebhookClientBuilder#setWait(boolean)}
+ * was set to false.
+ *
+ * @param first
+ * The first embed to send
+ * @param embeds
+ * Optional additional embeds to send, up to 10
+ *
+ * @return {@link java.util.concurrent.CompletableFuture}
+ *
+ * @see #isWait()
+ * @see #send(club.minnced.discord.webhook.send.WebhookMessage)
+ */
+ @NotNull
+ public CompletableFuture sendEmbeds(@NotNull WebhookEmbed first, @NotNull WebhookEmbed... embeds) {
+ return send(WebhookMessage.embeds(first, embeds));
+ }
+
+ /**
+ * Sends the provided {@link club.minnced.discord.webhook.send.WebhookEmbed} to the webhook.
+ * The returned future receives {@code null} if {@link club.minnced.discord.webhook.WebhookClientBuilder#setWait(boolean)}
+ * was set to false.
+ *
+ * @param embeds
+ * The embeds to send
+ *
+ * @return {@link java.util.concurrent.CompletableFuture}
+ *
+ * @see #isWait()
+ * @see #send(club.minnced.discord.webhook.send.WebhookMessage)
+ */
+ @NotNull
+ public CompletableFuture sendEmbeds(@NotNull Collection embeds) {
+ return send(WebhookMessage.embeds(embeds));
+ }
+
+ /**
+ * Sends the provided {@link club.minnced.discord.webhook.send.component.ComponentLayout} to the webhook.
+ * The returned future receives {@code null} if {@link club.minnced.discord.webhook.WebhookClientBuilder#setWait(boolean)}
+ * was set to false.
+ *
+ * @param first
+ * The first embed to send
+ * @param components
+ * Optional additional components to send, up to 5
+ *
+ * @return {@link java.util.concurrent.CompletableFuture}
+ *
+ * @see #isWait()
+ * @see #send(club.minnced.discord.webhook.send.WebhookMessage)
+ */
+ @NotNull
+ public CompletableFuture sendComponents(@NotNull ComponentLayout first, @NotNull ComponentLayout... components) {
+ return send(WebhookMessage.components(first, components));
+ }
+
+ /**
+ * Sends the provided {@link club.minnced.discord.webhook.send.component.ComponentLayout} to the webhook.
+ * The returned future receives {@code null} if {@link club.minnced.discord.webhook.WebhookClientBuilder#setWait(boolean)}
+ * was set to false.
+ *
+ * @param components
+ * The components to send
+ *
+ * @return {@link java.util.concurrent.CompletableFuture}
+ *
+ * @see #isWait()
+ * @see #send(club.minnced.discord.webhook.send.WebhookMessage)
+ */
+ @NotNull
+ public CompletableFuture sendComponents(@NotNull Collection extends ComponentLayout> components) {
+ return send(WebhookMessage.components(components));
+ }
+
/**
* Sends the provided content as normal message to the webhook.
* The returned future receives {@code null} if {@link club.minnced.discord.webhook.WebhookClientBuilder#setWait(boolean)}
diff --git a/src/main/java/club/minnced/discord/webhook/WebhookCluster.java b/src/main/java/club/minnced/discord/webhook/WebhookCluster.java
index 2ce43a3..9d139f6 100644
--- a/src/main/java/club/minnced/discord/webhook/WebhookCluster.java
+++ b/src/main/java/club/minnced/discord/webhook/WebhookCluster.java
@@ -462,7 +462,7 @@ public List> broadcast(@NotNull WebhookEmbed
@NotNull
public List> broadcast(@NotNull Collection embeds) {
return webhooks.stream()
- .map(w -> w.send(embeds))
+ .map(w -> w.sendEmbeds(embeds))
.collect(Collectors.toList());
}
diff --git a/src/main/java/club/minnced/discord/webhook/external/D4JWebhookClient.java b/src/main/java/club/minnced/discord/webhook/external/D4JWebhookClient.java
index 32aa95e..06fcc39 100644
--- a/src/main/java/club/minnced/discord/webhook/external/D4JWebhookClient.java
+++ b/src/main/java/club/minnced/discord/webhook/external/D4JWebhookClient.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2018-2020 Florian Spieß
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package club.minnced.discord.webhook.external;
import club.minnced.discord.webhook.WebhookClient;
diff --git a/src/main/java/club/minnced/discord/webhook/external/JDAWebhookClient.java b/src/main/java/club/minnced/discord/webhook/external/JDAWebhookClient.java
index 4109988..cc80547 100644
--- a/src/main/java/club/minnced/discord/webhook/external/JDAWebhookClient.java
+++ b/src/main/java/club/minnced/discord/webhook/external/JDAWebhookClient.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2018-2020 Florian Spieß
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package club.minnced.discord.webhook.external;
import club.minnced.discord.webhook.WebhookClient;
@@ -135,7 +151,7 @@ public CompletableFuture send(@NotNull net.dv8tion.jda.api.enti
*/
@NotNull
public CompletableFuture send(@NotNull net.dv8tion.jda.api.entities.MessageEmbed embed) {
- return send(WebhookEmbedBuilder.fromJDA(embed).build());
+ return sendEmbeds(WebhookEmbedBuilder.fromJDA(embed).build());
}
/**
diff --git a/src/main/java/club/minnced/discord/webhook/external/JavacordWebhookClient.java b/src/main/java/club/minnced/discord/webhook/external/JavacordWebhookClient.java
index 735122f..a51af06 100644
--- a/src/main/java/club/minnced/discord/webhook/external/JavacordWebhookClient.java
+++ b/src/main/java/club/minnced/discord/webhook/external/JavacordWebhookClient.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2018-2020 Florian Spieß
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package club.minnced.discord.webhook.external;
import club.minnced.discord.webhook.WebhookClient;
@@ -134,7 +150,7 @@ public CompletableFuture send(@NotNull org.javacord.api.entity.
*/
@NotNull
public CompletableFuture send(@NotNull org.javacord.api.entity.message.embed.Embed embed) {
- return send(WebhookEmbedBuilder.fromJavacord(embed).build());
+ return sendEmbeds(WebhookEmbedBuilder.fromJavacord(embed).build());
}
/**
diff --git a/src/main/java/club/minnced/discord/webhook/send/WebhookMessage.java b/src/main/java/club/minnced/discord/webhook/send/WebhookMessage.java
index b412643..6a6f63e 100644
--- a/src/main/java/club/minnced/discord/webhook/send/WebhookMessage.java
+++ b/src/main/java/club/minnced/discord/webhook/send/WebhookMessage.java
@@ -19,6 +19,8 @@
import club.minnced.discord.webhook.IOUtil;
import club.minnced.discord.webhook.MessageFlags;
import club.minnced.discord.webhook.receive.ReadonlyMessage;
+import club.minnced.discord.webhook.send.component.ComponentLayout;
+import club.minnced.discord.webhook.send.component.layout.ActionRow;
import okhttp3.MultipartBody;
import okhttp3.RequestBody;
import org.jetbrains.annotations.NotNull;
@@ -43,22 +45,28 @@ public class WebhookMessage {
public static final int MAX_FILES = 10;
/** Maximum amount of embeds a single message can hold (10) */
public static final int MAX_EMBEDS = 10;
+ /**
+ * Maximum allowed component layouts (e.g. {@link club.minnced.discord.webhook.send.component.layout.ActionRow ActionRows}) in a message
+ */
+ public static final int MAX_COMPONENTS = 5;
protected final String username, avatarUrl, content;
protected final List embeds;
+ protected final List components;
protected final boolean isTTS;
protected final MessageAttachment[] attachments;
protected final AllowedMentions allowedMentions;
protected final int flags;
protected WebhookMessage(final String username, final String avatarUrl, final String content,
- final List embeds, final boolean isTTS,
+ final List embeds, List components, final boolean isTTS,
final MessageAttachment[] files, final AllowedMentions allowedMentions,
final int flags) {
this.username = username;
this.avatarUrl = avatarUrl;
this.content = content;
this.embeds = embeds;
+ this.components = components;
this.isTTS = isTTS;
this.attachments = files;
this.allowedMentions = allowedMentions;
@@ -149,7 +157,7 @@ public WebhookMessage asEphemeral(boolean ephemeral) {
flags |= MessageFlags.EPHEMERAL;
else
flags &= ~MessageFlags.EPHEMERAL;
- return new WebhookMessage(username, avatarUrl, content, embeds, isTTS, attachments, allowedMentions, flags);
+ return new WebhookMessage(username, avatarUrl, content, embeds, components, isTTS, attachments, allowedMentions, flags);
}
/**
@@ -205,7 +213,7 @@ public static WebhookMessage embeds(@NotNull WebhookEmbed first, @NotNull Webhoo
List list = new ArrayList<>(1 + embeds.length);
list.add(first);
Collections.addAll(list, embeds);
- return new WebhookMessage(null, null, null, list, false, null, AllowedMentions.all(), 0);
+ return new WebhookMessage(null, null, null, list, null, false, null, AllowedMentions.all(), 0);
}
/**
@@ -230,7 +238,62 @@ public static WebhookMessage embeds(@NotNull Collection embeds) {
if (embeds.isEmpty())
throw new IllegalArgumentException("Cannot build an empty message");
embeds.forEach(Objects::requireNonNull);
- return new WebhookMessage(null, null, null, new ArrayList<>(embeds), false, null, AllowedMentions.all(), 0);
+ return new WebhookMessage(null, null, null, new ArrayList<>(embeds), null, false, null, AllowedMentions.all(), 0);
+ }
+
+ /**
+ * Creates a WebhookMessage from
+ * the provided components. A message can hold up to {@value #MAX_COMPONENTS} components.
+ *
+ * @param first
+ * The first component layout (see {@link ActionRow})
+ * @param components
+ * Additional components for the message (see {@link ActionRow})
+ *
+ * @throws java.lang.NullPointerException
+ * If provided with null
+ * @throws java.lang.IllegalArgumentException
+ * If more than {@value WebhookMessage#MAX_COMPONENTS} are provided
+ *
+ * @return A WebhookMessage for the components
+ */
+ @NotNull
+ public static WebhookMessage components(@NotNull ComponentLayout first, @NotNull ComponentLayout... components) {
+ Objects.requireNonNull(components, "Components");
+ if (components.length >= WebhookMessage.MAX_COMPONENTS)
+ throw new IllegalArgumentException("Cannot add more than 5 components to a message");
+ for (ComponentLayout e : components) {
+ Objects.requireNonNull(e);
+ }
+ List list = new ArrayList<>(1 + components.length);
+ list.add(first);
+ Collections.addAll(list, components);
+ return new WebhookMessage(null, null, null, null, list, false, null, AllowedMentions.all(), 0);
+ }
+
+ /**
+ * Creates a WebhookMessage from
+ * the provided components. A message can hold up to {@value #MAX_COMPONENTS} components.
+ *
+ * @param components
+ * Components for the message (see {@link ActionRow})
+ *
+ * @throws java.lang.NullPointerException
+ * If provided with null
+ * @throws java.lang.IllegalArgumentException
+ * If more than {@value WebhookMessage#MAX_COMPONENTS} are provided
+ *
+ * @return A WebhookMessage for the components
+ */
+ @NotNull
+ public static WebhookMessage components(@NotNull Collection extends ComponentLayout> components) {
+ Objects.requireNonNull(components, "Components");
+ if (components.size() > WebhookMessage.MAX_COMPONENTS)
+ throw new IllegalArgumentException("Cannot add more than 5 component layouts to a message");
+ if (components.isEmpty())
+ throw new IllegalArgumentException("Cannot build an empty message");
+ components.forEach(Objects::requireNonNull);
+ return new WebhookMessage(null, null, null, null, new ArrayList<>(components), false, null, AllowedMentions.all(), 0);
}
/**
@@ -267,7 +330,7 @@ public static WebhookMessage files(@NotNull Map attachments) {
Object data = attachment.getValue();
files[i++] = convertAttachment(name, data);
}
- return new WebhookMessage(null, null, null, null, false, files, AllowedMentions.all(), 0);
+ return new WebhookMessage(null, null, null, null, null, false, files, AllowedMentions.all(), 0);
}
/**
@@ -279,20 +342,20 @@ public static WebhookMessage files(@NotNull Map attachments) {
* to the first 2 arguments.
* The allowed data types are {@code byte[] | InputStream | File}
*
- * @param name1
+ * @param name1
* The alternative name of the first attachment
- * @param data1
+ * @param data1
* The first attachment, must be of type {@code byte[] | InputStream | File}
- * @param attachments
+ * @param attachments
* Optional additional attachments to add, pairs of {@literal String->Data}
*
- * @return A WebhookMessage for the attachments
- *
* @throws java.lang.NullPointerException
* If provided with null
* @throws java.lang.IllegalArgumentException
* If no attachments are provided or more than {@value #MAX_FILES}
* or the additional arguments are not an even count or an invalid format
+ *
+ * @return A WebhookMessage for the attachments
*/
@NotNull // forcing first pair as we expect at least one entry (Effective Java 3rd. Edition - Item 53)
public static WebhookMessage files(@NotNull String name1, @NotNull Object data1, @NotNull Object... attachments) {
@@ -313,7 +376,7 @@ public static WebhookMessage files(@NotNull String name1, @NotNull Object data1,
throw new IllegalArgumentException("Provided arguments must be pairs for (String, Data). Expected String and found " + (name == null ? null : name.getClass().getName()));
files[j] = convertAttachment((String) name, data);
}
- return new WebhookMessage(null, null, null, null, false, files, AllowedMentions.all(), 0);
+ return new WebhookMessage(null, null, null, null, null, false, files, AllowedMentions.all(), 0);
}
/**
@@ -344,6 +407,15 @@ public RequestBody getBody() {
} else {
payload.put("embeds", new JSONArray());
}
+ if (components != null && !components.isEmpty()) {
+ final JSONArray array = new JSONArray();
+ for (ComponentLayout component : components) {
+ array.put(component);
+ }
+ payload.put("components", array);
+ } else {
+ payload.put("components", new JSONArray());
+ }
if (avatarUrl != null)
payload.put("avatar_url", avatarUrl);
if (username != null)
@@ -386,4 +458,4 @@ else if (data instanceof byte[])
throw new IllegalArgumentException(ex);
}
}
-}
+}
\ No newline at end of file
diff --git a/src/main/java/club/minnced/discord/webhook/send/WebhookMessageBuilder.java b/src/main/java/club/minnced/discord/webhook/send/WebhookMessageBuilder.java
index 7b857c5..db63cec 100644
--- a/src/main/java/club/minnced/discord/webhook/send/WebhookMessageBuilder.java
+++ b/src/main/java/club/minnced/discord/webhook/send/WebhookMessageBuilder.java
@@ -17,6 +17,7 @@
package club.minnced.discord.webhook.send;
import club.minnced.discord.webhook.MessageFlags;
+import club.minnced.discord.webhook.send.component.ComponentLayout;
import discord4j.core.spec.MessageCreateSpec;
import discord4j.core.spec.MessageEditSpec;
import discord4j.discordjson.json.AllowedMentionsData;
@@ -48,6 +49,7 @@
public class WebhookMessageBuilder {
protected final StringBuilder content = new StringBuilder();
protected final List embeds = new LinkedList<>();
+ protected final List components = new ArrayList<>();
protected final MessageAttachment[] files = new MessageAttachment[WebhookMessage.MAX_FILES];
protected AllowedMentions allowedMentions = AllowedMentions.all();
protected String username, avatarUrl;
@@ -61,7 +63,7 @@ public class WebhookMessageBuilder {
* @return True, if this builder is empty
*/
public boolean isEmpty() {
- return content.length() == 0 && embeds.isEmpty() && getFileAmount() == 0;
+ return content.length() == 0 && embeds.isEmpty() && getFileAmount() == 0 && components.isEmpty();
}
/**
@@ -183,6 +185,58 @@ public WebhookMessageBuilder addEmbeds(@NotNull Collection extends WebhookEmbe
return this;
}
+ /**
+ * Adds the provided components to the builder
+ * Note that most components are only usable by interactions.
+ *
+ * @param components
+ * The layout components to add
+ *
+ * @throws java.lang.NullPointerException
+ * If provided with null
+ * @throws java.lang.IllegalStateException
+ * If more than {@value WebhookMessage#MAX_COMPONENTS} are added
+ *
+ * @return This builder for chaining convenience
+ */
+ @NotNull
+ public WebhookMessageBuilder addComponents(@NotNull ComponentLayout... components) {
+ Objects.requireNonNull(components, "Components");
+ if (this.components.size() + components.length > WebhookMessage.MAX_COMPONENTS)
+ throw new IllegalStateException("Cannot have more than " + WebhookMessage.MAX_COMPONENTS + " component layouts in a message");
+ for (ComponentLayout component : components) {
+ Objects.requireNonNull(component, "Component");
+ this.components.add(component);
+ }
+ return this;
+ }
+
+ /**
+ * Adds the provided components to the builder
+ * Note that most components are only usable by interactions.
+ *
+ * @param components
+ * The layout components to add
+ *
+ * @throws java.lang.NullPointerException
+ * If provided with null
+ * @throws java.lang.IllegalStateException
+ * If more than {@value WebhookMessage#MAX_COMPONENTS} are added
+ *
+ * @return This builder for chaining convenience
+ */
+ @NotNull
+ public WebhookMessageBuilder addComponents(@NotNull Collection extends ComponentLayout> components) {
+ Objects.requireNonNull(components, "Components");
+ if (this.components.size() + components.size() > WebhookMessage.MAX_COMPONENTS)
+ throw new IllegalStateException("Cannot have more than " + WebhookMessage.MAX_COMPONENTS + " component layouts in a message");
+ for (ComponentLayout component : components) {
+ Objects.requireNonNull(component, "Component");
+ this.components.add(component);
+ }
+ return this;
+ }
+
/**
* Configures the content for this builder
*
@@ -409,7 +463,7 @@ public WebhookMessageBuilder addFile(@NotNull String name, @NotNull InputStream
public WebhookMessage build() {
if (isEmpty())
throw new IllegalStateException("Cannot build an empty message!");
- return new WebhookMessage(username, avatarUrl, content.toString(), embeds, isTTS,
+ return new WebhookMessage(username, avatarUrl, content.toString(), embeds, components, isTTS,
fileIndex == 0 ? null : Arrays.copyOf(files, fileIndex), allowedMentions, flags);
}
@@ -452,13 +506,13 @@ public static WebhookMessageBuilder fromJDA(@NotNull net.dv8tion.jda.api.entitie
AllowedMentions allowedMentions = AllowedMentions.none();
Mentions mentions = message.getMentions();
allowedMentions.withRoles(
- mentions.getRoles().stream()
- .map(Role::getId)
- .collect(Collectors.toList()));
+ mentions.getRoles().stream()
+ .map(Role::getId)
+ .collect(Collectors.toList()));
allowedMentions.withUsers(
- mentions.getUsers().stream()
- .map(User::getId)
- .collect(Collectors.toList()));
+ mentions.getUsers().stream()
+ .map(User::getId)
+ .collect(Collectors.toList()));
allowedMentions.withParseEveryone(mentions.mentionsEveryone());
builder.setAllowedMentions(allowedMentions);
builder.setEphemeral(message.isEphemeral());
@@ -486,13 +540,13 @@ public static WebhookMessageBuilder fromJavacord(@NotNull org.javacord.api.entit
AllowedMentions allowedMentions = AllowedMentions.none();
allowedMentions.withUsers(
- message.getMentionedUsers().stream()
- .map(DiscordEntity::getIdAsString)
- .collect(Collectors.toList()));
+ message.getMentionedUsers().stream()
+ .map(DiscordEntity::getIdAsString)
+ .collect(Collectors.toList()));
allowedMentions.withRoles(
- message.getMentionedRoles().stream()
- .map(DiscordEntity::getIdAsString)
- .collect(Collectors.toList()));
+ message.getMentionedRoles().stream()
+ .map(DiscordEntity::getIdAsString)
+ .collect(Collectors.toList()));
allowedMentions.withParseEveryone(message.mentionsEveryone());
builder.setAllowedMentions(allowedMentions);
return builder;
@@ -548,10 +602,10 @@ public static WebhookMessageBuilder fromD4J(@NotNull MessageCreateSpec spec) {
builder.setTTS(tts.get());
if (!embeds.isAbsent()) {
builder.addEmbeds(
- embeds.get().stream()
- .map(WebhookEmbedBuilder::fromD4J)
- .map(WebhookEmbedBuilder::build)
- .collect(Collectors.toList())
+ embeds.get().stream()
+ .map(WebhookEmbedBuilder::fromD4J)
+ .map(WebhookEmbedBuilder::build)
+ .collect(Collectors.toList())
);
}
@@ -627,4 +681,4 @@ public static WebhookMessageBuilder fromD4J(@NotNull MessageEditSpec spec) {
return builder;
}
-}
+}
\ No newline at end of file
diff --git a/src/main/java/club/minnced/discord/webhook/send/component/ActionComponent.java b/src/main/java/club/minnced/discord/webhook/send/component/ActionComponent.java
new file mode 100644
index 0000000..d935e86
--- /dev/null
+++ b/src/main/java/club/minnced/discord/webhook/send/component/ActionComponent.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2018-2020 Florian Spieß
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package club.minnced.discord.webhook.send.component;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Interactive components that can be inserted inside a {@link ComponentLayout}
+ *
+ * @see ComponentLayout#addComponents(java.util.Collection)
+ */
+public interface ActionComponent extends ComponentElement {
+ int MAX_CUSTOM_ID_LENGTH = 100;
+
+ /**
+ * The custom id of the component.
+ * This can be used for handling interactions with this component.
+ *
+ * @return Custom id of the component, or null if not applicable
+ */
+ @Nullable
+ String getCustomId();
+
+ /**
+ * Changes the disabled status of button.
+ * Note: This does not reflect updating a message in discord.
+ * You must still use {@link club.minnced.discord.webhook.WebhookClient#edit(long, club.minnced.discord.webhook.send.WebhookMessage)}.
+ *
+ * @param disabled
+ * use true to disable button
+ *
+ * @return Updated component instance
+ */
+ @NotNull
+ ActionComponent setDisabled(boolean disabled);
+
+ /**
+ * Whether this component is disabled (not usable).
+ *
+ * @return true if the component is disabled
+ */
+ boolean isDisabled();
+
+}
\ No newline at end of file
diff --git a/src/main/java/club/minnced/discord/webhook/send/component/Component.java b/src/main/java/club/minnced/discord/webhook/send/component/Component.java
new file mode 100644
index 0000000..a9a9e06
--- /dev/null
+++ b/src/main/java/club/minnced/discord/webhook/send/component/Component.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2018-2020 Florian Spieß
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package club.minnced.discord.webhook.send.component;
+
+import club.minnced.discord.webhook.util.Validate;
+import org.jetbrains.annotations.NotNull;
+import org.json.JSONString;
+
+/**
+ * Components are a framework for adding interactive elements to the messages your webhooks send.
+ */
+public interface Component extends JSONString, Validate {
+ /**
+ * Type enum representing the individual types of components.
+ * This includes both {@link ComponentLayout ComponentLayouts} and {@link ComponentElement ComponentElements}.
+ *
+ * @return The type of the component
+ */
+ @NotNull
+ Type getType();
+
+ /**
+ * All currently available component types.
+ */
+ enum Type {
+ ACTION_ROW(1),
+ BUTTON(2, 5),
+ STRING_SELECT(3),
+// Not currently usable in messages.
+// TEXT_INPUT(4),
+// TODO: Add this later
+// USER_SELECT(5),
+// ROLE_SELECT(6),
+// MENTIONABLE_SELECT(7),
+// CHANNEL_SELECT(8),
+ ;
+
+ private final int id;
+ private final int maxPerLayout;
+
+ Type(int id) {
+ this.id = id;
+ this.maxPerLayout = 1;
+ }
+
+ Type(int id, int maxPerLayout) {
+ this.id = id;
+ this.maxPerLayout = maxPerLayout;
+ }
+
+ /**
+ * The raw type number used by the API.
+ *
+ * @return Integer used by discord to determine the type of component
+ */
+ public int getId() {
+ return this.id;
+ }
+
+ /**
+ * The maximum number of components of this type that can be added to a single layout.
+ *
+ * @return The maximum number of components of this type that can be added to a single layout
+ */
+ public int getMaxPerLayout() {
+ return this.maxPerLayout;
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/club/minnced/discord/webhook/send/component/ComponentElement.java b/src/main/java/club/minnced/discord/webhook/send/component/ComponentElement.java
new file mode 100644
index 0000000..b1c4e39
--- /dev/null
+++ b/src/main/java/club/minnced/discord/webhook/send/component/ComponentElement.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2018-2020 Florian Spieß
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package club.minnced.discord.webhook.send.component;
+
+/**
+ * Components which can be added to {@link ComponentLayout ComponentLayouts}
+ */
+public interface ComponentElement extends Component {
+}
diff --git a/src/main/java/club/minnced/discord/webhook/send/component/ComponentLayout.java b/src/main/java/club/minnced/discord/webhook/send/component/ComponentLayout.java
new file mode 100644
index 0000000..076d75a
--- /dev/null
+++ b/src/main/java/club/minnced/discord/webhook/send/component/ComponentLayout.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2018-2020 Florian Spieß
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package club.minnced.discord.webhook.send.component;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Container for multiple {@link ActionComponent ActionComponents}.
+ * A LayoutComponent cannot contain another LayoutComponent
+ *
+ * @see club.minnced.discord.webhook.send.WebhookMessageBuilder#addComponents(ComponentLayout...)
+ */
+public interface ComponentLayout extends Component {
+ /**
+ * The currently applied {@link ActionComponent ActionComponents}.
+ *
+ * @return The action components (buttons and select menus)
+ */
+ @NotNull List getComponents();
+
+ /**
+ * Creates an action row with the given list of components
+ *
+ * @param components
+ * The component to be added to the layout component
+ *
+ * @throws java.lang.NullPointerException
+ * If null is provided
+ *
+ * @return This instance of action component for chaining
+ */
+ @NotNull ComponentLayout addComponents(@NotNull Collection extends ComponentElement> components);
+
+}
\ No newline at end of file
diff --git a/src/main/java/club/minnced/discord/webhook/send/component/EmojiHolder.java b/src/main/java/club/minnced/discord/webhook/send/component/EmojiHolder.java
new file mode 100644
index 0000000..fbb13ce
--- /dev/null
+++ b/src/main/java/club/minnced/discord/webhook/send/component/EmojiHolder.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2018-2020 Florian Spieß
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package club.minnced.discord.webhook.send.component;
+
+import club.minnced.discord.webhook.DiscordEmoji;
+import club.minnced.discord.webhook.WebhookClient;
+import club.minnced.discord.webhook.send.WebhookMessage;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Components which can make use of emojis for display purposes.
+ *
+ * @param
+ * The component subtype
+ */
+public interface EmojiHolder {
+ /**
+ * Use the provided emoji.
+ * Note: This does not reflect updating a message in discord.
+ * You must still use {@link WebhookClient#edit(long, WebhookMessage)}.
+ *
+ * @param emoji
+ * the emoji to add, or null to remove emoji
+ *
+ * @return Update object with the provided emoji
+ */
+ @NotNull
+ T setEmoji(@Nullable DiscordEmoji emoji);
+
+ /**
+ * The emoji used by this object.
+ *
+ * @return The emoji, or null if there is no emoji
+ */
+ @Nullable DiscordEmoji getEmoji();
+
+}
\ No newline at end of file
diff --git a/src/main/java/club/minnced/discord/webhook/send/component/button/Button.java b/src/main/java/club/minnced/discord/webhook/send/component/button/Button.java
new file mode 100644
index 0000000..d17fc13
--- /dev/null
+++ b/src/main/java/club/minnced/discord/webhook/send/component/button/Button.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright 2018-2020 Florian Spieß
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package club.minnced.discord.webhook.send.component.button;
+
+import club.minnced.discord.webhook.DiscordEmoji;
+import club.minnced.discord.webhook.send.component.ActionComponent;
+import club.minnced.discord.webhook.send.component.ComponentLayout;
+import club.minnced.discord.webhook.send.component.EmojiHolder;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.json.JSONObject;
+
+/**
+ * Button components that can be placed inside a {@link ComponentLayout}
+ *
+ * @see ComponentLayout#addComponents(java.util.Collection)
+ */
+public class Button implements ActionComponent, EmojiHolder