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 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 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 CollectionNote 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 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 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