| @@ -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 | namespace Discord | ||||
| { | { | ||||
| [Flags] | [Flags] | ||||
| public enum GuildFeature | |||||
| public enum GuildFeature : long | |||||
| { | { | ||||
| /// <summary> | /// <summary> | ||||
| /// The guild has no features. | /// The guild has no features. | ||||
| @@ -53,6 +53,11 @@ namespace Discord | |||||
| /// <summary> | /// <summary> | ||||
| /// A <see cref="double"/>. | /// A <see cref="double"/>. | ||||
| /// </summary> | /// </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; | using System.Threading.Tasks; | ||||
| namespace Discord | namespace Discord | ||||
| @@ -41,7 +42,7 @@ namespace Discord | |||||
| /// </returns> | /// </returns> | ||||
| /// <seealso cref="IMessage.AddReactionAsync(IEmote, RequestOptions)"/> | /// <seealso cref="IMessage.AddReactionAsync(IEmote, RequestOptions)"/> | ||||
| /// <seealso cref="IEmote"/> | /// <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) | foreach (var rxn in reactions) | ||||
| await msg.AddReactionAsync(rxn, options).ConfigureAwait(false); | await msg.AddReactionAsync(rxn, options).ConfigureAwait(false); | ||||
| @@ -67,7 +68,7 @@ namespace Discord | |||||
| /// </returns> | /// </returns> | ||||
| /// <seealso cref="IMessage.RemoveReactionAsync(IEmote, IUser, RequestOptions)"/> | /// <seealso cref="IMessage.RemoveReactionAsync(IEmote, IUser, RequestOptions)"/> | ||||
| /// <seealso cref="IEmote"/> | /// <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) | foreach (var rxn in reactions) | ||||
| await msg.RemoveReactionAsync(rxn, user, options).ConfigureAwait(false); | await msg.RemoveReactionAsync(rxn, user, options).ConfigureAwait(false); | ||||
| @@ -1,4 +1,7 @@ | |||||
| using Discord.Rest; | |||||
| using System; | using System; | ||||
| using System.Collections.Generic; | |||||
| using System.IO; | |||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| namespace Discord.Interactions | namespace Discord.Interactions | ||||
| @@ -47,18 +50,66 @@ namespace Discord.Interactions | |||||
| AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent components = null, Embed embed = null) => | 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); | 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)"/> | /// <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, | 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) => | 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); | 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[])"/> | /// <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, | 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) => | AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null) => | ||||
| await Context.Channel.SendMessageAsync(text, false, embed, options, allowedMentions, messageReference, components).ConfigureAwait(false); | 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)"/> | /// <inheritdoc cref="IDeletable.DeleteAsync(RequestOptions)"/> | ||||
| protected virtual async Task DeleteOriginalResponseAsync ( ) | |||||
| protected virtual async Task DeleteOriginalResponseAsync() | |||||
| { | { | ||||
| var response = await Context.Interaction.GetOriginalResponseAsync().ConfigureAwait(false); | var response = await Context.Interaction.GetOriginalResponseAsync().ConfigureAwait(false); | ||||
| await response.DeleteAsync().ConfigureAwait(false); | await response.DeleteAsync().ConfigureAwait(false); | ||||
| @@ -183,6 +183,7 @@ namespace Discord.Interactions | |||||
| { | { | ||||
| [typeof(IChannel)] = typeof(DefaultChannelConverter<>), | [typeof(IChannel)] = typeof(DefaultChannelConverter<>), | ||||
| [typeof(IRole)] = typeof(DefaultRoleConverter<>), | [typeof(IRole)] = typeof(DefaultRoleConverter<>), | ||||
| [typeof(IAttachment)] = typeof(DefaultAttachmentConverter<>), | |||||
| [typeof(IUser)] = typeof(DefaultUserConverter<>), | [typeof(IUser)] = typeof(DefaultUserConverter<>), | ||||
| [typeof(IMentionable)] = typeof(DefaultMentionableConverter<>), | [typeof(IMentionable)] = typeof(DefaultMentionableConverter<>), | ||||
| [typeof(IConvertible)] = typeof(DefaultValueConverter<>), | [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 | internal class DefaultRoleConverter<T> : DefaultEntityTypeConverter<T> where T : class, IRole | ||||
| { | { | ||||
| public override ApplicationCommandOptionType GetDiscordType ( ) => ApplicationCommandOptionType.Role; | public override ApplicationCommandOptionType GetDiscordType ( ) => ApplicationCommandOptionType.Role; | ||||
| @@ -18,5 +18,7 @@ namespace Discord.API | |||||
| public Optional<Dictionary<string, Role>> Roles { get; set; } | public Optional<Dictionary<string, Role>> Roles { get; set; } | ||||
| [JsonProperty("messages")] | [JsonProperty("messages")] | ||||
| public Optional<Dictionary<string, Message>> Messages { get; set; } | 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); | 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) | public async Task DeleteInteractionResponseAsync(string interactionToken, RequestOptions options = null) | ||||
| { | { | ||||
| options = RequestOptions.CreateOrClone(options); | options = RequestOptions.CreateOrClone(options); | ||||
| @@ -19,6 +19,9 @@ namespace Discord.Rest | |||||
| internal readonly Dictionary<ulong, RestMessage> Messages | internal readonly Dictionary<ulong, RestMessage> Messages | ||||
| = new Dictionary<ulong, RestMessage>(); | = 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) | internal async Task PopulateAsync(DiscordRestClient discord, RestGuild guild, IRestMessageChannel channel, T model) | ||||
| { | { | ||||
| var resolved = model.Resolved.Value; | var resolved = model.Resolved.Value; | ||||
| @@ -91,6 +94,16 @@ namespace Discord.Rest | |||||
| Messages.Add(message.Id, message); | 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."); | 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) | 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 Model = Discord.API.Interaction; | ||||
| using DataModel = Discord.API.ApplicationCommandInteractionData; | using DataModel = Discord.API.ApplicationCommandInteractionData; | ||||
| using Newtonsoft.Json; | using Newtonsoft.Json; | ||||
| using Discord.Net; | |||||
| namespace Discord.Rest | namespace Discord.Rest | ||||
| { | { | ||||
| @@ -133,7 +134,11 @@ namespace Discord.Rest | |||||
| if(Channel == null && model.ChannelId.IsSpecified) | 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 | UserLocale = model.UserLocale.IsSpecified | ||||
| @@ -43,6 +43,7 @@ namespace Discord.Rest | |||||
| case ApplicationCommandOptionType.Role: | case ApplicationCommandOptionType.Role: | ||||
| case ApplicationCommandOptionType.Channel: | case ApplicationCommandOptionType.Channel: | ||||
| case ApplicationCommandOptionType.Mentionable: | case ApplicationCommandOptionType.Mentionable: | ||||
| case ApplicationCommandOptionType.Attachment: | |||||
| if (ulong.TryParse($"{model.Value.Value}", out var valueId)) | if (ulong.TryParse($"{model.Value.Value}", out var valueId)) | ||||
| { | { | ||||
| switch (Type) | switch (Type) | ||||
| @@ -80,6 +81,9 @@ namespace Discord.Rest | |||||
| } | } | ||||
| } | } | ||||
| break; | break; | ||||
| case ApplicationCommandOptionType.Attachment: | |||||
| Value = data.ResolvableData.Attachments.FirstOrDefault(x => x.Key == valueId).Value; | |||||
| break; | |||||
| default: | default: | ||||
| Value = model.Value.Value; | Value = model.Value.Value; | ||||
| break; | break; | ||||
| @@ -46,6 +46,7 @@ namespace Discord.WebSocket | |||||
| private bool _isDisposed; | private bool _isDisposed; | ||||
| private GatewayIntents _gatewayIntents; | private GatewayIntents _gatewayIntents; | ||||
| private ImmutableArray<StickerPack<SocketSticker>> _defaultStickers; | private ImmutableArray<StickerPack<SocketSticker>> _defaultStickers; | ||||
| private SocketSelfUser _previousSessionUser; | |||||
| /// <summary> | /// <summary> | ||||
| /// Provides access to a REST-only client with a shared state from this client. | /// Provides access to a REST-only client with a shared state from this client. | ||||
| @@ -888,6 +889,7 @@ namespace Discord.WebSocket | |||||
| _sessionId = data.SessionId; | _sessionId = data.SessionId; | ||||
| _unavailableGuildCount = unavailableGuilds; | _unavailableGuildCount = unavailableGuilds; | ||||
| CurrentUser = currentUser; | CurrentUser = currentUser; | ||||
| _previousSessionUser = CurrentUser; | |||||
| State = state; | State = state; | ||||
| } | } | ||||
| catch (Exception ex) | catch (Exception ex) | ||||
| @@ -930,6 +932,9 @@ namespace Discord.WebSocket | |||||
| await GuildAvailableAsync(guild).ConfigureAwait(false); | await GuildAvailableAsync(guild).ConfigureAwait(false); | ||||
| } | } | ||||
| // Restore the previous sessions current user | |||||
| CurrentUser = _previousSessionUser; | |||||
| await _gatewayLogger.InfoAsync("Resumed previous session").ConfigureAwait(false); | await _gatewayLogger.InfoAsync("Resumed previous session").ConfigureAwait(false); | ||||
| } | } | ||||
| break; | break; | ||||
| @@ -2238,60 +2243,40 @@ namespace Discord.WebSocket | |||||
| channel = State.GetDMChannel(data.User.Value.Id); | 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; | break; | ||||
| @@ -39,6 +39,7 @@ namespace Discord.WebSocket | |||||
| case ApplicationCommandOptionType.Role: | case ApplicationCommandOptionType.Role: | ||||
| case ApplicationCommandOptionType.Channel: | case ApplicationCommandOptionType.Channel: | ||||
| case ApplicationCommandOptionType.Mentionable: | case ApplicationCommandOptionType.Mentionable: | ||||
| case ApplicationCommandOptionType.Attachment: | |||||
| if (ulong.TryParse($"{model.Value.Value}", out var valueId)) | if (ulong.TryParse($"{model.Value.Value}", out var valueId)) | ||||
| { | { | ||||
| switch (Type) | switch (Type) | ||||
| @@ -76,6 +77,9 @@ namespace Discord.WebSocket | |||||
| } | } | ||||
| } | } | ||||
| break; | break; | ||||
| case ApplicationCommandOptionType.Attachment: | |||||
| Value = data.ResolvableData.Attachments.FirstOrDefault(x => x.Key == valueId).Value; | |||||
| break; | |||||
| default: | default: | ||||
| Value = model.Value.Value; | Value = model.Value.Value; | ||||
| break; | break; | ||||
| @@ -16,6 +16,9 @@ namespace Discord.WebSocket | |||||
| internal readonly Dictionary<ulong, SocketMessage> Messages | internal readonly Dictionary<ulong, SocketMessage> Messages | ||||
| = new Dictionary<ulong, SocketMessage>(); | = new Dictionary<ulong, SocketMessage>(); | ||||
| internal readonly Dictionary<ulong, Attachment> Attachments | |||||
| = new Dictionary<ulong, Attachment>(); | |||||
| internal SocketResolvableData(DiscordSocketClient discord, ulong? guildId, T model) | internal SocketResolvableData(DiscordSocketClient discord, ulong? guildId, T model) | ||||
| { | { | ||||
| var guild = guildId.HasValue ? discord.GetGuild(guildId.Value) : null; | var guild = guildId.HasValue ? discord.GetGuild(guildId.Value) : null; | ||||
| @@ -104,6 +107,16 @@ namespace Discord.WebSocket | |||||
| Messages.Add(message.Id, message); | 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> | /// <summary> | ||||
| /// The <see cref="ISocketMessageChannel"/> this interaction was used in. | /// The <see cref="ISocketMessageChannel"/> this interaction was used in. | ||||
| /// </summary> | /// </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; } | public ISocketMessageChannel Channel { get; private set; } | ||||
| /// <summary> | /// <summary> | ||||