@@ -122,7 +122,7 @@ namespace Discord | |||||
public ComponentBuilder WithSelectMenu(SelectMenuBuilder menu, int row = 0) | public ComponentBuilder WithSelectMenu(SelectMenuBuilder menu, int row = 0) | ||||
{ | { | ||||
Preconditions.LessThan(row, MaxActionRowCount, nameof(row)); | Preconditions.LessThan(row, MaxActionRowCount, nameof(row)); | ||||
if (menu.Options.Distinct().Count() != menu.Options.Count) | |||||
if (menu.Options is not null && menu.Options.Distinct().Count() != menu.Options.Count) | |||||
throw new InvalidOperationException("Please make sure that there is no duplicates values."); | throw new InvalidOperationException("Please make sure that there is no duplicates values."); | ||||
var builtMenu = menu.Build(); | var builtMenu = menu.Build(); | ||||
@@ -838,8 +838,6 @@ namespace Discord | |||||
{ | { | ||||
if (value != null) | if (value != null) | ||||
Preconditions.AtMost(value.Count, MaxOptionCount, nameof(Options)); | Preconditions.AtMost(value.Count, MaxOptionCount, nameof(Options)); | ||||
else | |||||
throw new ArgumentNullException(nameof(value), $"{nameof(Options)} cannot be null."); | |||||
_options = value; | _options = value; | ||||
} | } | ||||
@@ -1058,7 +1056,9 @@ namespace Discord | |||||
/// </returns> | /// </returns> | ||||
public SelectMenuBuilder WithChannelTypes(params ChannelType[] channelTypes) | public SelectMenuBuilder WithChannelTypes(params ChannelType[] channelTypes) | ||||
{ | { | ||||
ChannelTypes = channelTypes.ToList(); | |||||
ChannelTypes = channelTypes is null | |||||
? ChannelTypeUtils.AllChannelTypes() | |||||
: channelTypes.ToList(); | |||||
return this; | return this; | ||||
} | } | ||||
@@ -37,6 +37,11 @@ namespace Discord | |||||
/// </summary> | /// </summary> | ||||
IReadOnlyCollection<IRole> Roles { get; } | IReadOnlyCollection<IRole> Roles { get; } | ||||
/// <summary> | |||||
/// Gets the guild member(s) of a <see cref="ComponentType.UserSelect"/> or <see cref="ComponentType.MentionableSelect"/> interaction response. | |||||
/// </summary> | |||||
IReadOnlyCollection<IGuildUser> Members { get; } | |||||
/// <summary> | /// <summary> | ||||
/// Gets the value of a <see cref="ComponentType.TextInput"/> interaction response. | /// Gets the value of a <see cref="ComponentType.TextInput"/> interaction response. | ||||
/// </summary> | /// </summary> | ||||
@@ -0,0 +1,14 @@ | |||||
using System.Collections.Generic; | |||||
namespace Discord.Utils; | |||||
public static class ChannelTypeUtils | |||||
{ | |||||
public static List<ChannelType> AllChannelTypes() | |||||
=> new List<ChannelType>() | |||||
{ | |||||
ChannelType.Forum, ChannelType.Category, ChannelType.DM, ChannelType.Group, ChannelType.GuildDirectory, | |||||
ChannelType.News, ChannelType.NewsThread, ChannelType.PrivateThread, ChannelType.PublicThread, | |||||
ChannelType.Stage, ChannelType.Store, ChannelType.Text, ChannelType.Voice | |||||
}; | |||||
} |
@@ -40,7 +40,7 @@ namespace Discord.API | |||||
{ | { | ||||
Type = component.Type; | Type = component.Type; | ||||
CustomId = component.CustomId; | CustomId = component.CustomId; | ||||
Options = component.Options.Select(x => new SelectMenuOption(x)).ToArray(); | |||||
Options = component.Options?.Select(x => new SelectMenuOption(x)).ToArray(); | |||||
Placeholder = component.Placeholder; | Placeholder = component.Placeholder; | ||||
MinValues = component.MinValues; | MinValues = component.MinValues; | ||||
MaxValues = component.MaxValues; | MaxValues = component.MaxValues; | ||||
@@ -34,6 +34,9 @@ namespace Discord.Rest | |||||
/// <inheritdoc cref="IComponentInteractionData.Roles"/>/> | /// <inheritdoc cref="IComponentInteractionData.Roles"/>/> | ||||
public IReadOnlyCollection<RestRole> Roles { get; } | public IReadOnlyCollection<RestRole> Roles { get; } | ||||
/// <inheritdoc cref="IComponentInteractionData.Members"/>/> | |||||
public IReadOnlyCollection<RestGuildUser> Members { get; } | |||||
#region IComponentInteractionData | #region IComponentInteractionData | ||||
/// <inheritdoc/> | /// <inheritdoc/> | ||||
@@ -45,6 +48,9 @@ namespace Discord.Rest | |||||
/// <inheritdoc/> | /// <inheritdoc/> | ||||
IReadOnlyCollection<IRole> IComponentInteractionData.Roles => Roles; | IReadOnlyCollection<IRole> IComponentInteractionData.Roles => Roles; | ||||
/// <inheritdoc/> | |||||
IReadOnlyCollection<IGuildUser> IComponentInteractionData.Members => Members; | |||||
#endregion | #endregion | ||||
/// <inheritdoc/> | /// <inheritdoc/> | ||||
@@ -60,19 +66,25 @@ namespace Discord.Rest | |||||
if (model.Resolved.IsSpecified) | if (model.Resolved.IsSpecified) | ||||
{ | { | ||||
Users = model.Resolved.Value.Users.IsSpecified | Users = model.Resolved.Value.Users.IsSpecified | ||||
? model.Resolved.Value.Users.Value.Select(user => RestUser.Create(discord, user.Value)) | |||||
.Concat(model.Resolved.Value.Members.IsSpecified | |||||
? model.Resolved.Value.Members.Value.Select(member => RestGuildUser.Create(discord, guild, member.Value)) | |||||
: Array.Empty<RestGuildUser>()).ToImmutableArray() | |||||
? model.Resolved.Value.Users.Value.Select(user => RestUser.Create(discord, user.Value)).ToImmutableArray() | |||||
: Array.Empty<RestUser>(); | |||||
Members = model.Resolved.Value.Members.IsSpecified | |||||
? model.Resolved.Value.Members.Value.Select(member => | |||||
{ | |||||
member.Value.User = model.Resolved.Value.Users.Value.First(u => u.Key == member.Key).Value; | |||||
return RestGuildUser.Create(discord, guild, member.Value); | |||||
}).ToImmutableArray() | |||||
: null; | : null; | ||||
Channels = model.Resolved.Value.Channels.IsSpecified | Channels = model.Resolved.Value.Channels.IsSpecified | ||||
? model.Resolved.Value.Channels.Value.Select(channel => RestChannel.Create(discord, channel.Value)).ToImmutableArray() | ? model.Resolved.Value.Channels.Value.Select(channel => RestChannel.Create(discord, channel.Value)).ToImmutableArray() | ||||
: null; | |||||
: Array.Empty<RestChannel>(); | |||||
Roles = model.Resolved.Value.Roles.IsSpecified | Roles = model.Resolved.Value.Roles.IsSpecified | ||||
? model.Resolved.Value.Roles.Value.Select(role => RestRole.Create(discord, guild, role.Value)).ToImmutableArray() | ? model.Resolved.Value.Roles.Value.Select(role => RestRole.Create(discord, guild, role.Value)).ToImmutableArray() | ||||
: null; | |||||
: Array.Empty<RestRole>(); | |||||
} | } | ||||
} | } | ||||
@@ -91,10 +103,16 @@ namespace Discord.Rest | |||||
if (select.Resolved.IsSpecified) | if (select.Resolved.IsSpecified) | ||||
{ | { | ||||
Users = select.Resolved.Value.Users.IsSpecified | Users = select.Resolved.Value.Users.IsSpecified | ||||
? select.Resolved.Value.Users.Value.Select(user => RestUser.Create(discord, user.Value)) | |||||
.Concat(select.Resolved.Value.Members.IsSpecified | |||||
? select.Resolved.Value.Members.Value.Select(member => RestGuildUser.Create(discord, guild, member.Value)) | |||||
: Array.Empty<RestGuildUser>()).ToImmutableArray() | |||||
? select.Resolved.Value.Users.Value.Select(user => RestUser.Create(discord, user.Value)).ToImmutableArray() | |||||
: null; | |||||
Members = select.Resolved.Value.Members.IsSpecified | |||||
? select.Resolved.Value.Members.Value.Select(member => | |||||
{ | |||||
member.Value.User = select.Resolved.Value.Users.Value.First(u => u.Key == member.Key).Value; | |||||
return RestGuildUser.Create(discord, guild, member.Value); | |||||
}).ToImmutableArray() | |||||
: null; | : null; | ||||
Channels = select.Resolved.Value.Channels.IsSpecified | Channels = select.Resolved.Value.Channels.IsSpecified | ||||
@@ -1,5 +1,3 @@ | |||||
#define DEBUG_PACKETS | |||||
using Discord.API.Gateway; | using Discord.API.Gateway; | ||||
using Discord.Net.Queue; | using Discord.Net.Queue; | ||||
using Discord.Net.Rest; | using Discord.Net.Rest; | ||||
@@ -35,7 +35,7 @@ namespace Discord.WebSocket | |||||
? (DataModel)model.Data.Value | ? (DataModel)model.Data.Value | ||||
: null; | : null; | ||||
Data = new SocketMessageComponentData(dataModel); | |||||
Data = new SocketMessageComponentData(dataModel, client, client.State, client.Guilds.FirstOrDefault(x => x.Id == model.GuildId.GetValueOrDefault())); | |||||
} | } | ||||
internal new static SocketMessageComponent Create(DiscordSocketClient client, Model model, ISocketMessageChannel channel, SocketUser user) | internal new static SocketMessageComponent Create(DiscordSocketClient client, Model model, ISocketMessageChannel channel, SocketUser user) | ||||
@@ -1,6 +1,9 @@ | |||||
using Discord.Rest; | |||||
using Discord.Utils; | using Discord.Utils; | ||||
using System; | |||||
using System.Linq; | using System.Linq; | ||||
using System.Collections.Generic; | using System.Collections.Generic; | ||||
using System.Collections.Immutable; | |||||
using Model = Discord.API.MessageComponentInteractionData; | using Model = Discord.API.MessageComponentInteractionData; | ||||
namespace Discord.WebSocket | namespace Discord.WebSocket | ||||
@@ -19,27 +22,74 @@ namespace Discord.WebSocket | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public IReadOnlyCollection<string> Values { get; } | public IReadOnlyCollection<string> Values { get; } | ||||
/// <inheritdoc cref="IComponentInteractionData.Channels"/>/> | |||||
public IReadOnlyCollection<SocketChannel> Channels { get; } | |||||
/// <inheritdoc cref="IComponentInteractionData.Users"/>/> | |||||
public IReadOnlyCollection<RestUser> Users { get; } | |||||
/// <inheritdoc cref="IComponentInteractionData.Roles"/>/> | |||||
public IReadOnlyCollection<SocketRole> Roles { get; } | |||||
/// <inheritdoc cref="IComponentInteractionData.Members"/>/> | |||||
public IReadOnlyCollection<SocketGuildUser> Members { get; } | |||||
#region IComponentInteractionData | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public IReadOnlyCollection<IChannel> Channels { get; } | |||||
IReadOnlyCollection<IChannel> IComponentInteractionData.Channels => Channels; | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public IReadOnlyCollection<IUser> Users { get; } | |||||
IReadOnlyCollection<IUser> IComponentInteractionData.Users => Users; | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public IReadOnlyCollection<IRole> Roles { get; } | |||||
IReadOnlyCollection<IRole> IComponentInteractionData.Roles => Roles; | |||||
/// <inheritdoc /> | |||||
IReadOnlyCollection<IGuildUser> IComponentInteractionData.Members => Members; | |||||
#endregion | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public string Value { get; } | public string Value { get; } | ||||
internal SocketMessageComponentData(Model model) | |||||
internal SocketMessageComponentData(Model model, DiscordSocketClient discord, ClientState state, SocketGuild guild) | |||||
{ | { | ||||
CustomId = model.CustomId; | CustomId = model.CustomId; | ||||
Type = model.ComponentType; | Type = model.ComponentType; | ||||
Values = model.Values.GetValueOrDefault(); | Values = model.Values.GetValueOrDefault(); | ||||
Value = model.Value.GetValueOrDefault(); | Value = model.Value.GetValueOrDefault(); | ||||
if (model.Resolved.IsSpecified) | |||||
{ | |||||
Users = model.Resolved.Value.Users.IsSpecified | |||||
? model.Resolved.Value.Users.Value.Select(user => RestUser.Create(discord, user.Value)).ToImmutableArray() | |||||
: null; | |||||
Members = model.Resolved.Value.Members.IsSpecified | |||||
? model.Resolved.Value.Members.Value.Select(member => | |||||
{ | |||||
member.Value.User = model.Resolved.Value.Users.Value.First(u => u.Key == member.Key).Value; | |||||
return SocketGuildUser.Create(guild, state, member.Value); | |||||
}).ToImmutableArray() | |||||
: null; | |||||
Channels = model.Resolved.Value.Channels.IsSpecified | |||||
? model.Resolved.Value.Channels.Value.Select( | |||||
channel => | |||||
{ | |||||
if (channel.Value.Type is ChannelType.DM) | |||||
return SocketDMChannel.Create(discord, state, channel.Value); | |||||
return (SocketChannel)SocketGuildChannel.Create(guild, state, channel.Value); | |||||
}).ToImmutableArray() | |||||
: null; | |||||
Roles = model.Resolved.Value.Roles.IsSpecified | |||||
? model.Resolved.Value.Roles.Value.Select(role => SocketRole.Create(guild, state, role.Value)).ToImmutableArray() | |||||
: null; | |||||
} | |||||
} | } | ||||
internal SocketMessageComponentData(IMessageComponent component) | |||||
internal SocketMessageComponentData(IMessageComponent component, DiscordSocketClient discord, ClientState state, SocketGuild guild) | |||||
{ | { | ||||
CustomId = component.CustomId; | CustomId = component.CustomId; | ||||
Type = component.Type; | Type = component.Type; | ||||
@@ -51,6 +101,35 @@ namespace Discord.WebSocket | |||||
if (component is API.SelectMenuComponent select) | if (component is API.SelectMenuComponent select) | ||||
{ | { | ||||
Values = select.Values.GetValueOrDefault(null); | Values = select.Values.GetValueOrDefault(null); | ||||
if (select.Resolved.IsSpecified) | |||||
{ | |||||
Users = select.Resolved.Value.Users.IsSpecified | |||||
? select.Resolved.Value.Users.Value.Select(user => RestUser.Create(discord, user.Value)).ToImmutableArray() | |||||
: null; | |||||
Members = select.Resolved.Value.Members.IsSpecified | |||||
? select.Resolved.Value.Members.Value.Select(member => | |||||
{ | |||||
member.Value.User = select.Resolved.Value.Users.Value.First(u => u.Key == member.Key).Value; | |||||
return SocketGuildUser.Create(guild, state, member.Value); | |||||
}).ToImmutableArray() | |||||
: null; | |||||
Channels = select.Resolved.Value.Channels.IsSpecified | |||||
? select.Resolved.Value.Channels.Value.Select( | |||||
channel => | |||||
{ | |||||
if (channel.Value.Type is ChannelType.DM) | |||||
return SocketDMChannel.Create(discord, state, channel.Value); | |||||
return (SocketChannel)SocketGuildChannel.Create(guild, state, channel.Value); | |||||
}).ToImmutableArray() | |||||
: null; | |||||
Roles = select.Resolved.Value.Roles.IsSpecified | |||||
? select.Resolved.Value.Roles.Value.Select(role => SocketRole.Create(guild, state, role.Value)).ToImmutableArray() | |||||
: null; | |||||
} | |||||
} | } | ||||
} | } | ||||
} | } | ||||
@@ -27,8 +27,8 @@ namespace Discord.WebSocket | |||||
var dataModel = model.Data.IsSpecified | var dataModel = model.Data.IsSpecified | ||||
? (DataModel)model.Data.Value | ? (DataModel)model.Data.Value | ||||
: null; | : null; | ||||
Data = new SocketModalData(dataModel); | |||||
Data = new SocketModalData(dataModel, client, client.State, client.Guilds.FirstOrDefault(x => x.Id == model.GuildId.GetValueOrDefault())); | |||||
} | } | ||||
internal new static SocketModal Create(DiscordSocketClient client, ModelBase model, ISocketMessageChannel channel, SocketUser user) | internal new static SocketModal Create(DiscordSocketClient client, ModelBase model, ISocketMessageChannel channel, SocketUser user) | ||||
@@ -22,12 +22,12 @@ namespace Discord.WebSocket | |||||
/// </summary> | /// </summary> | ||||
public IReadOnlyCollection<SocketMessageComponentData> Components { get; } | public IReadOnlyCollection<SocketMessageComponentData> Components { get; } | ||||
internal SocketModalData(Model model) | |||||
internal SocketModalData(Model model, DiscordSocketClient discord, ClientState state, SocketGuild guild) | |||||
{ | { | ||||
CustomId = model.CustomId; | CustomId = model.CustomId; | ||||
Components = model.Components | Components = model.Components | ||||
.SelectMany(x => x.Components) | .SelectMany(x => x.Components) | ||||
.Select(x => new SocketMessageComponentData(x)) | |||||
.Select(x => new SocketMessageComponentData(x, discord, state, guild)) | |||||
.ToArray(); | .ToArray(); | ||||
} | } | ||||