@@ -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> | |||