net.dv8tion.jda.api.interactions.commands.build.OptionData Maven / Gradle / Ivy
Show all versions of JDA Show documentation
/*
* 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.interactions.commands.build;
import net.dv8tion.jda.api.entities.channel.ChannelType;
import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent;
import net.dv8tion.jda.api.interactions.DiscordLocale;
import net.dv8tion.jda.api.interactions.commands.Command;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.localization.LocalizationMap;
import net.dv8tion.jda.api.utils.data.DataArray;
import net.dv8tion.jda.api.utils.data.DataObject;
import net.dv8tion.jda.api.utils.data.DataType;
import net.dv8tion.jda.api.utils.data.SerializableData;
import net.dv8tion.jda.internal.utils.Checks;
import net.dv8tion.jda.internal.utils.localization.LocalizationUtils;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.*;
import java.util.stream.Collectors;
/**
* Builder for a Slash-Command option.
*/
public class OptionData implements SerializableData
{
/**
* The highest positive amount Discord allows the {@link OptionType#NUMBER NUMBER} type to be.
*/
public static final double MAX_POSITIVE_NUMBER = (1L << 53) - 1; // 1L << 53 is non-inclusive for Discord
/**
* The largest negative amount Discord allows the {@link OptionType#NUMBER NUMBER} type to be.
*/
public static final double MIN_NEGATIVE_NUMBER = -(1L << 53) + 1; // 1L << 53 is non-inclusive for Discord
/**
* The maximum length the name of an option can be.
*/
public static final int MAX_NAME_LENGTH = 32;
/**
* The maximum length of the name of Command Option Choice names
*/
public static final int MAX_CHOICE_NAME_LENGTH = 100;
/**
* The maximum length the description of an option can be.
*/
public static final int MAX_DESCRIPTION_LENGTH = 100;
/**
* The maximum length a {@link OptionType#STRING String value} for a choice can be.
*/
public static final int MAX_CHOICE_VALUE_LENGTH = 100;
/**
* The total amount of {@link #getChoices() choices} you can set.
*/
public static final int MAX_CHOICES = 25;
/**
* The maximum length for a {@link OptionType#STRING String option}.
*/
public static final int MAX_STRING_OPTION_LENGTH = 6000;
private final OptionType type;
private String name, description;
private final LocalizationMap nameLocalizations = new LocalizationMap(this::checkName);
private final LocalizationMap descriptionLocalizations = new LocalizationMap(this::checkDescription);
private boolean isRequired, isAutoComplete;
private final EnumSet channelTypes = EnumSet.noneOf(ChannelType.class);
private Number minValue;
private Number maxValue;
private Integer minLength, maxLength;
private List choices;
/**
* Create an option builder.
*
This option is not {@link #isRequired() required} by default.
*
* @param type
* The {@link OptionType}
* @param name
* The option name, up to {@value #MAX_NAME_LENGTH} alphanumeric (with dash) lowercase characters, as
* defined by {@link #MAX_NAME_LENGTH}
* @param description
* The option description, up to {@value #MAX_DESCRIPTION_LENGTH} characters, as defined by {@link #MAX_DESCRIPTION_LENGTH}
*
* @throws IllegalArgumentException
*
* - If {@code type} is null
* - If {@code type} is {@link OptionType#UNKNOWN UNKNOWN}
* - If {@code name} is not alphanumeric (with dash), lowercase and between 1 and {@value #MAX_NAME_LENGTH} characters long
* - If {@code description} is not between 1 and {@value #MAX_DESCRIPTION_LENGTH} characters long
*
*/
public OptionData(@Nonnull OptionType type, @Nonnull String name, @Nonnull String description)
{
this(type, name, description, false);
}
/**
* Create an option builder.
*
* @param type
* The {@link OptionType}
* @param name
* The option name, up to {@value #MAX_NAME_LENGTH} alphanumeric (with dash) lowercase characters, as
* defined by {@link #MAX_NAME_LENGTH}
* @param description
* The option description, up to {@value #MAX_DESCRIPTION_LENGTH} characters, as defined by {@link #MAX_DESCRIPTION_LENGTH}
* @param isRequired
* {@code True}, if this option is required
*
* @throws IllegalArgumentException
*
* - If {@code type} is null
* - If {@code type} is {@link OptionType#UNKNOWN UNKNOWN}
* - If {@code name} is not alphanumeric (with dash), lowercase and between 1 and {@value #MAX_NAME_LENGTH} characters long
* - If {@code description} is not between 1 and {@value #MAX_DESCRIPTION_LENGTH} characters long
*
*/
public OptionData(@Nonnull OptionType type, @Nonnull String name, @Nonnull String description, boolean isRequired)
{
this(type, name, description, isRequired, false);
}
/**
* Create an option builder.
*
* @param type
* The {@link OptionType}
* @param name
* The option name, up to {@value #MAX_NAME_LENGTH} alphanumeric (with dash) lowercase characters, as
* defined by {@link #MAX_NAME_LENGTH}
* @param description
* The option description, up to {@value #MAX_DESCRIPTION_LENGTH} characters, as defined by {@link #MAX_DESCRIPTION_LENGTH}
* @param isRequired
* {@code True}, if this option is required
* @param isAutoComplete
* True, if auto-complete should be supported (requires {@link OptionType#canSupportChoices()})
*
* @throws IllegalArgumentException
*
* - If {@code type} is null
* - If {@code type} is {@link OptionType#UNKNOWN UNKNOWN}, {@link OptionType#SUB_COMMAND SUB_COMMAND}, or {@link OptionType#SUB_COMMAND_GROUP SUB_COMMAND_GROUP}
* - If {@code name} is not alphanumeric (with dash), lowercase and between 1 and {@value #MAX_NAME_LENGTH} characters long
* - If {@code description} is not between 1 and {@value #MAX_DESCRIPTION_LENGTH} characters long
* - If {@link OptionType#canSupportChoices()} is false and {@code isAutoComplete} is true
*
*/
public OptionData(@Nonnull OptionType type, @Nonnull String name, @Nonnull String description, boolean isRequired, boolean isAutoComplete)
{
Checks.notNull(type, "Type");
Checks.check(type != OptionType.UNKNOWN, "Cannot make option of unknown type!");
Checks.check(type != OptionType.SUB_COMMAND, "Cannot make a subcommand group with OptionData. Use addSubcommands(...) instead!");
Checks.check(type != OptionType.SUB_COMMAND_GROUP, "Cannot make a subcommand group with OptionData. Use addSubcommandGroups(...) instead!");
this.type = type;
setName(name);
setDescription(description);
setRequired(isRequired);
if (type.canSupportChoices())
choices = new ArrayList<>();
setAutoComplete(isAutoComplete);
}
protected void checkName(@Nonnull String name)
{
Checks.notEmpty(name, "Name");
Checks.notLonger(name, MAX_NAME_LENGTH, "Name");
Checks.isLowercase(name, "Name");
Checks.matches(name, Checks.ALPHANUMERIC_WITH_DASH, "Name");
}
protected void checkDescription(@Nonnull String description)
{
Checks.notEmpty(description, "Description");
Checks.notLonger(description, MAX_DESCRIPTION_LENGTH, "Description");
}
/**
* The {@link OptionType} for this option
*
* @return The {@link OptionType}
*/
@Nonnull
public OptionType getType()
{
return type;
}
/**
* The name for this option
*
* @return The name
*/
@Nonnull
public String getName()
{
return name;
}
/**
* The localizations of this option's name for {@link DiscordLocale various languages}.
*
* @return The {@link LocalizationMap} containing the mapping from {@link DiscordLocale} to the localized name
*/
@Nonnull
public LocalizationMap getNameLocalizations()
{
return nameLocalizations;
}
/**
* The description for this option
*
* @return The description
*/
@Nonnull
public String getDescription()
{
return description;
}
/**
* The localizations of this option's description for {@link DiscordLocale various languages}.
*
* @return The {@link LocalizationMap} containing the mapping from {@link DiscordLocale} to the localized description
*/
@Nonnull
public LocalizationMap getDescriptionLocalizations()
{
return descriptionLocalizations;
}
/**
* Whether this option is required.
*
This can be configured with {@link #setRequired(boolean)}.
*
* Required options must always be set by the command invocation.
*
* @return True, if this option is required
*/
public boolean isRequired()
{
return isRequired;
}
/**
* Whether this option supports auto-complete interactions
* via {@link CommandAutoCompleteInteractionEvent}.
*
* @return True, if this option supports auto-complete
*/
public boolean isAutoComplete()
{
return isAutoComplete;
}
/**
* The {@link ChannelType ChannelTypes} this option is restricted to.
*
This is empty if the option is not of type {@link OptionType#CHANNEL CHANNEL} or not restricted to specific types.
*
* @return {@link EnumSet} of {@link ChannelType}
*/
@Nonnull
public EnumSet getChannelTypes()
{
return channelTypes;
}
/**
* The minimum value which can be provided for this option.
*
This returns {@code null} if the value is not set or if the option
* is not of type {@link OptionType#INTEGER INTEGER} or {@link OptionType#NUMBER NUMBER}.
*
* @return The minimum value for this option
*/
@Nullable
public Number getMinValue()
{
return minValue;
}
/**
* The maximum value which can be provided for this option.
*
This returns {@code null} if the value is not set or if the option
* is not of type {@link OptionType#INTEGER INTEGER} or {@link OptionType#NUMBER NUMBER}.
*
* @return The maximum value for this option
*/
@Nullable
public Number getMaxValue()
{
return maxValue;
}
/**
* The minimum length for strings which can be provided for this option.
*
This returns {@code null} if the value is not set or if the option
* is not of type {@link OptionType#STRING STRING}.
*
* @return The minimum length for strings for this option or {@code null}
*/
@Nullable
public Integer getMinLength()
{
return minLength;
}
/**
* The maximum length for strings which can be provided for this option.
*
This returns {@code null} if the value is not set or if the option
* is not of type {@link OptionType#STRING STRING}.
*
* @return The maximum length for strings for this option or {@code null}
*/
@Nullable
public Integer getMaxLength()
{
return maxLength;
}
/**
* The choices for this option.
*
This is empty by default and can only be configured for specific option types.
*
* @return Immutable list of {@link Command.Choice Choices}
*
* @see #addChoice(String, long)
* @see #addChoice(String, String)
*/
@Nonnull
public List getChoices()
{
if (choices == null || choices.isEmpty())
return Collections.emptyList();
return Collections.unmodifiableList(choices);
}
/**
* Configure the name
*
* @param name
* The lowercase alphanumeric (with dash) name, {@link #MAX_NAME_LENGTH 1-32 characters long}
*
* @throws IllegalArgumentException
* If the name is null, empty, not alphanumeric, or not between 1-{@value #MAX_NAME_LENGTH} characters long,
* as defined by {@link #MAX_NAME_LENGTH}
*
* @return The OptionData instance, for chaining
*/
@Nonnull
public OptionData setName(@Nonnull String name)
{
checkName(name);
this.name = name;
return this;
}
/**
* Sets a {@link DiscordLocale language-specific} localization of this option's name.
*
* @param locale
* The locale to associate the translated name with
*
* @param name
* The translated name to put
*
* @throws IllegalArgumentException
*
* - If the locale is null
* - If the name is null
* - If the locale is {@link DiscordLocale#UNKNOWN}
* - If the name does not pass the corresponding {@link #setName(String) name check}
*
*
* @return This builder instance, for chaining
*/
@Nonnull
public OptionData setNameLocalization(@Nonnull DiscordLocale locale, @Nonnull String name)
{
//Checks are done in LocalizationMap
nameLocalizations.setTranslation(locale, name);
return this;
}
/**
* Sets multiple {@link DiscordLocale language-specific} localizations of this option's name.
*
* @param map
* The map from which to transfer the translated names
*
* @throws IllegalArgumentException
*
* - If the map is null
* - If the map contains an {@link DiscordLocale#UNKNOWN} key
* - If the map contains a name which does not pass the corresponding {@link #setName(String) name check}
*
*
* @return This builder instance, for chaining
*/
@Nonnull
public OptionData setNameLocalizations(@Nonnull Map map)
{
//Checks are done in LocalizationMap
nameLocalizations.setTranslations(map);
return this;
}
/**
* Configure the description
*
* @param description
* The description, 1-{@value #MAX_DESCRIPTION_LENGTH} characters, as defined by {@link #MAX_DESCRIPTION_LENGTH}
*
* @throws IllegalArgumentException
* If the name is null, empty, or longer than {@value #MAX_DESCRIPTION_LENGTH}, as defined by {@link #MAX_DESCRIPTION_LENGTH}
*
* @return The OptionData instance, for chaining
*/
@Nonnull
public OptionData setDescription(@Nonnull String description)
{
checkDescription(description);
this.description = description;
return this;
}
/**
* Sets a {@link DiscordLocale language-specific} localization of this option's description.
*
* @param locale
* The locale to associate the translated description with
*
* @param description
* The translated description to put
*
* @throws IllegalArgumentException
*
* - If the locale is null
* - If the description is null
* - If the locale is {@link DiscordLocale#UNKNOWN}
* - If the description does not pass the corresponding {@link #setDescription(String) description check}
*
*
* @return This builder instance, for chaining
*/
@Nonnull
public OptionData setDescriptionLocalization(@Nonnull DiscordLocale locale, @Nonnull String description)
{
//Checks are done in LocalizationMap
descriptionLocalizations.setTranslation(locale, description);
return this;
}
/**
* Sets multiple {@link DiscordLocale language-specific} localizations of this option's description.
*
* @param map
* The map from which to transfer the translated descriptions
*
* @throws IllegalArgumentException
*
* - If the map is null
* - If the map contains an {@link DiscordLocale#UNKNOWN} key
* - If the map contains a description which does not pass the corresponding {@link #setDescription(String) description check}
*
*
* @return This builder instance, for chaining
*/
@Nonnull
public OptionData setDescriptionLocalizations(@Nonnull Map map)
{
//Checks are done in LocalizationMap
descriptionLocalizations.setTranslations(map);
return this;
}
/**
* Configure whether the user must set this option.
*
Required options must always be filled out when using the command.
*
* @param required
* True, if this option is required
*
* @return The OptionData instance, for chaining
*/
@Nonnull
public OptionData setRequired(boolean required)
{
this.isRequired = required;
return this;
}
/**
* Configure whether this option should support auto-complete interactions
* via {@link CommandAutoCompleteInteractionEvent}.
*
* This is only supported for options which support choices. See {@link OptionType#canSupportChoices()}.
*
* @param autoComplete
* True, if auto-complete should be supported
*
* @throws IllegalStateException
* If this option is already configured to use choices or the option type does not support auto-complete
*
* @return The OptionData instance, for chaining
*/
@Nonnull
public OptionData setAutoComplete(boolean autoComplete)
{
if (autoComplete)
{
if (choices == null || !type.canSupportChoices())
throw new IllegalStateException("Cannot enable auto-complete for options of type " + type);
if (!choices.isEmpty())
throw new IllegalStateException("Cannot enable auto-complete for options with choices");
}
isAutoComplete = autoComplete;
return this;
}
/**
* Configure the {@link ChannelType ChannelTypes} to restrict this option to.
* This only applies to options of type {@link OptionType#CHANNEL CHANNEL}.
*
* @param channelTypes
* The {@link ChannelType ChannelTypes} to restrict this option to
* or empty array to accept all {@link ChannelType ChannelTypes}
*
* @throws IllegalArgumentException
*
* - If {@link OptionType type of this option} is not {@link OptionType#CHANNEL CHANNEL}
* - If {@code channelTypes} contain {@code null}
* - If {@code channelTypes} contains non-guild channels
*
*
* @return The OptionData instance, for chaining
*/
@Nonnull
public OptionData setChannelTypes(@Nonnull ChannelType... channelTypes)
{
Checks.noneNull(channelTypes, "ChannelTypes");
return setChannelTypes(Arrays.asList(channelTypes));
}
/**
* Configure the {@link ChannelType ChannelTypes} to restrict this option to.
* This only applies to options of type {@link OptionType#CHANNEL CHANNEL}.
*
* @param channelTypes
* The {@link ChannelType ChannelTypes} to restrict this option to
* or empty collection to accept all {@link ChannelType ChannelTypes}
*
* @throws IllegalArgumentException
*
* - If {@link OptionType type of this option} is not {@link OptionType#CHANNEL CHANNEL}
* - If {@code channelTypes} is null
* - If {@code channelTypes} contain {@code null}
* - If {@code channelTypes} contains non-guild channels
*
*
* @return The OptionData instance, for chaining
*/
@Nonnull
public OptionData setChannelTypes(@Nonnull Collection channelTypes)
{
if (type != OptionType.CHANNEL)
throw new IllegalArgumentException("Can only apply channel type restriction to options of type CHANNEL");
Checks.notNull(channelTypes, "ChannelType collection");
Checks.noneNull(channelTypes, "ChannelType");
for (ChannelType channelType : channelTypes)
{
if (!channelType.isGuild())
throw new IllegalArgumentException("Provided channel type is not a guild channel type. Provided: " + channelType);
}
this.channelTypes.clear();
this.channelTypes.addAll(channelTypes);
return this;
}
/**
* Configure the minimal value which can be provided for this option.
*
* @param value
* The minimal value which can be provided for this option.
* @throws IllegalArgumentException
*
* - If {@link OptionType type of this option} is not {@link OptionType#INTEGER INTEGER} or {@link OptionType#NUMBER NUMBER}
* - If {@code value} is less than {@link OptionData#MIN_NEGATIVE_NUMBER MIN_NEGATIVE_NUMBER}
*
*
* @return The OptionData instance, for chaining
*/
@Nonnull
public OptionData setMinValue(long value)
{
if (type != OptionType.INTEGER && type != OptionType.NUMBER)
throw new IllegalArgumentException("Can only set min and max long value for options of type INTEGER or NUMBER");
Checks.check(value >= MIN_NEGATIVE_NUMBER, "Long value may not be less than %f", MIN_NEGATIVE_NUMBER);
this.minValue = value;
return this;
}
/**
* Configure the minimal value which can be provided for this option.
*
* @param value
* The minimal value which can be provided for this option.
* @throws IllegalArgumentException
*
* - If {@link OptionType type of this option} is not {@link OptionType#NUMBER NUMBER}
* - If {@code value} is less than {@link OptionData#MIN_NEGATIVE_NUMBER MIN_NEGATIVE_NUMBER}
*
*
* @return The OptionData instance, for chaining
*/
@Nonnull
public OptionData setMinValue(double value)
{
if (type != OptionType.NUMBER)
throw new IllegalArgumentException("Can only set min double value for options of type NUMBER");
Checks.check(value >= MIN_NEGATIVE_NUMBER, "Double value may not be less than %f", MIN_NEGATIVE_NUMBER);
this.minValue = value;
return this;
}
/**
* Configure the maximal value which can be provided for this option.
*
* @param value
* The maximal value which can be provided for this option.
* @throws IllegalArgumentException
*
* - If {@link OptionType type of this option} is not {@link OptionType#INTEGER INTEGER} or {@link OptionType#NUMBER NUMBER}
* - If {@code value} is greater than {@link OptionData#MAX_POSITIVE_NUMBER MAX_POSITIVE_NUMBER}
*
*
* @return The OptionData instance, for chaining
*/
@Nonnull
public OptionData setMaxValue(long value)
{
if (type != OptionType.INTEGER && type != OptionType.NUMBER)
throw new IllegalArgumentException("Can only set min and max long value for options of type INTEGER or NUMBER");
Checks.check(value <= MAX_POSITIVE_NUMBER, "Long value may not be greater than %f", MAX_POSITIVE_NUMBER);
this.maxValue = value;
return this;
}
/**
* Configure the maximal value which can be provided for this option.
*
* @param value
* The maximal value which can be provided for this option.
* @throws IllegalArgumentException
*
* - If {@link OptionType type of this option} is not {@link OptionType#NUMBER NUMBER}
* - If {@code value} is greater than {@link OptionData#MAX_POSITIVE_NUMBER MAX_POSITIVE_NUMBER}
*
*
* @return The OptionData instance, for chaining
*/
@Nonnull
public OptionData setMaxValue(double value)
{
if (type != OptionType.NUMBER)
throw new IllegalArgumentException("Can only set max double value for options of type NUMBER");
Checks.check(value <= MAX_POSITIVE_NUMBER, "Double value may not be greater than %f", MAX_POSITIVE_NUMBER);
this.maxValue = value;
return this;
}
/**
* Configure the minimal and maximal value which can be provided for this option.
*
* @param minValue
* The minimal value which can be provided for this option.
* @param maxValue
* The maximal value which can be provided for this option.
* @throws IllegalArgumentException
*
* - If {@link OptionType type of this option} is not {@link OptionType#INTEGER INTEGER} or {@link OptionType#NUMBER NUMBER}
* - If {@code minValue} is less than or not equal to {@link OptionData#MIN_NEGATIVE_NUMBER MIN_NEGATIVE_NUMBER}
* - If {@code maxValue} is greater than {@link OptionData#MAX_POSITIVE_NUMBER MAX_POSITIVE_NUMBER}
*
*
* @return The OptionData instance, for chaining
*/
@Nonnull
public OptionData setRequiredRange(long minValue, long maxValue)
{
if (type != OptionType.INTEGER && type != OptionType.NUMBER)
throw new IllegalArgumentException("Can only set min and max long value for options of type INTEGER or NUMBER");
Checks.check(minValue >= MIN_NEGATIVE_NUMBER, "Long value may not be less than %f", MIN_NEGATIVE_NUMBER);
Checks.check(maxValue <= MAX_POSITIVE_NUMBER, "Long value may not be greater than %f", MAX_POSITIVE_NUMBER);
this.minValue = minValue;
this.maxValue = maxValue;
return this;
}
/**
* Configure the minimal and maximal value which can be provided for this option.
*
* @param minValue
* The minimal value which can be provided for this option.
* @param maxValue
* The maximal value which can be provided for this option.
* @throws IllegalArgumentException
*
* - If {@link OptionType type of this option} is not {@link OptionType#NUMBER NUMBER}
* - If {@code minValue} is less than or not equal to {@link OptionData#MIN_NEGATIVE_NUMBER MIN_NEGATIVE_NUMBER}
* - If {@code maxValue} is greater than {@link OptionData#MAX_POSITIVE_NUMBER MAX_POSITIVE_NUMBER}
*
*
* @return The OptionData instance, for chaining
*/
@Nonnull
public OptionData setRequiredRange(double minValue, double maxValue)
{
if (type != OptionType.NUMBER)
throw new IllegalArgumentException("Can only set min and max double value for options of type NUMBER");
Checks.check(minValue >= MIN_NEGATIVE_NUMBER, "Double value may not be less than %f", MIN_NEGATIVE_NUMBER);
Checks.check(maxValue <= MAX_POSITIVE_NUMBER, "Double value may not be greater than %f", MAX_POSITIVE_NUMBER);
this.minValue = minValue;
this.maxValue = maxValue;
return this;
}
/**
* Configure the minimum length for strings which can be provided for this option.
*
* @param minLength
* The minimum length for strings which can be provided for this option.
* @throws IllegalArgumentException
*
* - If {@link OptionType type of this option} is not {@link OptionType#STRING STRING}
* - If {@code minLength} is not positive
*
*
* @return The OptionData instance, for chaining
*/
@Nonnull
public OptionData setMinLength(int minLength)
{
if (type != OptionType.STRING)
throw new IllegalArgumentException("Can only set min length for options of type STRING");
Checks.positive(minLength, "Min length");
this.minLength = minLength;
return this;
}
/**
* Configure the maximum length for strings which can be provided for this option.
*
* @param maxLength
* The maximum length for strings which can be provided for this option.
* @throws IllegalArgumentException
*
* - If {@link OptionType type of this option} is not {@link OptionType#STRING STRING}
* - If {@code maxLength} is not positive or greater than {@value MAX_STRING_OPTION_LENGTH}
*
*
* @return The OptionData instance, for chaining
*/
@Nonnull
public OptionData setMaxLength(int maxLength)
{
if (type != OptionType.STRING)
throw new IllegalArgumentException("Can only set max length for options of type STRING");
Checks.positive(maxLength, "Max length");
Checks.check(maxLength <= MAX_STRING_OPTION_LENGTH, "Max length must not be greater than %d. Provided: %d", MAX_STRING_OPTION_LENGTH, maxLength);
this.maxLength = maxLength;
return this;
}
/**
* Configure the minimum and maximum length for strings which can be provided for this option.
*
* @param minLength
* The minimum length for strings which can be provided for this option.
* @param maxLength
* The maximum length for strings which can be provided for this option.
* @throws IllegalArgumentException
*
* - If {@link OptionType type of this option} is not {@link OptionType#STRING STRING}
* - If {@code minLength} is greater than {@code maxLength}
*
*
* @return The OptionData instance, for chaining
*/
@Nonnull
public OptionData setRequiredLength(int minLength, int maxLength)
{
if (type != OptionType.STRING)
throw new IllegalArgumentException("Can only set min and max length for options of type STRING");
Checks.check(minLength <= maxLength, "Min length must not be greater than max length. Provided: %d > %d", minLength, maxLength);
this.setMinLength(minLength);
this.setMaxLength(maxLength);
return this;
}
/**
* Add a predefined choice for this option.
*
The user can only provide one of the choices and cannot specify any other value.
*
* @param name
* The name used in the client, up to {@value #MAX_CHOICE_NAME_LENGTH} characters long, as defined by
* {@link #MAX_CHOICE_NAME_LENGTH}
* @param value
* The value received in {@link net.dv8tion.jda.api.interactions.commands.OptionMapping OptionMapping}
*
* @throws IllegalArgumentException
*
* - If {@code name} is null, empty, or greater than {@value #MAX_CHOICE_NAME_LENGTH} characters long
* - If {@code value} is less than {@link #MIN_NEGATIVE_NUMBER} or greater than {@link #MAX_POSITIVE_NUMBER}
* - If adding this choice would exceed {@value #MAX_CHOICES} choices, as defined by {@link #MAX_CHOICES}
* - If the {@link OptionType} is not {@link OptionType#NUMBER}
* - If the option is auto-complete enabled
*
*
* @return The OptionData instance, for chaining
*/
@Nonnull
public OptionData addChoice(@Nonnull String name, double value)
{
Checks.notEmpty(name, "Name");
Checks.notLonger(name, MAX_CHOICE_NAME_LENGTH, "Name");
Checks.check(value >= MIN_NEGATIVE_NUMBER, "Double value may not be less than %f", MIN_NEGATIVE_NUMBER);
Checks.check(value <= MAX_POSITIVE_NUMBER, "Double value may not be greater than %f", MAX_POSITIVE_NUMBER);
if (isAutoComplete)
throw new IllegalStateException("Cannot add choices to auto-complete options");
if (type != OptionType.NUMBER)
throw new IllegalArgumentException("Cannot add double choice for OptionType." + type);
Checks.check(choices.size() < MAX_CHOICES, "Cannot have more than 25 choices for an option!");
choices.add(new Command.Choice(name, value));
return this;
}
/**
* Add a predefined choice for this option.
*
The user can only provide one of the choices and cannot specify any other value.
*
* @param name
* The name used in the client
* @param value
* The value received in {@link net.dv8tion.jda.api.interactions.commands.OptionMapping OptionMapping}
*
* @throws IllegalArgumentException
*
* - If {@code name} is null, empty, or greater than {@value #MAX_CHOICE_NAME_LENGTH} characters long
* - If {@code value} is less than {@link #MIN_NEGATIVE_NUMBER} or greater than {@link #MAX_POSITIVE_NUMBER}
* - If adding this choice would exceed {@value #MAX_CHOICES} choices, as defined by {@link #MAX_CHOICES}
* - If the {@link OptionType} is not {@link OptionType#INTEGER}
* - If the option is auto-complete enabled
*
*
* @return The OptionData instance, for chaining
*/
@Nonnull
public OptionData addChoice(@Nonnull String name, long value)
{
Checks.notEmpty(name, "Name");
Checks.notLonger(name, MAX_CHOICE_NAME_LENGTH, "Name");
Checks.check(value >= MIN_NEGATIVE_NUMBER, "Long value may not be less than %f", MIN_NEGATIVE_NUMBER);
Checks.check(value <= MAX_POSITIVE_NUMBER, "Long value may not be greater than %f", MAX_POSITIVE_NUMBER);
if (isAutoComplete)
throw new IllegalStateException("Cannot add choices to auto-complete options");
if (type != OptionType.INTEGER)
throw new IllegalArgumentException("Cannot add long choice for OptionType." + type);
Checks.check(choices.size() < MAX_CHOICES, "Cannot have more than 25 choices for an option!");
choices.add(new Command.Choice(name, value));
return this;
}
/**
* Add a predefined choice for this option.
*
The user can only provide one of the choices and cannot specify any other value.
*
* @param name
* The name used in the client
* @param value
* The value received in {@link net.dv8tion.jda.api.interactions.commands.OptionMapping OptionMapping}
*
* @throws IllegalArgumentException
*
* - If {@code name} is null, empty, or greater than {@value #MAX_CHOICE_NAME_LENGTH} characters long
* - If {@code value} is less than {@link #MIN_NEGATIVE_NUMBER} or greater than {@link #MAX_POSITIVE_NUMBER}
* - If adding this choice would exceed {@value #MAX_CHOICES} choices, as defined by {@link #MAX_CHOICES}
* - If the {@link OptionType} is not {@link OptionType#STRING}
* - If the option is auto-complete enabled
*
*
* @return The OptionData instance, for chaining
*/
@Nonnull
public OptionData addChoice(@Nonnull String name, @Nonnull String value)
{
Checks.notEmpty(name, "Name");
Checks.notEmpty(value, "Value");
Checks.notLonger(name, MAX_CHOICE_NAME_LENGTH, "Name");
Checks.notLonger(value, MAX_CHOICE_VALUE_LENGTH, "Value");
if (isAutoComplete)
throw new IllegalStateException("Cannot add choices to auto-complete options");
if (type != OptionType.STRING)
throw new IllegalArgumentException("Cannot add string choice for OptionType." + type);
Checks.check(choices.size() < MAX_CHOICES, "Cannot have more than 25 choices for an option!");
choices.add(new Command.Choice(name, value));
return this;
}
/**
* Adds up to 25 predefined choices for this option.
*
The user can only provide one of the choices and cannot specify any other value.
*
* @param choices
* The choices to add
*
* @throws IllegalArgumentException
*
* - If the {@link OptionType} does not {@link OptionType#canSupportChoices() support choices}
* - If the provided {@code choices} are null
* - If the amount of {@code choices} provided, when combined with the already set choices, would be greater than {@value #MAX_CHOICES}, as defined by {@link #MAX_CHOICES}
* - If the {@link OptionType} of the choices is not either {@link OptionType#INTEGER}, {@link OptionType#STRING} or {@link OptionType#NUMBER}
* - If the option is auto-complete enabled
*
*
* @return The OptionData instance, for chaining
*/
@Nonnull
public OptionData addChoices(@Nonnull Command.Choice... choices)
{
Checks.noneNull(choices, "Choices");
return addChoices(Arrays.asList(choices));
}
/**
* Adds up to 25 predefined choices for this option.
*
The user can only provide one of the choices and cannot specify any other value.
*
* @param choices
* The choices to add
*
* @throws IllegalArgumentException
*
* - If the {@link OptionType} does not {@link OptionType#canSupportChoices() support choices}
* - If the provided {@code choices} collection is null
* - If the provided {@code choices} are null
* - If the amount of {@code choices} provided, when combined with the already set choices, would be greater than {@value #MAX_CHOICES}, as defined by {@link #MAX_CHOICES}
* - If the {@link OptionType} of the choices is not either {@link OptionType#INTEGER}, {@link OptionType#STRING} or {@link OptionType#NUMBER}
* - If the option is auto-complete enabled
*
*
* @return The OptionData instance, for chaining
*/
@Nonnull
public OptionData addChoices(@Nonnull Collection extends Command.Choice> choices)
{
Checks.notNull(choices, "Choices");
if (choices.size() == 0)
return this;
if (this.choices == null || !type.canSupportChoices())
throw new IllegalStateException("Cannot add choices for an option of type " + type);
Checks.noneNull(choices, "Choices");
if (isAutoComplete)
throw new IllegalStateException("Cannot add choices to auto-complete options");
Checks.check(choices.size() + this.choices.size() <= MAX_CHOICES, "Cannot have more than 25 choices for one option!");
this.choices.addAll(choices);
return this;
}
@Nonnull
@Override
public DataObject toData()
{
DataObject json = DataObject.empty()
.put("type", type.getKey())
.put("name", name)
.put("name_localizations", nameLocalizations)
.put("description", description)
.put("description_localizations", descriptionLocalizations);
if (type != OptionType.SUB_COMMAND && type != OptionType.SUB_COMMAND_GROUP)
{
json.put("required", isRequired);
json.put("autocomplete", isAutoComplete);
}
if (choices != null && !choices.isEmpty())
{
json.put("choices", DataArray.fromCollection(
choices.stream()
.map(choice -> choice.toData(type))
.collect(Collectors.toList())
));
}
if (type == OptionType.CHANNEL && !channelTypes.isEmpty())
json.put("channel_types", channelTypes.stream().map(ChannelType::getId).collect(Collectors.toList()));
if (type == OptionType.INTEGER || type == OptionType.NUMBER)
{
if (minValue != null)
json.put("min_value", minValue);
if (maxValue != null)
json.put("max_value", maxValue);
}
if (type == OptionType.STRING)
{
if (minLength != null)
json.put("min_length", minLength);
if (maxLength != null)
json.put("max_length", maxLength);
}
return json;
}
/**
* Parses the provided serialization back into an OptionData instance.
*
This is the reverse function for {@link #toData()}.
*
* @param json
* The serialized {@link DataObject} representing the option
*
* @throws net.dv8tion.jda.api.exceptions.ParsingException
* If the serialized object is missing required fields
* @throws IllegalArgumentException
* If any of the values are failing the respective checks such as length
*
* @return The parsed OptionData instance, which can be further configured through setters
*/
@Nonnull
public static OptionData fromData(@Nonnull DataObject json)
{
String name = json.getString("name");
String description = json.getString("description");
OptionType type = OptionType.fromKey(json.getInt("type"));
OptionData option = new OptionData(type, name, description);
option.setRequired(json.getBoolean("required"));
option.setAutoComplete(json.getBoolean("autocomplete"));
if (type == OptionType.INTEGER || type == OptionType.NUMBER)
{
if (!json.isNull("min_value"))
{
if (json.isType("min_value", DataType.INT))
option.setMinValue(json.getLong("min_value"));
else if (json.isType("min_value", DataType.FLOAT))
option.setMinValue(json.getDouble("min_value"));
}
if (!json.isNull("max_value"))
{
if (json.isType("max_value", DataType.INT))
option.setMaxValue(json.getLong("max_value"));
else if (json.isType("max_value", DataType.FLOAT))
option.setMaxValue(json.getDouble("max_value"));
}
}
if (type == OptionType.CHANNEL)
{
option.setChannelTypes(json.optArray("channel_types")
.map(it -> it.stream(DataArray::getInt).map(ChannelType::fromId).collect(Collectors.toSet()))
.orElse(Collections.emptySet()));
}
if (type == OptionType.STRING)
{
if (!json.isNull("min_length"))
option.setMinLength(json.getInt("min_length"));
if (!json.isNull("max_length"))
option.setMaxLength(json.getInt("max_length"));
}
json.optArray("choices").ifPresent(choices1 ->
option.addChoices(choices1.stream(DataArray::getObject)
.map(Command.Choice::new)
.collect(Collectors.toList())
)
);
option.setNameLocalizations(LocalizationUtils.mapFromProperty(json, "name_localizations"));
option.setDescriptionLocalizations(LocalizationUtils.mapFromProperty(json, "description_localizations"));
return option;
}
/**
* Converts the provided {@link Command.Option} into a OptionData instance.
*
* @param option
* The option to convert
*
* @throws IllegalArgumentException
* If null is provided or the option has illegal configuration
*
* @return An instance of OptionData
*/
@Nonnull
public static OptionData fromOption(@Nonnull Command.Option option)
{
Checks.notNull(option, "Option");
OptionData data = new OptionData(option.getType(), option.getName(), option.getDescription());
data.setRequired(option.isRequired());
data.setAutoComplete(option.isAutoComplete());
data.addChoices(option.getChoices());
data.setNameLocalizations(option.getNameLocalizations().toMap());
data.setDescriptionLocalizations(option.getDescriptionLocalizations().toMap());
Number min = option.getMinValue(), max = option.getMaxValue();
Integer minLength = option.getMinLength(), maxLength = option.getMaxLength();
switch (option.getType())
{
case CHANNEL:
data.setChannelTypes(option.getChannelTypes());
break;
case NUMBER:
if (min != null)
data.setMinValue(min.doubleValue());
if (max != null)
data.setMaxValue(max.doubleValue());
break;
case INTEGER:
if (min != null)
data.setMinValue(min.longValue());
if (max != null)
data.setMaxValue(max.longValue());
break;
case STRING:
if (minLength != null)
data.setMinLength(minLength);
if (maxLength != null)
data.setMaxLength(maxLength);
break;
}
return data;
}
}