| @@ -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(); | ||||
| } | } | ||||