From ac274d4b7643237f760c32a20c6608e8ebd8d60f Mon Sep 17 00:00:00 2001
From: Mihail Gribkov <61027276+Misha-133@users.noreply.github.com>
Date: Sat, 18 Nov 2023 23:44:16 +0300
Subject: [PATCH] [Feature] Select menu default values (#2776)
* initial commit
* docs & readonly
---
.../MessageComponents/ComponentBuilder.cs | 69 +++++++++++++++++--
.../SelectDefaultValueType.cs | 22 ++++++
.../MessageComponents/SelectMenuComponent.cs | 12 +++-
.../SelectMenuDefaultValue.cs | 46 +++++++++++++
.../Entities/Messages/IUserMessage.cs | 5 ++
.../Entities/Messages/MessageResolvedData.cs | 34 +++++++++
src/Discord.Net.Rest/API/Common/Message.cs | 3 +
.../API/Common/SelectMenuComponent.cs | 5 ++
.../API/Common/SelectMenuDefaultValue.cs | 12 ++++
.../Entities/Messages/RestMessage.cs | 5 +-
.../Entities/Messages/RestUserMessage.cs | 32 +++++++++
.../Net/Converters/DiscordContractResolver.cs | 2 +
.../SelectMenuDefaultValueTypeConverter.cs | 27 ++++++++
.../Entities/Messages/SocketMessage.cs | 5 +-
.../Entities/Messages/SocketUserMessage.cs | 31 +++++++++
15 files changed, 302 insertions(+), 8 deletions(-)
create mode 100644 src/Discord.Net.Core/Entities/Interactions/MessageComponents/SelectDefaultValueType.cs
create mode 100644 src/Discord.Net.Core/Entities/Interactions/MessageComponents/SelectMenuDefaultValue.cs
create mode 100644 src/Discord.Net.Core/Entities/Messages/MessageResolvedData.cs
create mode 100644 src/Discord.Net.Rest/API/Common/SelectMenuDefaultValue.cs
create mode 100644 src/Discord.Net.Rest/Net/Converters/SelectMenuDefaultValueTypeConverter.cs
diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ComponentBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ComponentBuilder.cs
index 549913dc..a10a58c3 100644
--- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ComponentBuilder.cs
+++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ComponentBuilder.cs
@@ -1,4 +1,5 @@
using Discord.Utils;
+
using System;
using System.Collections.Generic;
using System.Linq;
@@ -129,7 +130,8 @@ namespace Discord
/// Menus valid channel types (only for )
///
public ComponentBuilder WithSelectMenu(string customId, List 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
///
public List ChannelTypes { get; set; }
+ public List DefaultValues
+ {
+ get => _defaultValues;
+ set
+ {
+ if (value != null)
+ Preconditions.AtMost(value.Count, MaxOptionCount, nameof(DefaultValues));
+
+ _defaultValues = value;
+ }
+ }
+
private List _options = new List();
private int _minValues = 1;
private int _maxValues = 1;
private string _placeholder;
private string _customId;
private ComponentType _type = ComponentType.SelectMenu;
+ private List _defaultValues = new();
///
/// Creates a new instance of a .
@@ -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();
}
///
@@ -929,7 +946,8 @@ namespace Discord
/// Disabled this select menu or not.
/// The of this select menu.
/// The types of channels this menu can select (only valid on s)
- public SelectMenuBuilder(string customId, List options = null, string placeholder = null, int maxValues = 1, int minValues = 1, bool isDisabled = false, ComponentType type = ComponentType.SelectMenu, List channelTypes = null)
+ public SelectMenuBuilder(string customId, List options = null, string placeholder = null, int maxValues = 1, int minValues = 1,
+ bool isDisabled = false, ComponentType type = ComponentType.SelectMenu, List channelTypes = null, List defaultValues = null)
{
CustomId = customId;
Options = options;
@@ -939,6 +957,7 @@ namespace Discord
MinValues = minValues;
Type = type;
ChannelTypes = channelTypes ?? new();
+ DefaultValues = defaultValues ?? new();
}
///
@@ -1046,6 +1065,48 @@ namespace Discord
return this;
}
+ ///
+ /// Add one default value to menu options.
+ ///
+ /// The id of an entity to add.
+ /// The type of an entity to add.
+ /// Default values count reached .
+ ///
+ /// The current builder.
+ ///
+ public SelectMenuBuilder AddDefaultValue(ulong id, SelectDefaultValueType type)
+ => AddDefaultValue(new(id, type));
+
+ ///
+ /// Add one default value to menu options.
+ ///
+ /// The default value to add.
+ /// Default values count reached .
+ ///
+ /// The current builder.
+ ///
+ public SelectMenuBuilder AddDefaultValue(SelectMenuDefaultValue value)
+ {
+ if (DefaultValues.Count >= MaxOptionCount)
+ throw new InvalidOperationException($"Options count reached {MaxOptionCount}.");
+
+ DefaultValues.Add(value);
+ return this;
+ }
+
+ ///
+ /// Sets the field default values.
+ ///
+ /// The value to set the field default values to.
+ ///
+ /// The current builder.
+ ///
+ public SelectMenuBuilder WithDefaultValues(params SelectMenuDefaultValue[] defaultValues)
+ {
+ DefaultValues = defaultValues.ToList();
+ return this;
+ }
+
///
/// Sets whether the current menu is disabled.
///
@@ -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);
}
}
diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/SelectDefaultValueType.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/SelectDefaultValueType.cs
new file mode 100644
index 00000000..207a14f7
--- /dev/null
+++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/SelectDefaultValueType.cs
@@ -0,0 +1,22 @@
+namespace Discord;
+
+///
+/// Type of a .
+///
+public enum SelectDefaultValueType
+{
+ ///
+ /// The select menu default value is a user.
+ ///
+ User = 0,
+
+ ///
+ /// The select menu default value is a role.
+ ///
+ Role = 1,
+
+ ///
+ /// The select menu default value is a channel.
+ ///
+ Channel = 2
+}
diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/SelectMenuComponent.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/SelectMenuComponent.cs
index eccdd18c..39631dee 100644
--- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/SelectMenuComponent.cs
+++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/SelectMenuComponent.cs
@@ -45,6 +45,11 @@ namespace Discord
///
public IReadOnlyCollection ChannelTypes { get; }
+ ///
+ /// Gets default values for auto-populated select menu components.
+ ///
+ public IReadOnlyCollection DefaultValues { get; }
+
///
/// Turns this select menu into a builder.
///
@@ -58,9 +63,11 @@ namespace Discord
Placeholder,
MaxValues,
MinValues,
- IsDisabled, Type, ChannelTypes.ToList());
+ IsDisabled, Type, ChannelTypes.ToList(),
+ DefaultValues.ToList());
- internal SelectMenuComponent(string customId, List options, string placeholder, int minValues, int maxValues, bool disabled, ComponentType type, IEnumerable channelTypes = null)
+ internal SelectMenuComponent(string customId, List options, string placeholder, int minValues, int maxValues,
+ bool disabled, ComponentType type, IEnumerable channelTypes = null, IEnumerable defaultValues = null)
{
CustomId = customId;
Options = options;
@@ -70,6 +77,7 @@ namespace Discord
IsDisabled = disabled;
Type = type;
ChannelTypes = channelTypes?.ToArray() ?? Array.Empty();
+ DefaultValues = defaultValues?.ToArray() ?? Array.Empty();
}
}
}
diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/SelectMenuDefaultValue.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/SelectMenuDefaultValue.cs
new file mode 100644
index 00000000..f2718de1
--- /dev/null
+++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/SelectMenuDefaultValue.cs
@@ -0,0 +1,46 @@
+namespace Discord;
+
+///
+/// Represents a default value of an auto-populated select menu.
+///
+public readonly struct SelectMenuDefaultValue
+{
+ ///
+ /// Gets the id of entity this default value refers to.
+ ///
+ public ulong Id { get; }
+
+ ///
+ /// Gets the type of this default value.
+ ///
+ public SelectDefaultValueType Type { get; }
+
+ ///
+ /// Creates a new default value.
+ ///
+ /// Id of the target object.
+ /// Type of the target entity.
+ public SelectMenuDefaultValue(ulong id, SelectDefaultValueType type)
+ {
+ Id = id;
+ Type = type;
+ }
+
+ ///
+ /// Creates a new default value from a .
+ ///
+ public static SelectMenuDefaultValue FromChannel(IChannel channel)
+ => new(channel.Id, SelectDefaultValueType.Channel);
+
+ ///
+ /// Creates a new default value from a .
+ ///
+ public static SelectMenuDefaultValue FromRole(IRole role)
+ => new(role.Id, SelectDefaultValueType.Role);
+
+ ///
+ /// Creates a new default value from a .
+ ///
+ public static SelectMenuDefaultValue FromUser(IUser user)
+ => new(user.Id, SelectDefaultValueType.User);
+}
diff --git a/src/Discord.Net.Core/Entities/Messages/IUserMessage.cs b/src/Discord.Net.Core/Entities/Messages/IUserMessage.cs
index c2d0e13b..72770899 100644
--- a/src/Discord.Net.Core/Entities/Messages/IUserMessage.cs
+++ b/src/Discord.Net.Core/Entities/Messages/IUserMessage.cs
@@ -8,6 +8,11 @@ namespace Discord
///
public interface IUserMessage : IMessage
{
+ ///
+ /// Gets the resolved data if the message has components. otherwise.
+ ///
+ MessageResolvedData ResolvedData { get; }
+
///
/// Gets the referenced message if it is a crosspost, channel follow add, pin, or reply message.
///
diff --git a/src/Discord.Net.Core/Entities/Messages/MessageResolvedData.cs b/src/Discord.Net.Core/Entities/Messages/MessageResolvedData.cs
new file mode 100644
index 00000000..2f6216a2
--- /dev/null
+++ b/src/Discord.Net.Core/Entities/Messages/MessageResolvedData.cs
@@ -0,0 +1,34 @@
+using System.Collections.Generic;
+
+namespace Discord;
+
+public class MessageResolvedData
+{
+ ///
+ /// Gets a collection of resolved in the message.
+ ///
+ public IReadOnlyCollection Users { get; }
+
+ ///
+ /// Gets a collection of resolved in the message.
+ ///
+ public IReadOnlyCollection Members { get; }
+
+ ///
+ /// Gets a collection of resolved in the message.
+ ///
+ public IReadOnlyCollection Roles { get; }
+
+ ///
+ /// Gets a collection of resolved in the message.
+ ///
+ public IReadOnlyCollection Channels { get; }
+
+ internal MessageResolvedData(IReadOnlyCollection users, IReadOnlyCollection members, IReadOnlyCollection roles, IReadOnlyCollection channels)
+ {
+ Users = users;
+ Members = members;
+ Roles = roles;
+ Channels = channels;
+ }
+}
diff --git a/src/Discord.Net.Rest/API/Common/Message.cs b/src/Discord.Net.Rest/API/Common/Message.cs
index 2b845bee..8cde11c8 100644
--- a/src/Discord.Net.Rest/API/Common/Message.cs
+++ b/src/Discord.Net.Rest/API/Common/Message.cs
@@ -67,5 +67,8 @@ namespace Discord.API
[JsonProperty("thread")]
public Optional Thread { get; set; }
+
+ [JsonProperty("resolved")]
+ public Optional Resolved { get; set; }
}
}
diff --git a/src/Discord.Net.Rest/API/Common/SelectMenuComponent.cs b/src/Discord.Net.Rest/API/Common/SelectMenuComponent.cs
index 3975a8c1..c7a69568 100644
--- a/src/Discord.Net.Rest/API/Common/SelectMenuComponent.cs
+++ b/src/Discord.Net.Rest/API/Common/SelectMenuComponent.cs
@@ -34,6 +34,10 @@ namespace Discord.API
[JsonProperty("values")]
public Optional Values { get; set; }
+
+ [JsonProperty("default_values")]
+ public Optional 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();
}
}
}
diff --git a/src/Discord.Net.Rest/API/Common/SelectMenuDefaultValue.cs b/src/Discord.Net.Rest/API/Common/SelectMenuDefaultValue.cs
new file mode 100644
index 00000000..ebaed146
--- /dev/null
+++ b/src/Discord.Net.Rest/API/Common/SelectMenuDefaultValue.cs
@@ -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; }
+}
diff --git a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs
index f928e362..0f4dd291 100644
--- a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs
+++ b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs
@@ -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()
);
}
default:
diff --git a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs
index 083a8e72..f807c9e1 100644
--- a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs
+++ b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs
@@ -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
///
public IUserMessage ReferencedMessage => _referencedMessage;
+ ///
+ 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();
}
+
+ 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.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.Empty;
+
+ var roles = model.Resolved.Value.Roles.IsSpecified
+ ? model.Resolved.Value.Roles.Value.Select(x => RestRole.Create(Discord, guild, x.Value)).ToImmutableArray()
+ : ImmutableArray.Empty;
+
+ var channels = model.Resolved.Value.Channels.IsSpecified
+ ? model.Resolved.Value.Channels.Value.Select(x => RestChannel.Create(Discord, x.Value, guild)).ToImmutableArray()
+ : ImmutableArray.Empty;
+
+ ResolvedData = new MessageResolvedData(users, members, roles, channels);
+ }
}
///
diff --git a/src/Discord.Net.Rest/Net/Converters/DiscordContractResolver.cs b/src/Discord.Net.Rest/Net/Converters/DiscordContractResolver.cs
index ae16f19c..62c0e580 100644
--- a/src/Discord.Net.Rest/Net/Converters/DiscordContractResolver.cs
+++ b/src/Discord.Net.Rest/Net/Converters/DiscordContractResolver.cs
@@ -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))
diff --git a/src/Discord.Net.Rest/Net/Converters/SelectMenuDefaultValueTypeConverter.cs b/src/Discord.Net.Rest/Net/Converters/SelectMenuDefaultValueTypeConverter.cs
new file mode 100644
index 00000000..32212feb
--- /dev/null
+++ b/src/Discord.Net.Rest/Net/Converters/SelectMenuDefaultValueTypeConverter.cs
@@ -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((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));
+ }
+}
diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs
index e7b09c33..24c9dce2 100644
--- a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs
+++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs
@@ -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()
);
}
default:
diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs
index f5abb2c4..4f061b7e 100644
--- a/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs
+++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs
@@ -49,6 +49,9 @@ namespace Discord.WebSocket
///
public IUserMessage ReferencedMessage => _referencedMessage;
+ ///
+ 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();
}
+
+ 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.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.Empty;
+
+ var roles = model.Resolved.Value.Roles.IsSpecified
+ ? model.Resolved.Value.Roles.Value.Select(x => RestRole.Create(Discord, guild, x.Value)).ToImmutableArray()
+ : ImmutableArray.Empty;
+
+ var channels = model.Resolved.Value.Channels.IsSpecified
+ ? model.Resolved.Value.Channels.Value.Select(x => RestChannel.Create(Discord, x.Value, guild)).ToImmutableArray()
+ : ImmutableArray.Empty;
+
+ ResolvedData = new MessageResolvedData(users, members, roles, channels);
+ }
}
///