diff --git a/.github/ISSUE_TEMPLATE/bugreport.yml b/.github/ISSUE_TEMPLATE/bugreport.yml new file mode 100644 index 000000000..81cac4af7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bugreport.yml @@ -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 diff --git a/src/Discord.Net.Core/Entities/Guilds/GuildFeature.cs b/src/Discord.Net.Core/Entities/Guilds/GuildFeature.cs index aec637be2..cb57b2726 100644 --- a/src/Discord.Net.Core/Entities/Guilds/GuildFeature.cs +++ b/src/Discord.Net.Core/Entities/Guilds/GuildFeature.cs @@ -7,7 +7,7 @@ using System.Threading.Tasks; namespace Discord { [Flags] - public enum GuildFeature + public enum GuildFeature : long { /// /// The guild has no features. diff --git a/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOptionType.cs b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOptionType.cs index 0f919f1f6..5bb00797b 100644 --- a/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOptionType.cs +++ b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOptionType.cs @@ -53,6 +53,11 @@ namespace Discord /// /// A . /// - Number = 10 + Number = 10, + + /// + /// A . + /// + Attachment = 11 } } diff --git a/src/Discord.Net.Core/Extensions/MessageExtensions.cs b/src/Discord.Net.Core/Extensions/MessageExtensions.cs index c187ecd5b..cf330c44d 100644 --- a/src/Discord.Net.Core/Extensions/MessageExtensions.cs +++ b/src/Discord.Net.Core/Extensions/MessageExtensions.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Threading.Tasks; namespace Discord @@ -41,7 +42,7 @@ namespace Discord /// /// /// - public static async Task AddReactionsAsync(this IUserMessage msg, IEmote[] reactions, RequestOptions options = null) + public static async Task AddReactionsAsync(this IUserMessage msg, IEnumerable reactions, RequestOptions options = null) { foreach (var rxn in reactions) await msg.AddReactionAsync(rxn, options).ConfigureAwait(false); @@ -67,7 +68,7 @@ namespace Discord /// /// /// - 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 reactions, RequestOptions options = null) { foreach (var rxn in reactions) await msg.RemoveReactionAsync(rxn, user, options).ConfigureAwait(false); diff --git a/src/Discord.Net.Interactions/InteractionModuleBase.cs b/src/Discord.Net.Interactions/InteractionModuleBase.cs index b67ff2eb4..873f4c173 100644 --- a/src/Discord.Net.Interactions/InteractionModuleBase.cs +++ b/src/Discord.Net.Interactions/InteractionModuleBase.cs @@ -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); + /// + 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); + + /// + 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); + + /// + 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); + + /// + protected virtual Task RespondWithFilesAsync(IEnumerable 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); + /// protected virtual async Task 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); + /// + protected virtual Task 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); + + /// + protected virtual Task 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); + + /// + protected virtual Task 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); + + /// + protected virtual Task FollowupWithFilesAsync(IEnumerable 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); + /// protected virtual async Task 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); + /// + protected virtual Task GetOriginalResponseAsync(RequestOptions options = null) + => Context.Interaction.GetOriginalResponseAsync(options); + + /// + protected virtual Task ModifyOriginalResponseAsync(Action func, RequestOptions options = null) + => Context.Interaction.ModifyOriginalResponseAsync(func, options); + /// - protected virtual async Task DeleteOriginalResponseAsync ( ) + protected virtual async Task DeleteOriginalResponseAsync() { var response = await Context.Interaction.GetOriginalResponseAsync().ConfigureAwait(false); await response.DeleteAsync().ConfigureAwait(false); diff --git a/src/Discord.Net.Interactions/InteractionService.cs b/src/Discord.Net.Interactions/InteractionService.cs index 32dd71daf..c1291bd6b 100644 --- a/src/Discord.Net.Interactions/InteractionService.cs +++ b/src/Discord.Net.Interactions/InteractionService.cs @@ -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<>), diff --git a/src/Discord.Net.Interactions/TypeConverters/DefaultEntityTypeConverter.cs b/src/Discord.Net.Interactions/TypeConverters/DefaultEntityTypeConverter.cs index 9107fbf35..fb493ed72 100644 --- a/src/Discord.Net.Interactions/TypeConverters/DefaultEntityTypeConverter.cs +++ b/src/Discord.Net.Interactions/TypeConverters/DefaultEntityTypeConverter.cs @@ -20,6 +20,11 @@ namespace Discord.Interactions } } + internal class DefaultAttachmentConverter : DefaultEntityTypeConverter where T : class, IAttachment + { + public override ApplicationCommandOptionType GetDiscordType() => ApplicationCommandOptionType.Attachment; + } + internal class DefaultRoleConverter : DefaultEntityTypeConverter where T : class, IRole { public override ApplicationCommandOptionType GetDiscordType ( ) => ApplicationCommandOptionType.Role; diff --git a/src/Discord.Net.Rest/API/Common/ApplicationCommandInteractionDataResolved.cs b/src/Discord.Net.Rest/API/Common/ApplicationCommandInteractionDataResolved.cs index 5b4b83e23..690be6cef 100644 --- a/src/Discord.Net.Rest/API/Common/ApplicationCommandInteractionDataResolved.cs +++ b/src/Discord.Net.Rest/API/Common/ApplicationCommandInteractionDataResolved.cs @@ -18,5 +18,7 @@ namespace Discord.API public Optional> Roles { get; set; } [JsonProperty("messages")] public Optional> Messages { get; set; } + [JsonProperty("attachments")] + public Optional> Attachments { get; set; } } } diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs index 129465618..f6d579d79 100644 --- a/src/Discord.Net.Rest/DiscordRestApiClient.cs +++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs @@ -1362,6 +1362,12 @@ namespace Discord.API return await SendJsonAsync("PATCH", () => $"webhooks/{CurrentApplicationId}/{interactionToken}/messages/@original", args, new BucketIds(), options: options); } + public async Task ModifyInteractionResponseAsync(UploadWebhookFileParams args, string interactionToken, RequestOptions options = null) + { + options = RequestOptions.CreateOrClone(options); + + return await SendMultipartAsync("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); diff --git a/src/Discord.Net.Rest/Entities/Interactions/CommandBase/RestResolvableData.cs b/src/Discord.Net.Rest/Entities/Interactions/CommandBase/RestResolvableData.cs index 710207ef9..9353a8530 100644 --- a/src/Discord.Net.Rest/Entities/Interactions/CommandBase/RestResolvableData.cs +++ b/src/Discord.Net.Rest/Entities/Interactions/CommandBase/RestResolvableData.cs @@ -19,6 +19,9 @@ namespace Discord.Rest internal readonly Dictionary Messages = new Dictionary(); + internal readonly Dictionary Attachments + = new Dictionary(); + 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); + } + } } } } diff --git a/src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs b/src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs index 8605bf02b..e345bfa94 100644 --- a/src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs +++ b/src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs @@ -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.Unspecified, - AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value?.ToModel() : Optional.Unspecified, - Components = args.Components.IsSpecified ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() : Optional.Unspecified, - Flags = args.Flags - }; + var apiArgs = new ModifyInteractionResponseParams + { + Content = args.Content, + Embeds = apiEmbeds?.ToArray() ?? Optional.Unspecified, + AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value?.ToModel() : Optional.Unspecified, + Components = args.Components.IsSpecified ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() : Optional.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.Unspecified, + AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value?.ToModel() : Optional.Unspecified, + MessageComponents = args.Components.IsSpecified ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() : Optional.Unspecified, + }; + + return await client.ApiClient.ModifyInteractionResponseAsync(apiArgs, token, options).ConfigureAwait(false); + } } public static async Task DeleteInteractionResponseAsync(BaseDiscordClient client, RestInteractionMessage message, RequestOptions options = null) diff --git a/src/Discord.Net.Rest/Entities/Interactions/RestInteraction.cs b/src/Discord.Net.Rest/Entities/Interactions/RestInteraction.cs index 42b265fcd..5894ee264 100644 --- a/src/Discord.Net.Rest/Entities/Interactions/RestInteraction.cs +++ b/src/Discord.Net.Rest/Entities/Interactions/RestInteraction.cs @@ -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 diff --git a/src/Discord.Net.Rest/Entities/Interactions/SlashCommands/RestSlashCommandDataOption.cs b/src/Discord.Net.Rest/Entities/Interactions/SlashCommands/RestSlashCommandDataOption.cs index bb931f68e..cbb958968 100644 --- a/src/Discord.Net.Rest/Entities/Interactions/SlashCommands/RestSlashCommandDataOption.cs +++ b/src/Discord.Net.Rest/Entities/Interactions/SlashCommands/RestSlashCommandDataOption.cs @@ -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; diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index 9eee8c915..e7f9b10ee 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -46,6 +46,7 @@ namespace Discord.WebSocket private bool _isDisposed; private GatewayIntents _gatewayIntents; private ImmutableArray> _defaultStickers; + private SocketSelfUser _previousSessionUser; /// /// 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; diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SlashCommands/SocketSlashCommandDataOption.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SlashCommands/SocketSlashCommandDataOption.cs index 265eda75b..2a44b4e03 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/SlashCommands/SocketSlashCommandDataOption.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/SlashCommands/SocketSlashCommandDataOption.cs @@ -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; diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketResolvableData.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketResolvableData.cs index c065637ca..d722c5a13 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketResolvableData.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketResolvableData.cs @@ -16,6 +16,9 @@ namespace Discord.WebSocket internal readonly Dictionary Messages = new Dictionary(); + internal readonly Dictionary Attachments + = new Dictionary(); + 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); + } + } } } } diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs index 412aec481..1c3563ab0 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs @@ -17,6 +17,10 @@ namespace Discord.WebSocket /// /// The this interaction was used in. /// + /// + /// If the channel isn't cached or the bot doesn't have access to it then + /// this property will be . + /// public ISocketMessageChannel Channel { get; private set; } ///