[Feature] Select menu default values (#2776)
* initial commit * docs & readonly
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
using Discord.Utils;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -129,7 +130,8 @@ namespace Discord
|
||||
/// <param name="channelTypes">Menus valid channel types (only for <see cref="ComponentType.ChannelSelect"/>)</param>
|
||||
/// <returns></returns>
|
||||
public ComponentBuilder WithSelectMenu(string customId, List<SelectMenuOptionBuilder> options = null,
|
||||
string placeholder = null, int minValues = 1, int maxValues = 1, bool disabled = false, int row = 0, ComponentType type = ComponentType.SelectMenu, ChannelType[] channelTypes = null)
|
||||
string placeholder = null, int minValues = 1, int maxValues = 1, bool disabled = false, int row = 0, ComponentType type = ComponentType.SelectMenu,
|
||||
ChannelType[] channelTypes = null, SelectMenuDefaultValue[] defaultValues = null)
|
||||
{
|
||||
return WithSelectMenu(new SelectMenuBuilder()
|
||||
.WithCustomId(customId)
|
||||
@@ -139,7 +141,8 @@ namespace Discord
|
||||
.WithMinValues(minValues)
|
||||
.WithDisabled(disabled)
|
||||
.WithType(type)
|
||||
.WithChannelTypes(channelTypes),
|
||||
.WithChannelTypes(channelTypes)
|
||||
.WithDefaultValues(defaultValues),
|
||||
row);
|
||||
}
|
||||
|
||||
@@ -891,12 +894,25 @@ namespace Discord
|
||||
/// </summary>
|
||||
public List<ChannelType> ChannelTypes { get; set; }
|
||||
|
||||
public List<SelectMenuDefaultValue> DefaultValues
|
||||
{
|
||||
get => _defaultValues;
|
||||
set
|
||||
{
|
||||
if (value != null)
|
||||
Preconditions.AtMost(value.Count, MaxOptionCount, nameof(DefaultValues));
|
||||
|
||||
_defaultValues = value;
|
||||
}
|
||||
}
|
||||
|
||||
private List<SelectMenuOptionBuilder> _options = new List<SelectMenuOptionBuilder>();
|
||||
private int _minValues = 1;
|
||||
private int _maxValues = 1;
|
||||
private string _placeholder;
|
||||
private string _customId;
|
||||
private ComponentType _type = ComponentType.SelectMenu;
|
||||
private List<SelectMenuDefaultValue> _defaultValues = new();
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of a <see cref="SelectMenuBuilder"/>.
|
||||
@@ -916,6 +932,7 @@ namespace Discord
|
||||
Options = selectMenu.Options?
|
||||
.Select(x => new SelectMenuOptionBuilder(x.Label, x.Value, x.Description, x.Emote, x.IsDefault))
|
||||
.ToList();
|
||||
DefaultValues = selectMenu.DefaultValues?.ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -929,7 +946,8 @@ namespace Discord
|
||||
/// <param name="isDisabled">Disabled this select menu or not.</param>
|
||||
/// <param name="type">The <see cref="ComponentType"/> of this select menu.</param>
|
||||
/// <param name="channelTypes">The types of channels this menu can select (only valid on <see cref="ComponentType.ChannelSelect"/>s)</param>
|
||||
public SelectMenuBuilder(string customId, List<SelectMenuOptionBuilder> options = null, string placeholder = null, int maxValues = 1, int minValues = 1, bool isDisabled = false, ComponentType type = ComponentType.SelectMenu, List<ChannelType> channelTypes = null)
|
||||
public SelectMenuBuilder(string customId, List<SelectMenuOptionBuilder> options = null, string placeholder = null, int maxValues = 1, int minValues = 1,
|
||||
bool isDisabled = false, ComponentType type = ComponentType.SelectMenu, List<ChannelType> channelTypes = null, List<SelectMenuDefaultValue> defaultValues = null)
|
||||
{
|
||||
CustomId = customId;
|
||||
Options = options;
|
||||
@@ -939,6 +957,7 @@ namespace Discord
|
||||
MinValues = minValues;
|
||||
Type = type;
|
||||
ChannelTypes = channelTypes ?? new();
|
||||
DefaultValues = defaultValues ?? new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1046,6 +1065,48 @@ namespace Discord
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add one default value to menu options.
|
||||
/// </summary>
|
||||
/// <param name="id">The id of an entity to add.</param>
|
||||
/// <param name="type">The type of an entity to add.</param>
|
||||
/// <exception cref="InvalidOperationException">Default values count reached <see cref="MaxOptionCount"/>.</exception>
|
||||
/// <returns>
|
||||
/// The current builder.
|
||||
/// </returns>
|
||||
public SelectMenuBuilder AddDefaultValue(ulong id, SelectDefaultValueType type)
|
||||
=> AddDefaultValue(new(id, type));
|
||||
|
||||
/// <summary>
|
||||
/// Add one default value to menu options.
|
||||
/// </summary>
|
||||
/// <param name="value">The default value to add.</param>
|
||||
/// <exception cref="InvalidOperationException">Default values count reached <see cref="MaxOptionCount"/>.</exception>
|
||||
/// <returns>
|
||||
/// The current builder.
|
||||
/// </returns>
|
||||
public SelectMenuBuilder AddDefaultValue(SelectMenuDefaultValue value)
|
||||
{
|
||||
if (DefaultValues.Count >= MaxOptionCount)
|
||||
throw new InvalidOperationException($"Options count reached {MaxOptionCount}.");
|
||||
|
||||
DefaultValues.Add(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the field default values.
|
||||
/// </summary>
|
||||
/// <param name="defaultValues">The value to set the field default values to.</param>
|
||||
/// <returns>
|
||||
/// The current builder.
|
||||
/// </returns>
|
||||
public SelectMenuBuilder WithDefaultValues(params SelectMenuDefaultValue[] defaultValues)
|
||||
{
|
||||
DefaultValues = defaultValues.ToList();
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets whether the current menu is disabled.
|
||||
/// </summary>
|
||||
@@ -1108,7 +1169,7 @@ namespace Discord
|
||||
{
|
||||
var options = Options?.Select(x => x.Build()).ToList();
|
||||
|
||||
return new SelectMenuComponent(CustomId, options, Placeholder, MinValues, MaxValues, IsDisabled, Type, ChannelTypes);
|
||||
return new SelectMenuComponent(CustomId, options, Placeholder, MinValues, MaxValues, IsDisabled, Type, ChannelTypes, DefaultValues);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
namespace Discord;
|
||||
|
||||
/// <summary>
|
||||
/// Type of a <see cref="SelectDefaultValueType" />.
|
||||
/// </summary>
|
||||
public enum SelectDefaultValueType
|
||||
{
|
||||
/// <summary>
|
||||
/// The select menu default value is a user.
|
||||
/// </summary>
|
||||
User = 0,
|
||||
|
||||
/// <summary>
|
||||
/// The select menu default value is a role.
|
||||
/// </summary>
|
||||
Role = 1,
|
||||
|
||||
/// <summary>
|
||||
/// The select menu default value is a channel.
|
||||
/// </summary>
|
||||
Channel = 2
|
||||
}
|
||||
@@ -45,6 +45,11 @@ namespace Discord
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<ChannelType> ChannelTypes { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets default values for auto-populated select menu components.
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<SelectMenuDefaultValue> DefaultValues { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Turns this select menu into a builder.
|
||||
/// </summary>
|
||||
@@ -58,9 +63,11 @@ namespace Discord
|
||||
Placeholder,
|
||||
MaxValues,
|
||||
MinValues,
|
||||
IsDisabled, Type, ChannelTypes.ToList());
|
||||
IsDisabled, Type, ChannelTypes.ToList(),
|
||||
DefaultValues.ToList());
|
||||
|
||||
internal SelectMenuComponent(string customId, List<SelectMenuOption> options, string placeholder, int minValues, int maxValues, bool disabled, ComponentType type, IEnumerable<ChannelType> channelTypes = null)
|
||||
internal SelectMenuComponent(string customId, List<SelectMenuOption> options, string placeholder, int minValues, int maxValues,
|
||||
bool disabled, ComponentType type, IEnumerable<ChannelType> channelTypes = null, IEnumerable<SelectMenuDefaultValue> defaultValues = null)
|
||||
{
|
||||
CustomId = customId;
|
||||
Options = options;
|
||||
@@ -70,6 +77,7 @@ namespace Discord
|
||||
IsDisabled = disabled;
|
||||
Type = type;
|
||||
ChannelTypes = channelTypes?.ToArray() ?? Array.Empty<ChannelType>();
|
||||
DefaultValues = defaultValues?.ToArray() ?? Array.Empty<SelectMenuDefaultValue>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
namespace Discord;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a default value of an auto-populated select menu.
|
||||
/// </summary>
|
||||
public readonly struct SelectMenuDefaultValue
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the id of entity this default value refers to.
|
||||
/// </summary>
|
||||
public ulong Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of this default value.
|
||||
/// </summary>
|
||||
public SelectDefaultValueType Type { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new default value.
|
||||
/// </summary>
|
||||
/// <param name="id">Id of the target object.</param>
|
||||
/// <param name="type">Type of the target entity.</param>
|
||||
public SelectMenuDefaultValue(ulong id, SelectDefaultValueType type)
|
||||
{
|
||||
Id = id;
|
||||
Type = type;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new default value from a <see cref="IChannel"/>.
|
||||
/// </summary>
|
||||
public static SelectMenuDefaultValue FromChannel(IChannel channel)
|
||||
=> new(channel.Id, SelectDefaultValueType.Channel);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new default value from a <see cref="IRole"/>.
|
||||
/// </summary>
|
||||
public static SelectMenuDefaultValue FromRole(IRole role)
|
||||
=> new(role.Id, SelectDefaultValueType.Role);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new default value from a <see cref="IUser"/>.
|
||||
/// </summary>
|
||||
public static SelectMenuDefaultValue FromUser(IUser user)
|
||||
=> new(user.Id, SelectDefaultValueType.User);
|
||||
}
|
||||
@@ -8,6 +8,11 @@ namespace Discord
|
||||
/// </summary>
|
||||
public interface IUserMessage : IMessage
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the resolved data if the message has components. <see langword="null"/> otherwise.
|
||||
/// </summary>
|
||||
MessageResolvedData ResolvedData { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the referenced message if it is a crosspost, channel follow add, pin, or reply message.
|
||||
/// </summary>
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Discord;
|
||||
|
||||
public class MessageResolvedData
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a collection of <see cref="IUser"/> resolved in the message.
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<IUser> Users { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a collection of <see cref="IGuildUser"/> resolved in the message.
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<IGuildUser> Members { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a collection of <see cref="IRole"/> resolved in the message.
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<IRole> Roles { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a collection of <see cref="IChannel"/> resolved in the message.
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<IChannel> Channels { get; }
|
||||
|
||||
internal MessageResolvedData(IReadOnlyCollection<IUser> users, IReadOnlyCollection<IGuildUser> members, IReadOnlyCollection<IRole> roles, IReadOnlyCollection<IChannel> channels)
|
||||
{
|
||||
Users = users;
|
||||
Members = members;
|
||||
Roles = roles;
|
||||
Channels = channels;
|
||||
}
|
||||
}
|
||||
@@ -67,5 +67,8 @@ namespace Discord.API
|
||||
|
||||
[JsonProperty("thread")]
|
||||
public Optional<Channel> Thread { get; set; }
|
||||
|
||||
[JsonProperty("resolved")]
|
||||
public Optional<MessageComponentInteractionDataResolved> Resolved { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,10 @@ namespace Discord.API
|
||||
|
||||
[JsonProperty("values")]
|
||||
public Optional<string[]> Values { get; set; }
|
||||
|
||||
[JsonProperty("default_values")]
|
||||
public Optional<SelectMenuDefaultValue[]> DefaultValues { get; set; }
|
||||
|
||||
public SelectMenuComponent() { }
|
||||
|
||||
public SelectMenuComponent(Discord.SelectMenuComponent component)
|
||||
@@ -46,6 +50,7 @@ namespace Discord.API
|
||||
MaxValues = component.MaxValues;
|
||||
Disabled = component.IsDisabled;
|
||||
ChannelTypes = component.ChannelTypes.ToArray();
|
||||
DefaultValues = component.DefaultValues.Select(x => new SelectMenuDefaultValue {Id = x.Id, Type = x.Type}).ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
12
src/Discord.Net.Rest/API/Common/SelectMenuDefaultValue.cs
Normal file
12
src/Discord.Net.Rest/API/Common/SelectMenuDefaultValue.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Discord.API;
|
||||
|
||||
internal class SelectMenuDefaultValue
|
||||
{
|
||||
[JsonProperty("id")]
|
||||
public ulong Id { get; set; }
|
||||
|
||||
[JsonProperty("type")]
|
||||
public SelectDefaultValueType Type { get; set; }
|
||||
}
|
||||
@@ -203,7 +203,10 @@ namespace Discord.Rest
|
||||
parsed.MaxValues,
|
||||
parsed.Disabled,
|
||||
parsed.Type,
|
||||
parsed.ChannelTypes.GetValueOrDefault()
|
||||
parsed.ChannelTypes.GetValueOrDefault(),
|
||||
parsed.DefaultValues.IsSpecified
|
||||
? parsed.DefaultValues.Value.Select(x => new SelectMenuDefaultValue(x.Id, x.Type))
|
||||
: Array.Empty<SelectMenuDefaultValue>()
|
||||
);
|
||||
}
|
||||
default:
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Model = Discord.API.Message;
|
||||
|
||||
@@ -47,6 +48,9 @@ namespace Discord.Rest
|
||||
/// <inheritdoc />
|
||||
public IUserMessage ReferencedMessage => _referencedMessage;
|
||||
|
||||
/// <inheritdoc />
|
||||
public MessageResolvedData ResolvedData { get; internal set; }
|
||||
|
||||
internal RestUserMessage(BaseDiscordClient discord, ulong id, IMessageChannel channel, IUser author, MessageSource source)
|
||||
: base(discord, id, channel, author, source)
|
||||
{
|
||||
@@ -130,6 +134,34 @@ namespace Discord.Rest
|
||||
else
|
||||
_stickers = ImmutableArray.Create<StickerItem>();
|
||||
}
|
||||
|
||||
if (model.Resolved.IsSpecified)
|
||||
{
|
||||
var users = model.Resolved.Value.Users.IsSpecified
|
||||
? model.Resolved.Value.Users.Value.Select(x => RestUser.Create(Discord, x.Value)).ToImmutableArray()
|
||||
: ImmutableArray<RestUser>.Empty;
|
||||
|
||||
var members = model.Resolved.Value.Members.IsSpecified
|
||||
? model.Resolved.Value.Members.Value.Select(x =>
|
||||
{
|
||||
x.Value.User = model.Resolved.Value.Users.Value.TryGetValue(x.Key, out var user)
|
||||
? user
|
||||
: null;
|
||||
|
||||
return RestGuildUser.Create(Discord, guild, x.Value, guildId);
|
||||
}).ToImmutableArray()
|
||||
: ImmutableArray<RestGuildUser>.Empty;
|
||||
|
||||
var roles = model.Resolved.Value.Roles.IsSpecified
|
||||
? model.Resolved.Value.Roles.Value.Select(x => RestRole.Create(Discord, guild, x.Value)).ToImmutableArray()
|
||||
: ImmutableArray<RestRole>.Empty;
|
||||
|
||||
var channels = model.Resolved.Value.Channels.IsSpecified
|
||||
? model.Resolved.Value.Channels.Value.Select(x => RestChannel.Create(Discord, x.Value, guild)).ToImmutableArray()
|
||||
: ImmutableArray<RestChannel>.Empty;
|
||||
|
||||
ResolvedData = new MessageResolvedData(users, members, roles, channels);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -79,6 +79,8 @@ namespace Discord.Net.Converters
|
||||
return UserStatusConverter.Instance;
|
||||
if (type == typeof(EmbedType))
|
||||
return EmbedTypeConverter.Instance;
|
||||
if (type == typeof(SelectDefaultValueType))
|
||||
return SelectMenuDefaultValueTypeConverter.Instance;
|
||||
|
||||
//Special
|
||||
if (type == typeof(API.Image))
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Discord.Net.Converters;
|
||||
|
||||
internal class SelectMenuDefaultValueTypeConverter : JsonConverter
|
||||
{
|
||||
public static readonly SelectMenuDefaultValueTypeConverter Instance = new ();
|
||||
|
||||
public override bool CanConvert(Type objectType) => true;
|
||||
public override bool CanRead => true;
|
||||
public override bool CanWrite => true;
|
||||
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||
{
|
||||
return Enum.TryParse<SelectDefaultValueType>((string)reader.Value, true, out var result)
|
||||
? result
|
||||
: null;
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||
{
|
||||
writer.WriteValue(((SelectDefaultValueType)value).ToString().ToLower(CultureInfo.InvariantCulture));
|
||||
}
|
||||
}
|
||||
@@ -237,7 +237,10 @@ namespace Discord.WebSocket
|
||||
parsed.MaxValues,
|
||||
parsed.Disabled,
|
||||
parsed.Type,
|
||||
parsed.ChannelTypes.GetValueOrDefault()
|
||||
parsed.ChannelTypes.GetValueOrDefault(),
|
||||
parsed.DefaultValues.IsSpecified
|
||||
? parsed.DefaultValues.Value.Select(x => new SelectMenuDefaultValue(x.Id, x.Type))
|
||||
: Array.Empty<SelectMenuDefaultValue>()
|
||||
);
|
||||
}
|
||||
default:
|
||||
|
||||
@@ -49,6 +49,9 @@ namespace Discord.WebSocket
|
||||
/// <inheritdoc />
|
||||
public IUserMessage ReferencedMessage => _referencedMessage;
|
||||
|
||||
/// <inheritdoc />
|
||||
public MessageResolvedData ResolvedData { get; internal set; }
|
||||
|
||||
internal SocketUserMessage(DiscordSocketClient discord, ulong id, ISocketMessageChannel channel, SocketUser author, MessageSource source)
|
||||
: base(discord, id, channel, author, source)
|
||||
{
|
||||
@@ -172,6 +175,34 @@ namespace Discord.WebSocket
|
||||
else
|
||||
_stickers = ImmutableArray.Create<SocketSticker>();
|
||||
}
|
||||
|
||||
if (model.Resolved.IsSpecified)
|
||||
{
|
||||
var users = model.Resolved.Value.Users.IsSpecified
|
||||
? model.Resolved.Value.Users.Value.Select(x => RestUser.Create(Discord, x.Value)).ToImmutableArray()
|
||||
: ImmutableArray<RestUser>.Empty;
|
||||
|
||||
var members = model.Resolved.Value.Members.IsSpecified
|
||||
? model.Resolved.Value.Members.Value.Select(x =>
|
||||
{
|
||||
x.Value.User = model.Resolved.Value.Users.Value.TryGetValue(x.Key, out var user)
|
||||
? user
|
||||
: null;
|
||||
|
||||
return RestGuildUser.Create(Discord, guild, x.Value);
|
||||
}).ToImmutableArray()
|
||||
: ImmutableArray<RestGuildUser>.Empty;
|
||||
|
||||
var roles = model.Resolved.Value.Roles.IsSpecified
|
||||
? model.Resolved.Value.Roles.Value.Select(x => RestRole.Create(Discord, guild, x.Value)).ToImmutableArray()
|
||||
: ImmutableArray<RestRole>.Empty;
|
||||
|
||||
var channels = model.Resolved.Value.Channels.IsSpecified
|
||||
? model.Resolved.Value.Channels.Value.Select(x => RestChannel.Create(Discord, x.Value, guild)).ToImmutableArray()
|
||||
: ImmutableArray<RestChannel>.Empty;
|
||||
|
||||
ResolvedData = new MessageResolvedData(users, members, roles, channels);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
Reference in New Issue
Block a user