From 8ccfc564657f32553cef4abe464d334f14e4986f Mon Sep 17 00:00:00 2001
From: freya02 <41875020+freya022@users.noreply.github.com>
Date: Sun, 3 Nov 2024 14:35:53 +0100
Subject: [PATCH] Add premium buttons (#2752)
Deprecates replyWithPremiumRequired
---
.../jda/api/entities/SkuSnowflake.java | 61 ++++++
.../GenericCommandInteractionEvent.java | 1 +
...enericComponentInteractionCreateEvent.java | 1 +
.../jda/api/interactions/Interaction.java | 2 -
.../IPremiumRequiredReplyCallback.java | 13 +-
.../components/LayoutComponent.java | 6 +-
.../components/buttons/Button.java | 180 ++++++++++--------
.../components/buttons/ButtonStyle.java | 2 +
.../InteractionCallbackAction.java | 10 +-
.../PremiumRequiredCallbackAction.java | 6 +
.../internal/entities/SkuSnowflakeImpl.java | 58 ++++++
.../interactions/component/ButtonImpl.java | 65 ++++++-
.../jda/internal/utils/ComponentsUtil.java | 43 +++++
.../jda/test/interactions/ButtonTests.java | 141 ++++++++++++++
.../jda/test/util/ComponentsUtilTest.java | 48 +++++
15 files changed, 546 insertions(+), 91 deletions(-)
create mode 100644 src/main/java/net/dv8tion/jda/api/entities/SkuSnowflake.java
create mode 100644 src/main/java/net/dv8tion/jda/internal/entities/SkuSnowflakeImpl.java
create mode 100644 src/main/java/net/dv8tion/jda/internal/utils/ComponentsUtil.java
create mode 100644 src/test/java/net/dv8tion/jda/test/interactions/ButtonTests.java
create mode 100644 src/test/java/net/dv8tion/jda/test/util/ComponentsUtilTest.java
diff --git a/src/main/java/net/dv8tion/jda/api/entities/SkuSnowflake.java b/src/main/java/net/dv8tion/jda/api/entities/SkuSnowflake.java
new file mode 100644
index 0000000000..6cbb6191f5
--- /dev/null
+++ b/src/main/java/net/dv8tion/jda/api/entities/SkuSnowflake.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2015 Austin Keener, Michael Ritter, Florian Spieß, and the JDA contributors
+ *
+ * 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 net.dv8tion.jda.api.entities;
+
+import net.dv8tion.jda.api.utils.MiscUtil;
+import net.dv8tion.jda.internal.entities.SkuSnowflakeImpl;
+
+import javax.annotation.Nonnull;
+
+/**
+ * Represents an abstract SKU reference by only the SKU ID.
+ *
+ *
This is used for methods which only need a SKU ID to function, you cannot use this for getting any properties.
+ */
+public interface SkuSnowflake extends ISnowflake
+{
+ /**
+ * Creates a SKU instance which only wraps an ID.
+ *
+ * @param id
+ * The SKU id
+ *
+ * @return A SKU snowflake instance
+ */
+ @Nonnull
+ static SkuSnowflake fromId(long id)
+ {
+ return new SkuSnowflakeImpl(id);
+ }
+
+ /**
+ * Creates a SKU instance which only wraps an ID.
+ *
+ * @param id
+ * The SKU id
+ *
+ * @throws IllegalArgumentException
+ * If the provided ID is not a valid snowflake
+ *
+ * @return A SKU snowflake instance
+ */
+ @Nonnull
+ static SkuSnowflake fromId(@Nonnull String id)
+ {
+ return fromId(MiscUtil.parseSnowflake(id));
+ }
+}
diff --git a/src/main/java/net/dv8tion/jda/api/events/interaction/command/GenericCommandInteractionEvent.java b/src/main/java/net/dv8tion/jda/api/events/interaction/command/GenericCommandInteractionEvent.java
index b3bb9dc6d9..7a0c4ef531 100644
--- a/src/main/java/net/dv8tion/jda/api/events/interaction/command/GenericCommandInteractionEvent.java
+++ b/src/main/java/net/dv8tion/jda/api/events/interaction/command/GenericCommandInteractionEvent.java
@@ -125,6 +125,7 @@ public ModalCallbackAction replyModal(@Nonnull Modal modal)
@Nonnull
@Override
+ @Deprecated
@CheckReturnValue
public PremiumRequiredCallbackAction replyWithPremiumRequired()
{
diff --git a/src/main/java/net/dv8tion/jda/api/events/interaction/component/GenericComponentInteractionCreateEvent.java b/src/main/java/net/dv8tion/jda/api/events/interaction/component/GenericComponentInteractionCreateEvent.java
index 7e766e76ef..a470852bea 100644
--- a/src/main/java/net/dv8tion/jda/api/events/interaction/component/GenericComponentInteractionCreateEvent.java
+++ b/src/main/java/net/dv8tion/jda/api/events/interaction/component/GenericComponentInteractionCreateEvent.java
@@ -132,6 +132,7 @@ public ModalCallbackAction replyModal(@Nonnull Modal modal)
@Nonnull
@Override
+ @Deprecated
@CheckReturnValue
public PremiumRequiredCallbackAction replyWithPremiumRequired()
{
diff --git a/src/main/java/net/dv8tion/jda/api/interactions/Interaction.java b/src/main/java/net/dv8tion/jda/api/interactions/Interaction.java
index 32fbb0d545..9f2e154d5e 100644
--- a/src/main/java/net/dv8tion/jda/api/interactions/Interaction.java
+++ b/src/main/java/net/dv8tion/jda/api/interactions/Interaction.java
@@ -48,8 +48,6 @@
*
Which supports choice suggestions for auto-complete interactions via {@link IAutoCompleteCallback#replyChoices(Command.Choice...)}
*
{@link IModalCallback}
*
Which supports replying using a {@link Modal} via {@link IModalCallback#replyModal(Modal)}
- * {@link IPremiumRequiredReplyCallback}
- *
Which will reply stating that an {@link Entitlement Entitlement} is required
*
*
* Once the interaction is acknowledged, you can not reply with these methods again. If the interaction is a {@link IDeferrableCallback deferrable},
diff --git a/src/main/java/net/dv8tion/jda/api/interactions/callbacks/IPremiumRequiredReplyCallback.java b/src/main/java/net/dv8tion/jda/api/interactions/callbacks/IPremiumRequiredReplyCallback.java
index a25b923c94..03339af158 100644
--- a/src/main/java/net/dv8tion/jda/api/interactions/callbacks/IPremiumRequiredReplyCallback.java
+++ b/src/main/java/net/dv8tion/jda/api/interactions/callbacks/IPremiumRequiredReplyCallback.java
@@ -16,8 +16,10 @@
package net.dv8tion.jda.api.interactions.callbacks;
-import net.dv8tion.jda.api.requests.restaction.interactions.PremiumRequiredCallbackAction;
import net.dv8tion.jda.api.entities.Entitlement;
+import net.dv8tion.jda.api.entities.SkuSnowflake;
+import net.dv8tion.jda.api.interactions.components.buttons.Button;
+import net.dv8tion.jda.api.requests.restaction.interactions.PremiumRequiredCallbackAction;
import javax.annotation.CheckReturnValue;
import javax.annotation.Nonnull;
@@ -28,10 +30,19 @@
*
Replying with {@link #replyWithPremiumRequired()} will automatically acknowledge this interaction.
*
*
Note:This interaction requires monetization to be enabled.
+ *
+ * @deprecated Replaced with {@link Button#premium(SkuSnowflake)},
+ * see the Discord change logs for more details.
*/
+@Deprecated
public interface IPremiumRequiredReplyCallback extends IDeferrableCallback
{
+ /**
+ * @deprecated Replaced with {@link Button#premium(SkuSnowflake)},
+ * see the Discord change logs for more details.
+ */
@Nonnull
+ @Deprecated
@CheckReturnValue
PremiumRequiredCallbackAction replyWithPremiumRequired();
}
diff --git a/src/main/java/net/dv8tion/jda/api/interactions/components/LayoutComponent.java b/src/main/java/net/dv8tion/jda/api/interactions/components/LayoutComponent.java
index 167c4868d6..15070e0271 100644
--- a/src/main/java/net/dv8tion/jda/api/interactions/components/LayoutComponent.java
+++ b/src/main/java/net/dv8tion/jda/api/interactions/components/LayoutComponent.java
@@ -20,6 +20,7 @@
import net.dv8tion.jda.api.interactions.components.buttons.ButtonStyle;
import net.dv8tion.jda.api.utils.data.SerializableData;
import net.dv8tion.jda.internal.utils.Checks;
+import net.dv8tion.jda.internal.utils.ComponentsUtil;
import net.dv8tion.jda.internal.utils.Helpers;
import org.jetbrains.annotations.Unmodifiable;
@@ -207,7 +208,8 @@ default boolean isValid()
*
This will locate and replace the existing component with the specified ID. If you provide null it will be removed instead.
*
* @param id
- * The custom id of this component, can also be a URL for a {@link Button} with {@link ButtonStyle#LINK}
+ * The custom id of this component, can also be a URL for a {@link Button} with {@link ButtonStyle#LINK},
+ * or an SKU id for {@link ButtonStyle#PREMIUM}
* @param newComponent
* The new component or null to remove it
*
@@ -227,7 +229,7 @@ default ItemComponent updateComponent(@Nonnull String id, @Nullable ItemComponen
if (!(component instanceof ActionComponent))
continue;
ActionComponent action = (ActionComponent) component;
- if (id.equals(action.getId()) || (action instanceof Button && id.equals(((Button) action).getUrl())))
+ if (ComponentsUtil.isSameIdentifier(action, id))
{
if (newComponent == null)
it.remove();
diff --git a/src/main/java/net/dv8tion/jda/api/interactions/components/buttons/Button.java b/src/main/java/net/dv8tion/jda/api/interactions/components/buttons/Button.java
index a13b6807e2..deddd28f0c 100644
--- a/src/main/java/net/dv8tion/jda/api/interactions/components/buttons/Button.java
+++ b/src/main/java/net/dv8tion/jda/api/interactions/components/buttons/Button.java
@@ -16,6 +16,7 @@
package net.dv8tion.jda.api.interactions.components.buttons;
+import net.dv8tion.jda.api.entities.SkuSnowflake;
import net.dv8tion.jda.api.entities.emoji.Emoji;
import net.dv8tion.jda.api.entities.emoji.EmojiUnion;
import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent;
@@ -91,7 +92,8 @@ public interface Button extends ActionComponent
int URL_MAX_LENGTH = 512;
/**
- * The visible text on the button.
+ * The visible text on the button,
+ * or an empty string if this is a {@link ButtonStyle#PREMIUM PREMIUM}-style button.
*
* @return The button label
*/
@@ -114,6 +116,14 @@ public interface Button extends ActionComponent
@Nullable
String getUrl();
+ /**
+ * The target SKU for this button, if it is a {@link ButtonStyle#PREMIUM PREMIUM}-style Button.
+ *
+ * @return The target SKU or {@code null}
+ */
+ @Nullable
+ SkuSnowflake getSku();
+
/**
* The emoji attached to this button.
*
This can be either {@link Emoji.Type#UNICODE unicode} or {@link Emoji.Type#CUSTOM custom}.
@@ -143,7 +153,7 @@ default Button asEnabled()
@CheckReturnValue
default Button withDisabled(boolean disabled)
{
- return new ButtonImpl(getId(), getLabel(), getStyle(), getUrl(), disabled, getEmoji());
+ return new ButtonImpl(getId(), getLabel(), getStyle(), getUrl(), getSku(), disabled, getEmoji()).checkValid();
}
/**
@@ -152,13 +162,16 @@ default Button withDisabled(boolean disabled)
* @param emoji
* The emoji to use
*
+ * @throws IllegalArgumentException
+ * If this is a {@link ButtonStyle#PREMIUM PREMIUM}-styled button
+ *
* @return New button with emoji
*/
@Nonnull
@CheckReturnValue
default Button withEmoji(@Nullable Emoji emoji)
{
- return new ButtonImpl(getId(), getLabel(), getStyle(), getUrl(), isDisabled(), emoji);
+ return new ButtonImpl(getId(), getLabel(), getStyle(), getUrl(), null, isDisabled(), emoji).checkValid();
}
/**
@@ -169,6 +182,7 @@ default Button withEmoji(@Nullable Emoji emoji)
*
* @throws IllegalArgumentException
*
+ * - If this is a {@link ButtonStyle#PREMIUM PREMIUM}-styled button
* - If the provided {@code label} is null or empty.
* - If the character limit for {@code label}, defined by {@link #LABEL_MAX_LENGTH} as {@value #LABEL_MAX_LENGTH},
* is exceeded.
@@ -180,9 +194,7 @@ default Button withEmoji(@Nullable Emoji emoji)
@CheckReturnValue
default Button withLabel(@Nonnull String label)
{
- Checks.notEmpty(label, "Label");
- Checks.notLonger(label, LABEL_MAX_LENGTH, "Label");
- return new ButtonImpl(getId(), label, getStyle(), getUrl(), isDisabled(), getEmoji());
+ return new ButtonImpl(getId(), label, getStyle(), getUrl(), getSku(), isDisabled(), getEmoji()).checkValid();
}
/**
@@ -193,6 +205,7 @@ default Button withLabel(@Nonnull String label)
*
* @throws IllegalArgumentException
*
+ * - If this is a {@link ButtonStyle#LINK LINK}-styled or {@link ButtonStyle#PREMIUM PREMIUM}-styled button
* - If the provided {@code id} is null or empty.
* - If the character limit for {@code id}, defined by {@link #ID_MAX_LENGTH} as {@value #ID_MAX_LENGTH},
* is exceeded.
@@ -204,9 +217,7 @@ default Button withLabel(@Nonnull String label)
@CheckReturnValue
default Button withId(@Nonnull String id)
{
- Checks.notEmpty(id, "ID");
- Checks.notLonger(id, ID_MAX_LENGTH, "ID");
- return new ButtonImpl(id, getLabel(), getStyle(), null, isDisabled(), getEmoji());
+ return new ButtonImpl(id, getLabel(), getStyle(), null, null, isDisabled(), getEmoji()).checkValid();
}
/**
@@ -217,6 +228,7 @@ default Button withId(@Nonnull String id)
*
* @throws IllegalArgumentException
*
+ * - If this is not a {@link ButtonStyle#LINK LINK}-styled button
* - If the provided {@code url} is null or empty.
* - If the character limit for {@code url}, defined by {@link #URL_MAX_LENGTH} as {@value #URL_MAX_LENGTH},
* is exceeded.
@@ -228,9 +240,25 @@ default Button withId(@Nonnull String id)
@CheckReturnValue
default Button withUrl(@Nonnull String url)
{
- Checks.notEmpty(url, "URL");
- Checks.notLonger(url, URL_MAX_LENGTH, "URL");
- return new ButtonImpl(null, getLabel(), ButtonStyle.LINK, url, isDisabled(), getEmoji());
+ return new ButtonImpl(null, getLabel(), getStyle(), url, null, isDisabled(), getEmoji()).checkValid();
+ }
+
+ /**
+ * Returns a copy of this button with the provided SKU.
+ *
+ * @param sku
+ * The SKU to use
+ *
+ * @throws IllegalArgumentException
+ * If the provided {@code sku} is null, or if the button is not a {@link ButtonStyle#PREMIUM PREMIUM} button
+ *
+ * @return New button with the changed url
+ */
+ @Nonnull
+ @CheckReturnValue
+ default Button withSku(@Nonnull SkuSnowflake sku)
+ {
+ return new ButtonImpl(null, "", getStyle(), null, sku, isDisabled(), null).checkValid();
}
/**
@@ -244,7 +272,7 @@ default Button withUrl(@Nonnull String url)
* @throws IllegalArgumentException
*
* - If the provided {@code style} is null.
- * - If the provided {@code style} tries to change whether this button is a {@link ButtonStyle#LINK LINK} button.
+ * - If the provided {@code style} tries to change whether this button is a {@link ButtonStyle#LINK LINK} or {@link ButtonStyle#PREMIUM PREMIUM} button.
*
*
* @return New button with the changed style
@@ -259,7 +287,11 @@ default Button withStyle(@Nonnull ButtonStyle style)
throw new IllegalArgumentException("You cannot change a link button to another style!");
if (getStyle() != ButtonStyle.LINK && style == ButtonStyle.LINK)
throw new IllegalArgumentException("You cannot change a styled button to a link button!");
- return new ButtonImpl(getId(), getLabel(), style, getUrl(), isDisabled(), getEmoji());
+ if (getStyle() == ButtonStyle.PREMIUM && style != ButtonStyle.PREMIUM)
+ throw new IllegalArgumentException("You cannot change a premium button to another style!");
+ if (getStyle() != ButtonStyle.PREMIUM && style == ButtonStyle.PREMIUM)
+ throw new IllegalArgumentException("You cannot change a styled button to a premium button!");
+ return new ButtonImpl(getId(), getLabel(), style, getUrl(), getSku(), isDisabled(), getEmoji()).checkValid();
}
/**
@@ -286,11 +318,7 @@ default Button withStyle(@Nonnull ButtonStyle style)
@Nonnull
static Button primary(@Nonnull String id, @Nonnull String label)
{
- Checks.notEmpty(id, "Id");
- Checks.notEmpty(label, "Label");
- Checks.notLonger(id, ID_MAX_LENGTH, "Id");
- Checks.notLonger(label, LABEL_MAX_LENGTH, "Label");
- return new ButtonImpl(id, label, ButtonStyle.PRIMARY, false, null);
+ return new ButtonImpl(id, label, ButtonStyle.PRIMARY, false, null).checkValid();
}
/**
@@ -317,10 +345,7 @@ static Button primary(@Nonnull String id, @Nonnull String label)
@Nonnull
static Button primary(@Nonnull String id, @Nonnull Emoji emoji)
{
- Checks.notEmpty(id, "Id");
- Checks.notNull(emoji, "Emoji");
- Checks.notLonger(id, ID_MAX_LENGTH, "Id");
- return new ButtonImpl(id, "", ButtonStyle.PRIMARY, false, emoji);
+ return new ButtonImpl(id, "", ButtonStyle.PRIMARY, false, emoji).checkValid();
}
/**
@@ -347,11 +372,7 @@ static Button primary(@Nonnull String id, @Nonnull Emoji emoji)
@Nonnull
static Button secondary(@Nonnull String id, @Nonnull String label)
{
- Checks.notEmpty(id, "Id");
- Checks.notEmpty(label, "Label");
- Checks.notLonger(id, ID_MAX_LENGTH, "Id");
- Checks.notLonger(label, LABEL_MAX_LENGTH, "Label");
- return new ButtonImpl(id, label, ButtonStyle.SECONDARY, false, null);
+ return new ButtonImpl(id, label, ButtonStyle.SECONDARY, false, null).checkValid();
}
/**
@@ -378,10 +399,7 @@ static Button secondary(@Nonnull String id, @Nonnull String label)
@Nonnull
static Button secondary(@Nonnull String id, @Nonnull Emoji emoji)
{
- Checks.notEmpty(id, "Id");
- Checks.notNull(emoji, "Emoji");
- Checks.notLonger(id, ID_MAX_LENGTH, "Id");
- return new ButtonImpl(id, "", ButtonStyle.SECONDARY, false, emoji);
+ return new ButtonImpl(id, "", ButtonStyle.SECONDARY, false, emoji).checkValid();
}
/**
@@ -408,11 +426,7 @@ static Button secondary(@Nonnull String id, @Nonnull Emoji emoji)
@Nonnull
static Button success(@Nonnull String id, @Nonnull String label)
{
- Checks.notEmpty(id, "Id");
- Checks.notEmpty(label, "Label");
- Checks.notLonger(id, ID_MAX_LENGTH, "Id");
- Checks.notLonger(label, LABEL_MAX_LENGTH, "Label");
- return new ButtonImpl(id, label, ButtonStyle.SUCCESS, false, null);
+ return new ButtonImpl(id, label, ButtonStyle.SUCCESS, false, null).checkValid();
}
/**
@@ -439,10 +453,7 @@ static Button success(@Nonnull String id, @Nonnull String label)
@Nonnull
static Button success(@Nonnull String id, @Nonnull Emoji emoji)
{
- Checks.notEmpty(id, "Id");
- Checks.notNull(emoji, "Emoji");
- Checks.notLonger(id, ID_MAX_LENGTH, "Id");
- return new ButtonImpl(id, "", ButtonStyle.SUCCESS, false, emoji);
+ return new ButtonImpl(id, "", ButtonStyle.SUCCESS, false, emoji).checkValid();
}
/**
@@ -469,11 +480,7 @@ static Button success(@Nonnull String id, @Nonnull Emoji emoji)
@Nonnull
static Button danger(@Nonnull String id, @Nonnull String label)
{
- Checks.notEmpty(id, "Id");
- Checks.notEmpty(label, "Label");
- Checks.notLonger(id, ID_MAX_LENGTH, "Id");
- Checks.notLonger(label, LABEL_MAX_LENGTH, "Label");
- return new ButtonImpl(id, label, ButtonStyle.DANGER, false, null);
+ return new ButtonImpl(id, label, ButtonStyle.DANGER, false, null).checkValid();
}
/**
@@ -500,10 +507,7 @@ static Button danger(@Nonnull String id, @Nonnull String label)
@Nonnull
static Button danger(@Nonnull String id, @Nonnull Emoji emoji)
{
- Checks.notEmpty(id, "Id");
- Checks.notNull(emoji, "Emoji");
- Checks.notLonger(id, ID_MAX_LENGTH, "Id");
- return new ButtonImpl(id, "", ButtonStyle.DANGER, false, emoji);
+ return new ButtonImpl(id, "", ButtonStyle.DANGER, false, emoji).checkValid();
}
/**
@@ -533,11 +537,7 @@ static Button danger(@Nonnull String id, @Nonnull Emoji emoji)
@Nonnull
static Button link(@Nonnull String url, @Nonnull String label)
{
- Checks.notEmpty(url, "URL");
- Checks.notEmpty(label, "Label");
- Checks.notLonger(url, URL_MAX_LENGTH, "URL");
- Checks.notLonger(label, LABEL_MAX_LENGTH, "Label");
- return new ButtonImpl(null, label, ButtonStyle.LINK, url, false, null);
+ return new ButtonImpl(null, label, ButtonStyle.LINK, url, null, false, null).checkValid();
}
/**
@@ -567,10 +567,29 @@ static Button link(@Nonnull String url, @Nonnull String label)
@Nonnull
static Button link(@Nonnull String url, @Nonnull Emoji emoji)
{
- Checks.notEmpty(url, "URL");
- Checks.notNull(emoji, "Emoji");
- Checks.notLonger(url, URL_MAX_LENGTH, "URL");
- return new ButtonImpl(null, "", ButtonStyle.LINK, url, false, emoji);
+ return new ButtonImpl(null, "", ButtonStyle.LINK, url, null, false, emoji).checkValid();
+ }
+
+ /**
+ * Creates a button with {@link ButtonStyle#PREMIUM PREMIUM} Style.
+ *
The button is enabled by default, and cannot have emojis attached to it.
+ * You can use {@link #asDisabled()} to further configure it.
+ *
+ * Note that premium buttons never send a {@link ButtonInteractionEvent ButtonInteractionEvent}.
+ * These buttons only open a modal about the SKU.
+ *
+ * @param sku
+ * The target SKU for this button
+ *
+ * @throws IllegalArgumentException
+ * If the provided SKU is {@code null}
+ *
+ * @return The button instance
+ */
+ @Nonnull
+ static Button premium(@Nonnull SkuSnowflake sku)
+ {
+ return new ButtonImpl(null, "", ButtonStyle.PREMIUM, null, sku, false, null).checkValid();
}
/**
@@ -578,6 +597,8 @@ static Button link(@Nonnull String url, @Nonnull Emoji emoji)
*
The button is enabled and has no emoji attached by default.
* You can use {@link #asDisabled()} and {@link #withEmoji(Emoji)} to further configure it.
*
+ *
This does not support premium buttons, use {@link #of(ButtonStyle, String, String, Emoji)} instead.
+ *
*
See {@link #link(String, String)} or {@link #primary(String, String)} for more details.
*
* @param style
@@ -590,6 +611,7 @@ static Button link(@Nonnull String url, @Nonnull Emoji emoji)
* @throws IllegalArgumentException
*
* - If any provided argument is null or empty.
+ * - If the requested style is {@link ButtonStyle#PREMIUM PREMIUM}.
* - If the id is longer than {@value #ID_MAX_LENGTH}, as defined by {@link #ID_MAX_LENGTH}.
* - If the url is longer than {@value #URL_MAX_LENGTH}, as defined by {@link #URL_MAX_LENGTH}.
* - If the character limit for {@code label}, defined by {@link #LABEL_MAX_LENGTH} as {@value #LABEL_MAX_LENGTH},
@@ -601,15 +623,10 @@ static Button link(@Nonnull String url, @Nonnull Emoji emoji)
@Nonnull
static Button of(@Nonnull ButtonStyle style, @Nonnull String idOrUrl, @Nonnull String label)
{
- Checks.check(style != ButtonStyle.UNKNOWN, "Cannot make button with unknown style!");
- Checks.notNull(style, "Style");
- Checks.notNull(label, "Label");
- Checks.notLonger(label, LABEL_MAX_LENGTH, "Label");
+ Checks.check(style != ButtonStyle.PREMIUM, "Premium buttons don't support labels");
if (style == ButtonStyle.LINK)
return link(idOrUrl, label);
- Checks.notEmpty(idOrUrl, "Id");
- Checks.notLonger(idOrUrl, ID_MAX_LENGTH, "Id");
- return new ButtonImpl(idOrUrl, label, style, false, null);
+ return new ButtonImpl(idOrUrl, label, style, false, null).checkValid();
}
/**
@@ -617,6 +634,8 @@ static Button of(@Nonnull ButtonStyle style, @Nonnull String idOrUrl, @Nonnull S
*
The button is enabled and has no text label.
* To use labels you can use {@code of(style, idOrUrl, label).withEmoji(emoji)}
*
+ * This does not support premium buttons, use {@link #of(ButtonStyle, String, String, Emoji)} instead.
+ *
*
See {@link #link(String, Emoji)} or {@link #primary(String, Emoji)} for more details.
*
* @param style
@@ -629,6 +648,7 @@ static Button of(@Nonnull ButtonStyle style, @Nonnull String idOrUrl, @Nonnull S
* @throws IllegalArgumentException
*
* - If any provided argument is null or empty.
+ * - If the requested style is {@link ButtonStyle#PREMIUM PREMIUM}.
* - If the id is longer than {@value #ID_MAX_LENGTH}, as defined by {@link #ID_MAX_LENGTH}.
* - If the url is longer than {@value #URL_MAX_LENGTH}, as defined by {@link #URL_MAX_LENGTH}.
*
@@ -638,14 +658,10 @@ static Button of(@Nonnull ButtonStyle style, @Nonnull String idOrUrl, @Nonnull S
@Nonnull
static Button of(@Nonnull ButtonStyle style, @Nonnull String idOrUrl, @Nonnull Emoji emoji)
{
- Checks.check(style != ButtonStyle.UNKNOWN, "Cannot make button with unknown style!");
- Checks.notNull(style, "Style");
- Checks.notNull(emoji, "Emoji");
+ Checks.check(style != ButtonStyle.PREMIUM, "Premium buttons don't support emojis");
if (style == ButtonStyle.LINK)
return link(idOrUrl, emoji);
- Checks.notEmpty(idOrUrl, "Id");
- Checks.notLonger(idOrUrl, ID_MAX_LENGTH, "Id");
- return new ButtonImpl(idOrUrl, "", style, false, emoji);
+ return new ButtonImpl(idOrUrl, "", style, false, emoji).checkValid();
}
/**
@@ -653,12 +669,13 @@ static Button of(@Nonnull ButtonStyle style, @Nonnull String idOrUrl, @Nonnull E
*
* You can use {@link #asDisabled()} to disable it.
*
- *
See {@link #link(String, String)} or {@link #primary(String, String)} for more details.
+ *
See {@link #link(String, String)}, {@link #premium(SkuSnowflake)}
+ * or {@link #primary(String, String)} for more details.
*
* @param style
* The button style
- * @param idOrUrl
- * Either the ID or URL for this button
+ * @param idOrUrlOrSku
+ * Either the ID, URL, or SKU for this button
* @param label
* The text to display on the button
* @param emoji
@@ -672,17 +689,18 @@ static Button of(@Nonnull ButtonStyle style, @Nonnull String idOrUrl, @Nonnull E
* or you provide an ID that is null, empty or longer than {@value #ID_MAX_LENGTH} characters, as defined by {@link #ID_MAX_LENGTH}.
* - The {@code label} is non-null and longer than {@value #LABEL_MAX_LENGTH} characters, as defined by {@link #LABEL_MAX_LENGTH}.
* - The {@code label} is null/empty, and the {@code emoji} is also null.
+ * - A label or emoji was provided for a {@link ButtonStyle#PREMIUM PREMIUM}-style button
*
*
* @return The button instance
*/
@Nonnull
- static Button of(@Nonnull ButtonStyle style, @Nonnull String idOrUrl, @Nullable String label, @Nullable Emoji emoji)
+ static Button of(@Nonnull ButtonStyle style, @Nonnull String idOrUrlOrSku, @Nullable String label, @Nullable Emoji emoji)
{
- if (label != null)
- return of(style, idOrUrl, label).withEmoji(emoji);
- else if (emoji != null)
- return of(style, idOrUrl, emoji);
- throw new IllegalArgumentException("Cannot build a button without a label and emoji. At least one has to be provided as non-null.");
+ if (style == ButtonStyle.LINK)
+ return new ButtonImpl(null, label, style, idOrUrlOrSku, null, false, emoji).checkValid();
+ if (style == ButtonStyle.PREMIUM)
+ return new ButtonImpl(null, label, style, null, SkuSnowflake.fromId(idOrUrlOrSku), false, emoji).checkValid();
+ return new ButtonImpl(idOrUrlOrSku, label, style, null, null, false, emoji).checkValid();
}
}
diff --git a/src/main/java/net/dv8tion/jda/api/interactions/components/buttons/ButtonStyle.java b/src/main/java/net/dv8tion/jda/api/interactions/components/buttons/ButtonStyle.java
index af228c73a6..9c06760906 100644
--- a/src/main/java/net/dv8tion/jda/api/interactions/components/buttons/ButtonStyle.java
+++ b/src/main/java/net/dv8tion/jda/api/interactions/components/buttons/ButtonStyle.java
@@ -40,6 +40,8 @@ public enum ButtonStyle
DANGER(4),
/** Link button style, usually in gray and has a link attached */
LINK(5),
+ /** Premium button style, usually in blurple and has a SKU attached */
+ PREMIUM(6),
;
private final int key;
diff --git a/src/main/java/net/dv8tion/jda/api/requests/restaction/interactions/InteractionCallbackAction.java b/src/main/java/net/dv8tion/jda/api/requests/restaction/interactions/InteractionCallbackAction.java
index 90d3b045bc..10c3363a8c 100644
--- a/src/main/java/net/dv8tion/jda/api/requests/restaction/interactions/InteractionCallbackAction.java
+++ b/src/main/java/net/dv8tion/jda/api/requests/restaction/interactions/InteractionCallbackAction.java
@@ -16,6 +16,8 @@
package net.dv8tion.jda.api.requests.restaction.interactions;
+import net.dv8tion.jda.api.entities.SkuSnowflake;
+import net.dv8tion.jda.api.interactions.components.buttons.Button;
import net.dv8tion.jda.api.requests.RestAction;
import javax.annotation.CheckReturnValue;
@@ -55,7 +57,13 @@ enum ResponseType
COMMAND_AUTOCOMPLETE_CHOICES(8),
/** Respond with a modal */
MODAL(9),
- /** Respond with the "Premium required" default Discord message for premium App subscriptions **/
+ /**
+ * Respond with the "Premium required" default Discord message for premium App subscriptions
+ *
+ * @deprecated Replaced with {@link Button#premium(SkuSnowflake)},
+ * see the Discord change logs for more details.
+ */
+ @Deprecated
PREMIUM_REQUIRED(10),
;
private final int raw;
diff --git a/src/main/java/net/dv8tion/jda/api/requests/restaction/interactions/PremiumRequiredCallbackAction.java b/src/main/java/net/dv8tion/jda/api/requests/restaction/interactions/PremiumRequiredCallbackAction.java
index 4d2031865a..b882845c57 100644
--- a/src/main/java/net/dv8tion/jda/api/requests/restaction/interactions/PremiumRequiredCallbackAction.java
+++ b/src/main/java/net/dv8tion/jda/api/requests/restaction/interactions/PremiumRequiredCallbackAction.java
@@ -16,13 +16,19 @@
package net.dv8tion.jda.api.requests.restaction.interactions;
+import net.dv8tion.jda.api.entities.SkuSnowflake;
+import net.dv8tion.jda.api.interactions.components.buttons.Button;
import net.dv8tion.jda.api.requests.FluentRestAction;
/**
* An {@link InteractionCallbackAction} that can be used to send the "Premium required" interaction response.
*
* @see net.dv8tion.jda.api.interactions.callbacks.IPremiumRequiredReplyCallback
+ *
+ * @deprecated Replaced with {@link Button#premium(SkuSnowflake)}
+ * see the Discord change logs for more details.
*/
+@Deprecated
public interface PremiumRequiredCallbackAction extends InteractionCallbackAction, FluentRestAction
{
diff --git a/src/main/java/net/dv8tion/jda/internal/entities/SkuSnowflakeImpl.java b/src/main/java/net/dv8tion/jda/internal/entities/SkuSnowflakeImpl.java
new file mode 100644
index 0000000000..29ec08137f
--- /dev/null
+++ b/src/main/java/net/dv8tion/jda/internal/entities/SkuSnowflakeImpl.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2015 Austin Keener, Michael Ritter, Florian Spieß, and the JDA contributors
+ *
+ * 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 net.dv8tion.jda.internal.entities;
+
+import net.dv8tion.jda.api.entities.SkuSnowflake;
+import net.dv8tion.jda.internal.utils.EntityString;
+
+public class SkuSnowflakeImpl implements SkuSnowflake
+{
+ protected final long id;
+
+ public SkuSnowflakeImpl(long id)
+ {
+ this.id = id;
+ }
+
+ @Override
+ public long getIdLong()
+ {
+ return this.id;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return Long.hashCode(id);
+ }
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (obj == this)
+ return true;
+ if (!(obj instanceof SkuSnowflakeImpl))
+ return false;
+ return ((SkuSnowflakeImpl) obj).getIdLong() == this.id;
+ }
+
+ @Override
+ public String toString()
+ {
+ return new EntityString(this).toString();
+ }
+}
diff --git a/src/main/java/net/dv8tion/jda/internal/interactions/component/ButtonImpl.java b/src/main/java/net/dv8tion/jda/internal/interactions/component/ButtonImpl.java
index 9a662de280..c2d81caa94 100644
--- a/src/main/java/net/dv8tion/jda/internal/interactions/component/ButtonImpl.java
+++ b/src/main/java/net/dv8tion/jda/internal/interactions/component/ButtonImpl.java
@@ -16,12 +16,14 @@
package net.dv8tion.jda.internal.interactions.component;
+import net.dv8tion.jda.api.entities.SkuSnowflake;
import net.dv8tion.jda.api.entities.emoji.Emoji;
import net.dv8tion.jda.api.entities.emoji.EmojiUnion;
import net.dv8tion.jda.api.interactions.components.buttons.Button;
import net.dv8tion.jda.api.interactions.components.buttons.ButtonStyle;
import net.dv8tion.jda.api.utils.data.DataObject;
import net.dv8tion.jda.internal.entities.EntityBuilder;
+import net.dv8tion.jda.internal.utils.Checks;
import net.dv8tion.jda.internal.utils.EntityString;
import javax.annotation.Nonnull;
@@ -34,6 +36,7 @@ public class ButtonImpl implements Button
private final String label;
private final ButtonStyle style;
private final String url;
+ private final SkuSnowflake sku;
private final boolean disabled;
private final EmojiUnion emoji;
@@ -44,25 +47,69 @@ public ButtonImpl(DataObject data)
data.getString("label", ""),
ButtonStyle.fromKey(data.getInt("style")),
data.getString("url", null),
+ data.hasKey("sku_id") ? SkuSnowflake.fromId(data.getLong("sku_id")) : null,
data.getBoolean("disabled"),
data.optObject("emoji").map(EntityBuilder::createEmoji).orElse(null));
}
public ButtonImpl(String id, String label, ButtonStyle style, boolean disabled, Emoji emoji)
{
- this(id, label, style, null, disabled, emoji);
+ this(id, label, style, null, null, disabled, emoji);
}
- public ButtonImpl(String id, String label, ButtonStyle style, String url, boolean disabled, Emoji emoji)
+ public ButtonImpl(String id, String label, ButtonStyle style, String url, SkuSnowflake sku, boolean disabled, Emoji emoji)
{
this.id = id;
this.label = label;
this.style = style;
this.url = url; // max length 512
+ this.sku = sku;
this.disabled = disabled;
this.emoji = (EmojiUnion) emoji;
}
+ public ButtonImpl checkValid()
+ {
+ Checks.notNull(style, "Style");
+ Checks.check(style != ButtonStyle.UNKNOWN, "Cannot make button with unknown style!");
+
+ switch (style)
+ {
+ case PRIMARY:
+ case SECONDARY:
+ case SUCCESS:
+ case DANGER:
+ Checks.check(url == null, "Cannot set an URL on action buttons");
+ Checks.check(sku == null, "Cannot set an SKU on action buttons");
+ Checks.check(emoji != null || (label != null && !label.isEmpty()), "Action buttons must have either an emoji or label");
+ Checks.notEmpty(id, "Id");
+ Checks.notLonger(id, ID_MAX_LENGTH, "Id");
+ break;
+ case LINK:
+ Checks.check(id == null, "Cannot set an ID on link buttons");
+ Checks.check(url != null, "You must set an URL on link buttons");
+ Checks.check(sku == null, "Cannot set an SKU on link buttons");
+ Checks.check(emoji != null || (label != null && !label.isEmpty()), "Link buttons must have either an emoji or label");
+ Checks.notEmpty(url, "URL");
+ Checks.notLonger(url, URL_MAX_LENGTH, "URL");
+ break;
+ case PREMIUM:
+ Checks.check(id == null, "Cannot set an ID on premium buttons");
+ Checks.check(url == null, "Cannot set an URL on premium buttons");
+ Checks.check(emoji == null, "Cannot set an emoji on premium buttons");
+ Checks.check(label == null || label.isEmpty(), "Cannot set a label on premium buttons");
+ Checks.notNull(sku, "SKU");
+ break;
+ }
+
+ if (label != null)
+ {
+ Checks.notLonger(label, LABEL_MAX_LENGTH, "Label");
+ }
+
+ return this;
+ }
+
@Nonnull
@Override
public Type getType()
@@ -98,6 +145,13 @@ public String getUrl()
return url;
}
+ @Nullable
+ @Override
+ public SkuSnowflake getSku()
+ {
+ return sku;
+ }
+
@Nullable
@Override
public EmojiUnion getEmoji()
@@ -117,15 +171,18 @@ public DataObject toData()
{
DataObject json = DataObject.empty();
json.put("type", 2);
- json.put("label", label);
+ if (!label.isEmpty())
+ json.put("label", label);
json.put("style", style.getKey());
json.put("disabled", disabled);
if (emoji != null)
json.put("emoji", emoji);
if (url != null)
json.put("url", url);
- else
+ else if (id != null)
json.put("custom_id", id);
+ else
+ json.put("sku_id", sku.getId());
return json;
}
diff --git a/src/main/java/net/dv8tion/jda/internal/utils/ComponentsUtil.java b/src/main/java/net/dv8tion/jda/internal/utils/ComponentsUtil.java
new file mode 100644
index 0000000000..6c24bd34b4
--- /dev/null
+++ b/src/main/java/net/dv8tion/jda/internal/utils/ComponentsUtil.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2015 Austin Keener, Michael Ritter, Florian Spieß, and the JDA contributors
+ *
+ * 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 net.dv8tion.jda.internal.utils;
+
+import net.dv8tion.jda.api.interactions.components.ActionComponent;
+import net.dv8tion.jda.api.interactions.components.buttons.Button;
+
+import javax.annotation.Nonnull;
+
+public class ComponentsUtil
+{
+ /** Checks whether the provided component has the {@code identifier} as its custom id, url or SKU id */
+ public static boolean isSameIdentifier(@Nonnull ActionComponent component, @Nonnull String identifier)
+ {
+ if (identifier.equals(component.getId()))
+ return true;
+
+ if (component instanceof Button)
+ {
+ final Button button = (Button) component;
+ if (identifier.equals(button.getUrl()))
+ return true;
+ if (button.getSku() != null)
+ return identifier.equals(button.getSku().getId());
+ }
+
+ return false;
+ }
+}
diff --git a/src/test/java/net/dv8tion/jda/test/interactions/ButtonTests.java b/src/test/java/net/dv8tion/jda/test/interactions/ButtonTests.java
new file mode 100644
index 0000000000..e45fce90fe
--- /dev/null
+++ b/src/test/java/net/dv8tion/jda/test/interactions/ButtonTests.java
@@ -0,0 +1,141 @@
+package net.dv8tion.jda.test.interactions;
+
+import net.dv8tion.jda.api.entities.SkuSnowflake;
+import net.dv8tion.jda.api.entities.emoji.Emoji;
+import net.dv8tion.jda.api.entities.emoji.UnicodeEmoji;
+import net.dv8tion.jda.api.interactions.components.buttons.Button;
+import net.dv8tion.jda.api.interactions.components.buttons.ButtonStyle;
+import net.dv8tion.jda.internal.interactions.component.ButtonImpl;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import java.util.stream.Stream;
+
+import static net.dv8tion.jda.api.interactions.components.buttons.ButtonStyle.*;
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+
+@SuppressWarnings("ResultOfMethodCallIgnored")
+public class ButtonTests
+{
+ private static final String EXAMPLE_ID = "id";
+ private static final String EXAMPLE_URL = "https://example.com";
+ private static final UnicodeEmoji EXAMPLE_EMOJI = Emoji.fromUnicode("🤔");
+ private static final String EXAMPLE_LABEL = "Label";
+ private static final SkuSnowflake EXAMPLE_SKU = SkuSnowflake.fromId(1234);
+
+ @MethodSource("testButtonValid")
+ @ParameterizedTest
+ void testButtonValid(ButtonStyle style, String id, String label, String url, SkuSnowflake sku, Emoji emoji)
+ {
+ ButtonImpl button = new ButtonImpl(id, label, style, url, sku, false, emoji);
+ assertDoesNotThrow(button::checkValid);
+ }
+
+ static Stream testButtonValid()
+ {
+ // The following button configurations are valid:
+ return Stream.of(
+ // Normal button; id, either label, emoji, label+emoji
+ Arguments.of(PRIMARY, "id", EXAMPLE_LABEL, null, null, null),
+ Arguments.of(PRIMARY, "id", EXAMPLE_LABEL, null, null, EXAMPLE_EMOJI),
+ Arguments.of(PRIMARY, "id", "", null, null, EXAMPLE_EMOJI),
+ // Link button; url, either label, emoji, label+emoji
+ Arguments.of(LINK, null, EXAMPLE_LABEL, EXAMPLE_URL, null, null),
+ Arguments.of(LINK, null, EXAMPLE_LABEL, EXAMPLE_URL, null, EXAMPLE_EMOJI),
+ Arguments.of(LINK, null, "", EXAMPLE_URL, null, EXAMPLE_EMOJI),
+ // Premium button doesn't have anything
+ Arguments.of(PREMIUM, null, "", null, EXAMPLE_SKU, null)
+ );
+ }
+
+ @MethodSource("testButtonInvalid")
+ @ParameterizedTest
+ void testButtonInvalid(ButtonStyle style, String id, String label, String url, SkuSnowflake sku, Emoji emoji)
+ {
+ ButtonImpl button = new ButtonImpl(id, label, style, url, sku, false, emoji);
+ assertThatIllegalArgumentException().isThrownBy(button::checkValid);
+ }
+
+ static Stream testButtonInvalid()
+ {
+ // The following button configuration will fail when:
+ return Stream.of(
+ // Normal button; has no id, has neither label/emoji, has url, has sku
+ Arguments.of(PRIMARY, null, EXAMPLE_LABEL, null, null, null),
+ Arguments.of(PRIMARY, "id", "", null, null, null),
+ Arguments.of(PRIMARY, "id", EXAMPLE_LABEL, EXAMPLE_URL, null, null),
+ Arguments.of(PRIMARY, "id", EXAMPLE_LABEL, null, EXAMPLE_SKU, null),
+ // Link button; has no url, has id, has sku
+ Arguments.of(LINK, null, EXAMPLE_LABEL, null, null, null),
+ Arguments.of(LINK, "id", EXAMPLE_LABEL, EXAMPLE_URL, null, null),
+ Arguments.of(LINK, null, EXAMPLE_LABEL, EXAMPLE_URL, EXAMPLE_SKU, null),
+ // Premium button; has no sku, has id, has url, has label, has emoji
+ Arguments.of(PREMIUM, null, "", null, null, null),
+ Arguments.of(PREMIUM, "id", "", null, EXAMPLE_SKU, null),
+ Arguments.of(PREMIUM, null, "", "url", EXAMPLE_SKU, null),
+ Arguments.of(PREMIUM, null, EXAMPLE_LABEL, null, EXAMPLE_SKU, null),
+ Arguments.of(PREMIUM, null, "", null, EXAMPLE_SKU, EXAMPLE_EMOJI)
+ );
+ }
+
+ @Test
+ void testPrimaryWithSku()
+ {
+ assertThatIllegalArgumentException()
+ .isThrownBy(() -> Button.primary("id", EXAMPLE_LABEL).withSku(EXAMPLE_SKU));
+ }
+
+ @Test
+ void testPrimaryWithUrl()
+ {
+ assertThatIllegalArgumentException()
+ .isThrownBy(() -> Button.primary("id", EXAMPLE_LABEL).withUrl(EXAMPLE_URL));
+ }
+
+ @Test
+ void linkWithId()
+ {
+ assertThatIllegalArgumentException()
+ .isThrownBy(() -> Button.link(EXAMPLE_URL, EXAMPLE_LABEL).withId(EXAMPLE_ID));
+ }
+
+ @Test
+ void linkWithSku()
+ {
+ assertThatIllegalArgumentException()
+ .isThrownBy(() -> Button.link(EXAMPLE_URL, EXAMPLE_LABEL).withSku(EXAMPLE_SKU));
+ }
+
+ @Test
+ void testPremiumWithId()
+ {
+ assertThatIllegalArgumentException()
+ .isThrownBy(() -> Button.premium(EXAMPLE_SKU).withLabel(EXAMPLE_ID));
+ }
+
+ @Test
+ void testPremiumWithLabel()
+ {
+ assertThatIllegalArgumentException()
+ .isThrownBy(() -> Button.premium(EXAMPLE_SKU).withLabel(EXAMPLE_LABEL));
+ }
+
+
+ @Test
+ void testPremiumWithEmoji()
+ {
+ assertThatIllegalArgumentException()
+ .isThrownBy(() -> Button.premium(EXAMPLE_SKU).withEmoji(EXAMPLE_EMOJI));
+ }
+
+
+ @Test
+ void testPremiumWithUrl()
+ {
+ assertThatIllegalArgumentException()
+ .isThrownBy(() -> Button.premium(EXAMPLE_SKU).withUrl(EXAMPLE_URL));
+ }
+}
diff --git a/src/test/java/net/dv8tion/jda/test/util/ComponentsUtilTest.java b/src/test/java/net/dv8tion/jda/test/util/ComponentsUtilTest.java
new file mode 100644
index 0000000000..534e648229
--- /dev/null
+++ b/src/test/java/net/dv8tion/jda/test/util/ComponentsUtilTest.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2015 Austin Keener, Michael Ritter, Florian Spieß, and the JDA contributors
+ *
+ * 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 net.dv8tion.jda.test.util;
+
+import net.dv8tion.jda.api.interactions.components.buttons.Button;
+import net.dv8tion.jda.api.interactions.components.buttons.ButtonStyle;
+import net.dv8tion.jda.internal.utils.ComponentsUtil;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import java.util.stream.Stream;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class ComponentsUtilTest
+{
+ @MethodSource("testCustomId")
+ @ParameterizedTest
+ void testCustomId(String customId, ButtonStyle style, String label, boolean expected)
+ {
+ final Button button = Button.of(style, customId, label, null);
+ assertThat(ComponentsUtil.isSameIdentifier(button, "id")).isEqualTo(expected);
+ }
+
+ static Stream testCustomId()
+ {
+ return Stream.of(
+ Arguments.of("id", ButtonStyle.PRIMARY, "Label", true),
+ Arguments.of("http://localhost:8080", ButtonStyle.LINK, "Label", false),
+ Arguments.of("1234", ButtonStyle.PREMIUM, "", false)
+ );
+ }
+}