Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add premium buttons #2752

Merged
merged 22 commits into from
Nov 3, 2024
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions src/main/java/net/dv8tion/jda/api/entities/SkuSnowflake.java
Original file line number Diff line number Diff line change
@@ -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.
*
* <p>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));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ public ModalCallbackAction replyModal(@Nonnull Modal modal)

@Nonnull
@Override
@Deprecated
@CheckReturnValue
public PremiumRequiredCallbackAction replyWithPremiumRequired()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ public ModalCallbackAction replyModal(@Nonnull Modal modal)

@Nonnull
@Override
@Deprecated
@CheckReturnValue
public PremiumRequiredCallbackAction replyWithPremiumRequired()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,6 @@
* <br>Which supports choice suggestions for auto-complete interactions via {@link IAutoCompleteCallback#replyChoices(Command.Choice...)}</li>
* <li>{@link IModalCallback}
* <br>Which supports replying using a {@link Modal} via {@link IModalCallback#replyModal(Modal)}</li>
* <li>{@link IPremiumRequiredReplyCallback}
* <br>Which will reply stating that an {@link Entitlement Entitlement} is required</li>
* </ul>
*
* <p>Once the interaction is acknowledged, you can not reply with these methods again. If the interaction is a {@link IDeferrableCallback deferrable},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -28,10 +30,19 @@
* <p>Replying with {@link #replyWithPremiumRequired()} will automatically acknowledge this interaction.
*
* <p><b>Note:</b>This interaction requires <a href="https://discord.com/developers/docs/monetization/overview" target="_blank">monetization</a> to be enabled.
*
* @deprecated Replaced with {@link Button#premium(SkuSnowflake, String)},
* see the <a href="https://discord.com/developers/docs/change-log#premium-apps-new-premium-button-style-deep-linking-url-schemes" target="_blank">Discord change logs</a> for more details.
*/
@Deprecated
public interface IPremiumRequiredReplyCallback extends IDeferrableCallback
{
/**
* @deprecated Replaced with {@link Button#premium(SkuSnowflake, String)},
* see the <a href="https://discord.com/developers/docs/change-log#premium-apps-new-premium-button-style-deep-linking-url-schemes" target="_blank">Discord change logs</a> for more details.
*/
@Nonnull
@Deprecated
@CheckReturnValue
PremiumRequiredCallbackAction replyWithPremiumRequired();
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -207,7 +208,8 @@ default boolean isValid()
* <br>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
*
Expand All @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -114,6 +115,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.
* <br>This can be either {@link Emoji.Type#UNICODE unicode} or {@link Emoji.Type#CUSTOM custom}.
Expand Down Expand Up @@ -143,7 +152,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());
}

/**
Expand All @@ -158,7 +167,8 @@ default Button withDisabled(boolean disabled)
@CheckReturnValue
default Button withEmoji(@Nullable Emoji emoji)
{
return new ButtonImpl(getId(), getLabel(), getStyle(), getUrl(), isDisabled(), emoji);
Checks.check(getStyle() != ButtonStyle.PREMIUM, "Premium components cannot have emojis");
freya022 marked this conversation as resolved.
Show resolved Hide resolved
return new ButtonImpl(getId(), getLabel(), getStyle(), getUrl(), null, isDisabled(), emoji);
}

/**
Expand All @@ -182,7 +192,7 @@ 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());
}

/**
Expand All @@ -206,7 +216,7 @@ 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());
}

/**
Expand All @@ -230,7 +240,27 @@ 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(), ButtonStyle.LINK, url, null, isDisabled(), getEmoji());
}

/**
* 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)
{
Checks.notNull(sku, "SKU");
Checks.check(getStyle() == ButtonStyle.PREMIUM, "You can only set an SKU on Premium buttons");
freya022 marked this conversation as resolved.
Show resolved Hide resolved
return new ButtonImpl(null, getLabel(), ButtonStyle.PREMIUM, null, sku, isDisabled(), null);
freya022 marked this conversation as resolved.
Show resolved Hide resolved
}

/**
Expand Down Expand Up @@ -259,7 +289,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());
}

/**
Expand Down Expand Up @@ -537,7 +571,7 @@ static Button link(@Nonnull String url, @Nonnull String label)
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);
}

/**
Expand Down Expand Up @@ -570,7 +604,38 @@ 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);
}

/**
* Creates a button with {@link ButtonStyle#PREMIUM PREMIUM} Style.
* <br>The button is enabled by default, and cannot have emojis attached to it.
* You can use {@link #asDisabled()} to further configure it.
*
* <p>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
* @param label
* The text to display on the button
*
* @throws IllegalArgumentException
* <ul>
* <li>If any provided argument is null or empty.</li>
* <li>If the character limit for {@code label}, defined by {@link #LABEL_MAX_LENGTH} as {@value #LABEL_MAX_LENGTH},
freya022 marked this conversation as resolved.
Show resolved Hide resolved
* is exceeded.</li>
* </ul>
*
* @return The button instance
*/
@Nonnull
static Button premium(@Nonnull SkuSnowflake sku, @Nonnull String label)
{
Checks.notNull(sku, "SKU");
Checks.notEmpty(label, "Label");
Checks.notLonger(label, LABEL_MAX_LENGTH, "Label");
return new ButtonImpl(null, label, ButtonStyle.PREMIUM, null, sku, false, null);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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, String)},
* see the <a href="https://discord.com/developers/docs/change-log#premium-apps-new-premium-button-style-deep-linking-url-schemes" target="_blank">Discord change logs</a> for more details.
*/
@Deprecated
PREMIUM_REQUIRED(10),
;
private final int raw;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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, String)}
* see the <a href="https://discord.com/developers/docs/change-log#premium-apps-new-premium-button-style-deep-linking-url-schemes" target="_blank">Discord change logs</a> for more details.
*/
@Deprecated
public interface PremiumRequiredCallbackAction extends InteractionCallbackAction<Void>, FluentRestAction<Void, PremiumRequiredCallbackAction>
{

Expand Down
Loading