| @@ -0,0 +1,77 @@ | |||
| name: 🐞 Bug Report | |||
| description: File a bug report | |||
| title: "[Bug]: " | |||
| labels: ["bug"] | |||
| body: | |||
| - type: markdown | |||
| attributes: | |||
| value: Thanks for taking the time to fill out this bug report! | |||
| - type: checkboxes | |||
| attributes: | |||
| label: Check The Docs | |||
| description: Please refer to our [FAQs](https://discordnet.dev/faq/basics/getting-started.html), [Documentation](https://discordnet.dev/api/index.html), | |||
| and [Migration Guide](https://discordnet.dev/guides/v2_v3_guide/v2_to_v3_guide.html) before reporting issues. | |||
| options: | |||
| - label: "I double checked the docs and couldn't find any useful information." | |||
| required: true | |||
| - type: checkboxes | |||
| attributes: | |||
| label: Verify Issue Source | |||
| description: If your issue is related to an exception make sure the error was thrown by Discord.Net, and not your code or another library. | |||
| If you get an `HttpException` with the error code `401`, then the error is caused by your bot's permissions, not dnet. | |||
| options: | |||
| - label: I verified the issue was caused by Discord.Net. | |||
| required: true | |||
| - type: checkboxes | |||
| attributes: | |||
| label: Check your intents | |||
| description: If your issue is related to not receiving expected events, you may have setup your gateway intents incorrectly. | |||
| Newer versions of Discord.Net use a more modern version of Discord's API that requires you tell it what events | |||
| you want to receive. Discord.Net defaults to all non-privleged intents, but if your bot requires privileged intents | |||
| you need specify them in your clients config. You can see what intents you need for your events | |||
| [here](https://discord.com/developers/docs/topics/gateway#list-of-intents). | |||
| options: | |||
| - label: I double checked that I have the required intents. | |||
| required: true | |||
| - type: textarea | |||
| id: description | |||
| attributes: | |||
| label: Description | |||
| description: A brief explination of the bug. | |||
| placeholder: When I start a DiscordSocketClient without stopping it, the gateway thread gets blocked. | |||
| validations: | |||
| required: true | |||
| - type: input | |||
| id: version | |||
| attributes: | |||
| label: Version | |||
| description: What version of Discord.Net are you using? | |||
| placeholder: ex. 3.1.0 | |||
| validations: | |||
| required: true | |||
| - type: input | |||
| id: working-version | |||
| attributes: | |||
| label: Working Version | |||
| description: If this worked on an older version of Discord.Net put that version here. | |||
| placeholder: ex. 2.4.0 | |||
| validations: | |||
| required: false | |||
| - type: textarea | |||
| id: logs | |||
| attributes: | |||
| label: Logs | |||
| description: Add applicable logs and/or a stacktrace here. | |||
| validations: | |||
| required: true | |||
| - type: textarea | |||
| id: sample | |||
| attributes: | |||
| label: Sample | |||
| description: Include a (short) code sample that reproduces your issue 100% of time (comments would be great). | |||
| placeholder: | | |||
| ```cs | |||
| My.Code(); | |||
| ``` | |||
| validations: | |||
| required: false | |||
| @@ -7,7 +7,7 @@ using System.Threading.Tasks; | |||
| namespace Discord | |||
| { | |||
| [Flags] | |||
| public enum GuildFeature | |||
| public enum GuildFeature : long | |||
| { | |||
| /// <summary> | |||
| /// The guild has no features. | |||
| @@ -53,6 +53,11 @@ namespace Discord | |||
| /// <summary> | |||
| /// A <see cref="double"/>. | |||
| /// </summary> | |||
| Number = 10 | |||
| Number = 10, | |||
| /// <summary> | |||
| /// A <see cref="Discord.Attachment"/>. | |||
| /// </summary> | |||
| Attachment = 11 | |||
| } | |||
| } | |||
| @@ -1,3 +1,4 @@ | |||
| using System.Collections.Generic; | |||
| using System.Threading.Tasks; | |||
| namespace Discord | |||
| @@ -41,7 +42,7 @@ namespace Discord | |||
| /// </returns> | |||
| /// <seealso cref="IMessage.AddReactionAsync(IEmote, RequestOptions)"/> | |||
| /// <seealso cref="IEmote"/> | |||
| public static async Task AddReactionsAsync(this IUserMessage msg, IEmote[] reactions, RequestOptions options = null) | |||
| public static async Task AddReactionsAsync(this IUserMessage msg, IEnumerable<IEmote> reactions, RequestOptions options = null) | |||
| { | |||
| foreach (var rxn in reactions) | |||
| await msg.AddReactionAsync(rxn, options).ConfigureAwait(false); | |||
| @@ -67,7 +68,7 @@ namespace Discord | |||
| /// </returns> | |||
| /// <seealso cref="IMessage.RemoveReactionAsync(IEmote, IUser, RequestOptions)"/> | |||
| /// <seealso cref="IEmote"/> | |||
| public static async Task RemoveReactionsAsync(this IUserMessage msg, IUser user, IEmote[] reactions, RequestOptions options = null) | |||
| public static async Task RemoveReactionsAsync(this IUserMessage msg, IUser user, IEnumerable<IEmote> reactions, RequestOptions options = null) | |||
| { | |||
| foreach (var rxn in reactions) | |||
| await msg.RemoveReactionAsync(rxn, user, options).ConfigureAwait(false); | |||
| @@ -1,4 +1,7 @@ | |||
| using Discord.Rest; | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.IO; | |||
| using System.Threading.Tasks; | |||
| namespace Discord.Interactions | |||
| @@ -47,18 +50,66 @@ namespace Discord.Interactions | |||
| AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent components = null, Embed embed = null) => | |||
| await Context.Interaction.RespondAsync(text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options).ConfigureAwait(false); | |||
| /// <inheritdoc cref="IDiscordInteraction.RespondWithFileAsync(Stream, string, string, Embed[], bool, bool, AllowedMentions, MessageComponent, Embed, RequestOptions)"/> | |||
| protected virtual Task RespondWithFileAsync(Stream fileStream, string fileName, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, | |||
| AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null) | |||
| => Context.Interaction.RespondWithFileAsync(fileStream, fileName, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options); | |||
| /// <inheritdoc cref="IDiscordInteraction.RespondWithFileAsync(string, string, string, Embed[], bool, bool, AllowedMentions, MessageComponent, Embed, RequestOptions)"/> | |||
| protected virtual Task RespondWithFileAsync(string filePath, string fileName = null, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, | |||
| AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null) | |||
| => Context.Interaction.RespondWithFileAsync(filePath, fileName, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options); | |||
| /// <inheritdoc cref="IDiscordInteraction.RespondWithFileAsync(FileAttachment, string, Embed[], bool, bool, AllowedMentions, MessageComponent, Embed, RequestOptions)"/> | |||
| protected virtual Task RespondWithFileAsync(FileAttachment attachment, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, | |||
| AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null) | |||
| => Context.Interaction.RespondWithFileAsync(attachment, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options); | |||
| /// <inheritdoc cref="IDiscordInteraction.RespondWithFilesAsync(IEnumerable{FileAttachment}, string, Embed[], bool, bool, AllowedMentions, MessageComponent, Embed, RequestOptions)"/> | |||
| protected virtual Task RespondWithFilesAsync(IEnumerable<FileAttachment> attachments, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, | |||
| AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null) | |||
| => Context.Interaction.RespondWithFilesAsync(attachments, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options); | |||
| /// <inheritdoc cref="IDiscordInteraction.FollowupAsync(string, Embed[], bool, bool, AllowedMentions, RequestOptions, MessageComponent, Embed)"/> | |||
| protected virtual async Task<IUserMessage> FollowupAsync (string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, | |||
| AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent components = null, Embed embed = null) => | |||
| await Context.Interaction.FollowupAsync(text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options).ConfigureAwait(false); | |||
| /// <inheritdoc cref="IDiscordInteraction.FollowupWithFileAsync(Stream, string, string, Embed[], bool, bool, AllowedMentions, MessageComponent, Embed, RequestOptions)"/> | |||
| protected virtual Task<IUserMessage> FollowupWithFileAsync(Stream fileStream, string fileName, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, | |||
| AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null) | |||
| => Context.Interaction.FollowupWithFileAsync(fileStream, fileName, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options); | |||
| /// <inheritdoc cref="IDiscordInteraction.FollowupWithFileAsync(string, string, string, Embed[], bool, bool, AllowedMentions, MessageComponent, Embed, RequestOptions)"/> | |||
| protected virtual Task<IUserMessage> FollowupWithFileAsync(string filePath, string fileName = null, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, | |||
| AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null) | |||
| => Context.Interaction.FollowupWithFileAsync(filePath, fileName, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options); | |||
| /// <inheritdoc cref="IDiscordInteraction.FollowupWithFileAsync(FileAttachment, string, Embed[], bool, bool, AllowedMentions, MessageComponent, Embed, RequestOptions)"/> | |||
| protected virtual Task<IUserMessage> FollowupWithFileAsync(FileAttachment attachment, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, | |||
| AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null) | |||
| => Context.Interaction.FollowupWithFileAsync(attachment, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options); | |||
| /// <inheritdoc cref="IDiscordInteraction.FollowupWithFilesAsync(IEnumerable{FileAttachment}, string, Embed[], bool, bool, AllowedMentions, MessageComponent, Embed, RequestOptions)"/> | |||
| protected virtual Task<IUserMessage> FollowupWithFilesAsync(IEnumerable<FileAttachment> attachments, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, | |||
| AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null) | |||
| => Context.Interaction.FollowupWithFilesAsync(attachments, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options); | |||
| /// <inheritdoc cref="IMessageChannel.SendMessageAsync(string, bool, Embed, RequestOptions, AllowedMentions, MessageReference, MessageComponent, ISticker[], Embed[])"/> | |||
| protected virtual async Task<IUserMessage> ReplyAsync (string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, | |||
| AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null) => | |||
| await Context.Channel.SendMessageAsync(text, false, embed, options, allowedMentions, messageReference, components).ConfigureAwait(false); | |||
| /// <inheritdoc cref="IDiscordInteraction.GetOriginalResponseAsync(RequestOptions)"/> | |||
| protected virtual Task<IUserMessage> GetOriginalResponseAsync(RequestOptions options = null) | |||
| => Context.Interaction.GetOriginalResponseAsync(options); | |||
| /// <inheritdoc cref="IDiscordInteraction.ModifyOriginalResponseAsync(Action{MessageProperties}, RequestOptions)"/> | |||
| protected virtual Task<IUserMessage> ModifyOriginalResponseAsync(Action<MessageProperties> func, RequestOptions options = null) | |||
| => Context.Interaction.ModifyOriginalResponseAsync(func, options); | |||
| /// <inheritdoc cref="IDeletable.DeleteAsync(RequestOptions)"/> | |||
| protected virtual async Task DeleteOriginalResponseAsync ( ) | |||
| protected virtual async Task DeleteOriginalResponseAsync() | |||
| { | |||
| var response = await Context.Interaction.GetOriginalResponseAsync().ConfigureAwait(false); | |||
| await response.DeleteAsync().ConfigureAwait(false); | |||
| @@ -183,6 +183,7 @@ namespace Discord.Interactions | |||
| { | |||
| [typeof(IChannel)] = typeof(DefaultChannelConverter<>), | |||
| [typeof(IRole)] = typeof(DefaultRoleConverter<>), | |||
| [typeof(IAttachment)] = typeof(DefaultAttachmentConverter<>), | |||
| [typeof(IUser)] = typeof(DefaultUserConverter<>), | |||
| [typeof(IMentionable)] = typeof(DefaultMentionableConverter<>), | |||
| [typeof(IConvertible)] = typeof(DefaultValueConverter<>), | |||
| @@ -20,6 +20,11 @@ namespace Discord.Interactions | |||
| } | |||
| } | |||
| internal class DefaultAttachmentConverter<T> : DefaultEntityTypeConverter<T> where T : class, IAttachment | |||
| { | |||
| public override ApplicationCommandOptionType GetDiscordType() => ApplicationCommandOptionType.Attachment; | |||
| } | |||
| internal class DefaultRoleConverter<T> : DefaultEntityTypeConverter<T> where T : class, IRole | |||
| { | |||
| public override ApplicationCommandOptionType GetDiscordType ( ) => ApplicationCommandOptionType.Role; | |||
| @@ -18,5 +18,7 @@ namespace Discord.API | |||
| public Optional<Dictionary<string, Role>> Roles { get; set; } | |||
| [JsonProperty("messages")] | |||
| public Optional<Dictionary<string, Message>> Messages { get; set; } | |||
| [JsonProperty("attachments")] | |||
| public Optional<Dictionary<string, Attachment>> Attachments { get; set; } | |||
| } | |||
| } | |||
| @@ -1362,6 +1362,12 @@ namespace Discord.API | |||
| return await SendJsonAsync<Message>("PATCH", () => $"webhooks/{CurrentApplicationId}/{interactionToken}/messages/@original", args, new BucketIds(), options: options); | |||
| } | |||
| public async Task<Message> ModifyInteractionResponseAsync(UploadWebhookFileParams args, string interactionToken, RequestOptions options = null) | |||
| { | |||
| options = RequestOptions.CreateOrClone(options); | |||
| return await SendMultipartAsync<Message>("PATCH", () => $"webhooks/{CurrentApplicationId}/{interactionToken}/messages/@original", args.ToDictionary(), new BucketIds(), options: options); | |||
| } | |||
| public async Task DeleteInteractionResponseAsync(string interactionToken, RequestOptions options = null) | |||
| { | |||
| options = RequestOptions.CreateOrClone(options); | |||
| @@ -19,6 +19,9 @@ namespace Discord.Rest | |||
| internal readonly Dictionary<ulong, RestMessage> Messages | |||
| = new Dictionary<ulong, RestMessage>(); | |||
| internal readonly Dictionary<ulong, Attachment> Attachments | |||
| = new Dictionary<ulong, Attachment>(); | |||
| internal async Task PopulateAsync(DiscordRestClient discord, RestGuild guild, IRestMessageChannel channel, T model) | |||
| { | |||
| var resolved = model.Resolved.Value; | |||
| @@ -91,6 +94,16 @@ namespace Discord.Rest | |||
| Messages.Add(message.Id, message); | |||
| } | |||
| } | |||
| if (resolved.Attachments.IsSpecified) | |||
| { | |||
| foreach (var attachment in resolved.Attachments.Value) | |||
| { | |||
| var discordAttachment = Attachment.Create(attachment.Value); | |||
| Attachments.Add(ulong.Parse(attachment.Key), discordAttachment); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -424,16 +424,31 @@ namespace Discord.Rest | |||
| Preconditions.AtMost(apiEmbeds?.Count ?? 0, 10, nameof(args.Embeds), "A max of 10 embeds are allowed."); | |||
| var apiArgs = new ModifyInteractionResponseParams | |||
| if (!args.Attachments.IsSpecified) | |||
| { | |||
| Content = args.Content, | |||
| Embeds = apiEmbeds?.ToArray() ?? Optional<API.Embed[]>.Unspecified, | |||
| AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value?.ToModel() : Optional<API.AllowedMentions>.Unspecified, | |||
| Components = args.Components.IsSpecified ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() : Optional<API.ActionRowComponent[]>.Unspecified, | |||
| Flags = args.Flags | |||
| }; | |||
| var apiArgs = new ModifyInteractionResponseParams | |||
| { | |||
| Content = args.Content, | |||
| Embeds = apiEmbeds?.ToArray() ?? Optional<API.Embed[]>.Unspecified, | |||
| AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value?.ToModel() : Optional<API.AllowedMentions>.Unspecified, | |||
| Components = args.Components.IsSpecified ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() : Optional<API.ActionRowComponent[]>.Unspecified, | |||
| Flags = args.Flags | |||
| }; | |||
| return await client.ApiClient.ModifyInteractionResponseAsync(apiArgs, token, options).ConfigureAwait(false); | |||
| return await client.ApiClient.ModifyInteractionResponseAsync(apiArgs, token, options).ConfigureAwait(false); | |||
| } | |||
| else | |||
| { | |||
| var apiArgs = new UploadWebhookFileParams(args.Attachments.Value.ToArray()) | |||
| { | |||
| Content = args.Content, | |||
| Embeds = apiEmbeds?.ToArray() ?? Optional<API.Embed[]>.Unspecified, | |||
| AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value?.ToModel() : Optional<API.AllowedMentions>.Unspecified, | |||
| MessageComponents = args.Components.IsSpecified ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() : Optional<API.ActionRowComponent[]>.Unspecified, | |||
| }; | |||
| return await client.ApiClient.ModifyInteractionResponseAsync(apiArgs, token, options).ConfigureAwait(false); | |||
| } | |||
| } | |||
| public static async Task DeleteInteractionResponseAsync(BaseDiscordClient client, RestInteractionMessage message, RequestOptions options = null) | |||
| @@ -7,6 +7,7 @@ using System.Threading.Tasks; | |||
| using Model = Discord.API.Interaction; | |||
| using DataModel = Discord.API.ApplicationCommandInteractionData; | |||
| using Newtonsoft.Json; | |||
| using Discord.Net; | |||
| namespace Discord.Rest | |||
| { | |||
| @@ -133,7 +134,11 @@ namespace Discord.Rest | |||
| if(Channel == null && model.ChannelId.IsSpecified) | |||
| { | |||
| Channel = (IRestMessageChannel)await discord.GetChannelAsync(model.ChannelId.Value); | |||
| try | |||
| { | |||
| Channel = (IRestMessageChannel)await discord.GetChannelAsync(model.ChannelId.Value); | |||
| } | |||
| catch(HttpException x) when(x.DiscordCode == DiscordErrorCode.MissingPermissions) { } // ignore | |||
| } | |||
| UserLocale = model.UserLocale.IsSpecified | |||
| @@ -43,6 +43,7 @@ namespace Discord.Rest | |||
| case ApplicationCommandOptionType.Role: | |||
| case ApplicationCommandOptionType.Channel: | |||
| case ApplicationCommandOptionType.Mentionable: | |||
| case ApplicationCommandOptionType.Attachment: | |||
| if (ulong.TryParse($"{model.Value.Value}", out var valueId)) | |||
| { | |||
| switch (Type) | |||
| @@ -80,6 +81,9 @@ namespace Discord.Rest | |||
| } | |||
| } | |||
| break; | |||
| case ApplicationCommandOptionType.Attachment: | |||
| Value = data.ResolvableData.Attachments.FirstOrDefault(x => x.Key == valueId).Value; | |||
| break; | |||
| default: | |||
| Value = model.Value.Value; | |||
| break; | |||
| @@ -46,6 +46,7 @@ namespace Discord.WebSocket | |||
| private bool _isDisposed; | |||
| private GatewayIntents _gatewayIntents; | |||
| private ImmutableArray<StickerPack<SocketSticker>> _defaultStickers; | |||
| private SocketSelfUser _previousSessionUser; | |||
| /// <summary> | |||
| /// Provides access to a REST-only client with a shared state from this client. | |||
| @@ -888,6 +889,7 @@ namespace Discord.WebSocket | |||
| _sessionId = data.SessionId; | |||
| _unavailableGuildCount = unavailableGuilds; | |||
| CurrentUser = currentUser; | |||
| _previousSessionUser = CurrentUser; | |||
| State = state; | |||
| } | |||
| catch (Exception ex) | |||
| @@ -930,6 +932,9 @@ namespace Discord.WebSocket | |||
| await GuildAvailableAsync(guild).ConfigureAwait(false); | |||
| } | |||
| // Restore the previous sessions current user | |||
| CurrentUser = _previousSessionUser; | |||
| await _gatewayLogger.InfoAsync("Resumed previous session").ConfigureAwait(false); | |||
| } | |||
| break; | |||
| @@ -2238,60 +2243,40 @@ namespace Discord.WebSocket | |||
| channel = State.GetDMChannel(data.User.Value.Id); | |||
| } | |||
| if (channel == null) | |||
| var guild = (channel as SocketGuildChannel)?.Guild; | |||
| if (guild != null && !guild.IsSynced) | |||
| { | |||
| var channelModel = await Rest.ApiClient.GetChannelAsync(data.ChannelId.Value); | |||
| if (data.GuildId.IsSpecified) | |||
| channel = SocketTextChannel.Create(State.GetGuild(data.GuildId.Value), State, channelModel); | |||
| else | |||
| channel = (SocketChannel)SocketChannel.CreatePrivate(this, State, channelModel); | |||
| State.AddChannel(channel); | |||
| await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); | |||
| return; | |||
| } | |||
| if (channel is ISocketMessageChannel textChannel) | |||
| { | |||
| var guild = (channel as SocketGuildChannel)?.Guild; | |||
| if (guild != null && !guild.IsSynced) | |||
| { | |||
| await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); | |||
| return; | |||
| } | |||
| var interaction = SocketInteraction.Create(this, data, channel as ISocketMessageChannel); | |||
| var interaction = SocketInteraction.Create(this, data, channel as ISocketMessageChannel); | |||
| await TimedInvokeAsync(_interactionCreatedEvent, nameof(InteractionCreated), interaction).ConfigureAwait(false); | |||
| await TimedInvokeAsync(_interactionCreatedEvent, nameof(InteractionCreated), interaction).ConfigureAwait(false); | |||
| switch (interaction) | |||
| { | |||
| case SocketSlashCommand slashCommand: | |||
| await TimedInvokeAsync(_slashCommandExecuted, nameof(SlashCommandExecuted), slashCommand).ConfigureAwait(false); | |||
| break; | |||
| case SocketMessageComponent messageComponent: | |||
| if(messageComponent.Data.Type == ComponentType.SelectMenu) | |||
| await TimedInvokeAsync(_selectMenuExecuted, nameof(SelectMenuExecuted), messageComponent).ConfigureAwait(false); | |||
| if(messageComponent.Data.Type == ComponentType.Button) | |||
| await TimedInvokeAsync(_buttonExecuted, nameof(ButtonExecuted), messageComponent).ConfigureAwait(false); | |||
| break; | |||
| case SocketUserCommand userCommand: | |||
| await TimedInvokeAsync(_userCommandExecuted, nameof(UserCommandExecuted), userCommand).ConfigureAwait(false); | |||
| break; | |||
| case SocketMessageCommand messageCommand: | |||
| await TimedInvokeAsync(_messageCommandExecuted, nameof(MessageCommandExecuted), messageCommand).ConfigureAwait(false); | |||
| break; | |||
| case SocketAutocompleteInteraction autocomplete: | |||
| await TimedInvokeAsync(_autocompleteExecuted, nameof(AutocompleteExecuted), autocomplete).ConfigureAwait(false); | |||
| break; | |||
| case SocketModal modal: | |||
| await TimedInvokeAsync(_modalSubmitted, nameof(ModalSubmitted), modal).ConfigureAwait(false); | |||
| break; | |||
| } | |||
| } | |||
| else | |||
| switch (interaction) | |||
| { | |||
| await UnknownChannelAsync(type, data.ChannelId.Value).ConfigureAwait(false); | |||
| return; | |||
| case SocketSlashCommand slashCommand: | |||
| await TimedInvokeAsync(_slashCommandExecuted, nameof(SlashCommandExecuted), slashCommand).ConfigureAwait(false); | |||
| break; | |||
| case SocketMessageComponent messageComponent: | |||
| if (messageComponent.Data.Type == ComponentType.SelectMenu) | |||
| await TimedInvokeAsync(_selectMenuExecuted, nameof(SelectMenuExecuted), messageComponent).ConfigureAwait(false); | |||
| if (messageComponent.Data.Type == ComponentType.Button) | |||
| await TimedInvokeAsync(_buttonExecuted, nameof(ButtonExecuted), messageComponent).ConfigureAwait(false); | |||
| break; | |||
| case SocketUserCommand userCommand: | |||
| await TimedInvokeAsync(_userCommandExecuted, nameof(UserCommandExecuted), userCommand).ConfigureAwait(false); | |||
| break; | |||
| case SocketMessageCommand messageCommand: | |||
| await TimedInvokeAsync(_messageCommandExecuted, nameof(MessageCommandExecuted), messageCommand).ConfigureAwait(false); | |||
| break; | |||
| case SocketAutocompleteInteraction autocomplete: | |||
| await TimedInvokeAsync(_autocompleteExecuted, nameof(AutocompleteExecuted), autocomplete).ConfigureAwait(false); | |||
| break; | |||
| case SocketModal modal: | |||
| await TimedInvokeAsync(_modalSubmitted, nameof(ModalSubmitted), modal).ConfigureAwait(false); | |||
| break; | |||
| } | |||
| } | |||
| break; | |||
| @@ -39,6 +39,7 @@ namespace Discord.WebSocket | |||
| case ApplicationCommandOptionType.Role: | |||
| case ApplicationCommandOptionType.Channel: | |||
| case ApplicationCommandOptionType.Mentionable: | |||
| case ApplicationCommandOptionType.Attachment: | |||
| if (ulong.TryParse($"{model.Value.Value}", out var valueId)) | |||
| { | |||
| switch (Type) | |||
| @@ -76,6 +77,9 @@ namespace Discord.WebSocket | |||
| } | |||
| } | |||
| break; | |||
| case ApplicationCommandOptionType.Attachment: | |||
| Value = data.ResolvableData.Attachments.FirstOrDefault(x => x.Key == valueId).Value; | |||
| break; | |||
| default: | |||
| Value = model.Value.Value; | |||
| break; | |||
| @@ -16,6 +16,9 @@ namespace Discord.WebSocket | |||
| internal readonly Dictionary<ulong, SocketMessage> Messages | |||
| = new Dictionary<ulong, SocketMessage>(); | |||
| internal readonly Dictionary<ulong, Attachment> Attachments | |||
| = new Dictionary<ulong, Attachment>(); | |||
| internal SocketResolvableData(DiscordSocketClient discord, ulong? guildId, T model) | |||
| { | |||
| var guild = guildId.HasValue ? discord.GetGuild(guildId.Value) : null; | |||
| @@ -104,6 +107,16 @@ namespace Discord.WebSocket | |||
| Messages.Add(message.Id, message); | |||
| } | |||
| } | |||
| if (resolved.Attachments.IsSpecified) | |||
| { | |||
| foreach (var attachment in resolved.Attachments.Value) | |||
| { | |||
| var discordAttachment = Attachment.Create(attachment.Value); | |||
| Attachments.Add(ulong.Parse(attachment.Key), discordAttachment); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -17,6 +17,10 @@ namespace Discord.WebSocket | |||
| /// <summary> | |||
| /// The <see cref="ISocketMessageChannel"/> this interaction was used in. | |||
| /// </summary> | |||
| /// <remarks> | |||
| /// If the channel isn't cached or the bot doesn't have access to it then | |||
| /// this property will be <see langword="null"/>. | |||
| /// </remarks> | |||
| public ISocketMessageChannel Channel { get; private set; } | |||
| /// <summary> | |||