diff --git a/src/Discord.Net.Core/Entities/Interactions/ContextMenus/IMessageCommandInteraction.cs b/src/Discord.Net.Core/Entities/Interactions/ContextMenus/IMessageCommandInteraction.cs index b1b331e8b..07d66bfcb 100644 --- a/src/Discord.Net.Core/Entities/Interactions/ContextMenus/IMessageCommandInteraction.cs +++ b/src/Discord.Net.Core/Entities/Interactions/ContextMenus/IMessageCommandInteraction.cs @@ -3,7 +3,7 @@ namespace Discord /// /// Represents a Message Command interaction. /// - public interface IMessageCommandInteraction : IDiscordInteraction + public interface IMessageCommandInteraction : IApplicationCommandInteraction { /// /// Gets the data associated with this interaction. diff --git a/src/Discord.Net.Core/Entities/Interactions/ContextMenus/IUserCommandInteraction.cs b/src/Discord.Net.Core/Entities/Interactions/ContextMenus/IUserCommandInteraction.cs index f7cfd67f0..2ffdfd9f6 100644 --- a/src/Discord.Net.Core/Entities/Interactions/ContextMenus/IUserCommandInteraction.cs +++ b/src/Discord.Net.Core/Entities/Interactions/ContextMenus/IUserCommandInteraction.cs @@ -3,7 +3,7 @@ namespace Discord /// /// Represents a User Command interaction. /// - public interface IUserCommandInteraction : IDiscordInteraction + public interface IUserCommandInteraction : IApplicationCommandInteraction { /// /// Gets the data associated with this interaction. diff --git a/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandInteraction.cs b/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandInteraction.cs new file mode 100644 index 000000000..b079a47be --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandInteraction.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord +{ + /// + /// Represents an application command interaction. + /// + public interface IApplicationCommandInteraction : IDiscordInteraction + { + /// + /// Gets the data of the application command interaction + /// + new IApplicationCommandInteractionData Data { get; } + } +} diff --git a/src/Discord.Net.Core/Entities/Interactions/IDiscordInteraction.cs b/src/Discord.Net.Core/Entities/Interactions/IDiscordInteraction.cs index 38a543964..77971b9f3 100644 --- a/src/Discord.Net.Core/Entities/Interactions/IDiscordInteraction.cs +++ b/src/Discord.Net.Core/Entities/Interactions/IDiscordInteraction.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using System.Threading.Tasks; @@ -34,6 +35,11 @@ namespace Discord /// int Version { get; } + /// + /// Gets the user who invoked the interaction. + /// + IUser User { get; } + /// /// Responds to an Interaction with type . /// @@ -43,10 +49,14 @@ namespace Discord /// if the response should be hidden to everyone besides the invoker of the command, otherwise . /// The allowed mentions for this response. /// The request options for this response. - /// A to be sent with this response. + /// A to be sent with this response. /// A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored. - Task RespondAsync(string text = null, Embed[] embeds = null, bool isTTS = false, - bool ephemeral = false, AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null); + /// + /// A task that represents an asynchronous send operation for delivering the message. The task result + /// contains the sent message. + /// + Task RespondAsync(string text = null, Embed[] embeds = null, bool isTTS = false, + bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null); /// /// Sends a followup message for this interaction. @@ -57,13 +67,14 @@ namespace Discord /// if the response should be hidden to everyone besides the invoker of the command, otherwise . /// The allowed mentions for this response. /// The request options for this response. - /// A to be sent with this response. + /// A to be sent with this response. /// A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored. /// - /// The sent message. + /// A task that represents an asynchronous send operation for delivering the message. The task result + /// contains the sent message. /// Task FollowupAsync(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, - AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null); + AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null); /// /// Sends a followup message for this interaction. @@ -76,13 +87,14 @@ namespace Discord /// if the response should be hidden to everyone besides the invoker of the command, otherwise . /// The allowed mentions for this response. /// The request options for this response. - /// A to be sent with this response. + /// A to be sent with this response. /// A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored. /// - /// The sent message. + /// A task that represents an asynchronous send operation for delivering the message. The task result + /// contains the sent message. /// public Task FollowupWithFileAsync(Stream fileStream, string fileName, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, - AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null); + AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null); /// /// Sends a followup message for this interaction. @@ -95,13 +107,50 @@ namespace Discord /// if the response should be hidden to everyone besides the invoker of the command, otherwise . /// The allowed mentions for this response. /// The request options for this response. - /// A to be sent with this response. + /// A to be sent with this response. + /// A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored. + /// + /// A task that represents an asynchronous send operation for delivering the message. The task result + /// contains the sent message. + /// + public 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); + /// + /// Sends a followup message for this interaction. + /// + /// The attachment containing the file and description. + /// The text of the message to be sent. + /// A array of embeds to send with this response. Max 10. + /// if the message should be read out by a text-to-speech reader, otherwise . + /// if the response should be hidden to everyone besides the invoker of the command, otherwise . + /// The allowed mentions for this response. + /// The request options for this response. + /// A to be sent with this response. + /// A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored. + /// + /// A task that represents an asynchronous send operation for delivering the message. The task result + /// contains the sent message. + /// + 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); + /// + /// Sends a followup message for this interaction. + /// + /// A collection of attachments to upload. + /// The text of the message to be sent. + /// A array of embeds to send with this response. Max 10. + /// if the message should be read out by a text-to-speech reader, otherwise . + /// if the response should be hidden to everyone besides the invoker of the command, otherwise . + /// The allowed mentions for this response. + /// The request options for this response. + /// A to be sent with this response. /// A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored. /// - /// The sent message. + /// A task that represents an asynchronous send operation for delivering the message. The task result + /// contains the sent message. /// - public Task FollowupWithFileAsync(string filePath, string text = null, string fileName = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, - AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null); + 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); /// /// Gets the original response for this interaction. @@ -115,14 +164,17 @@ namespace Discord /// /// A delegate containing the properties to modify the message with. /// The request options for this request. - /// A that represents the initial response. + /// + /// A task that represents an asynchronous modification operation. The task result + /// contains the updated message. + /// Task ModifyOriginalResponseAsync(Action func, RequestOptions options = null); /// /// Acknowledges this interaction. /// /// - /// A task that represents the asynchronous operation of acknowledging the interaction. + /// A task that represents the asynchronous operation of deferring the interaction. /// Task DeferAsync(bool ephemeral = false, RequestOptions options = null); } diff --git a/src/Discord.Net.Core/Entities/Interactions/SlashCommands/ISlashCommandInteraction.cs b/src/Discord.Net.Core/Entities/Interactions/SlashCommands/ISlashCommandInteraction.cs index 556182987..f28c35e40 100644 --- a/src/Discord.Net.Core/Entities/Interactions/SlashCommands/ISlashCommandInteraction.cs +++ b/src/Discord.Net.Core/Entities/Interactions/SlashCommands/ISlashCommandInteraction.cs @@ -3,7 +3,7 @@ namespace Discord /// /// Represents a slash command interaction. /// - public interface ISlashCommandInteraction : IDiscordInteraction + public interface ISlashCommandInteraction : IApplicationCommandInteraction { /// /// Gets the data associated with this interaction. diff --git a/src/Discord.Net.Core/Net/ApplicationCommandException.cs b/src/Discord.Net.Core/Net/ApplicationCommandException.cs deleted file mode 100644 index 4b4890d12..000000000 --- a/src/Discord.Net.Core/Net/ApplicationCommandException.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Linq; - -namespace Discord.Net -{ - [Obsolete("Please use HttpException instead of this. Will be removed in next major version.", false)] - public class ApplicationCommandException : HttpException - { - public ApplicationCommandException(HttpException httpError) - : base(httpError.HttpCode, httpError.Request, httpError.DiscordCode, httpError.Reason, httpError.Errors.ToArray()) - { - - } - } -} diff --git a/src/Discord.Net.Interactions/InteractionModuleBase.cs b/src/Discord.Net.Interactions/InteractionModuleBase.cs index c00f6b694..f0e50da8d 100644 --- a/src/Discord.Net.Interactions/InteractionModuleBase.cs +++ b/src/Discord.Net.Interactions/InteractionModuleBase.cs @@ -36,12 +36,12 @@ namespace Discord.Interactions /// protected virtual async Task RespondAsync (string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null) => - await Context.Interaction.RespondAsync(text, embeds, isTTS, ephemeral, allowedMentions, options, component, embed).ConfigureAwait(false); + await Context.Interaction.RespondAsync(text, embeds, isTTS, ephemeral, allowedMentions, component, embed, options).ConfigureAwait(false); /// protected virtual async Task FollowupAsync (string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null) => - await Context.Interaction.FollowupAsync(text, embeds, isTTS, ephemeral, allowedMentions, options, component, embed).ConfigureAwait(false); + await Context.Interaction.FollowupAsync(text, embeds, isTTS, ephemeral, allowedMentions, component, embed, options).ConfigureAwait(false); /// protected virtual async Task ReplyAsync (string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, diff --git a/src/Discord.Net.Interactions/RestInteractionModuleBase.cs b/src/Discord.Net.Interactions/RestInteractionModuleBase.cs index b086e14fb..ee1ce229e 100644 --- a/src/Discord.Net.Interactions/RestInteractionModuleBase.cs +++ b/src/Discord.Net.Interactions/RestInteractionModuleBase.cs @@ -53,7 +53,7 @@ namespace Discord.Interactions if (Context.Interaction is not RestInteraction restInteraction) throw new InvalidOperationException($"Invalid interaction type. Interaction must be a type of {nameof(RestInteraction)} in order to execute this method"); - await InteractionService._restResponseCallback(Context, restInteraction.Respond(text, embeds, isTTS, ephemeral, allowedMentions, options, component, embed)).ConfigureAwait(false); + await InteractionService._restResponseCallback(Context, restInteraction.Respond(text, embeds, isTTS, ephemeral, allowedMentions, component, embed, options)).ConfigureAwait(false); } } } diff --git a/src/Discord.Net.Rest/API/Rest/UploadWebhookFileParams.cs b/src/Discord.Net.Rest/API/Rest/UploadWebhookFileParams.cs index 3d09ad145..1a25e4782 100644 --- a/src/Discord.Net.Rest/API/Rest/UploadWebhookFileParams.cs +++ b/src/Discord.Net.Rest/API/Rest/UploadWebhookFileParams.cs @@ -21,6 +21,7 @@ namespace Discord.API.Rest public Optional Embeds { get; set; } public Optional AllowedMentions { get; set; } public Optional MessageComponents { get; set; } + public Optional Flags { get; set; } public UploadWebhookFileParams(params FileAttachment[] files) { @@ -48,6 +49,8 @@ namespace Discord.API.Rest payload["embeds"] = Embeds.Value; if (AllowedMentions.IsSpecified) payload["allowed_mentions"] = AllowedMentions.Value; + if (Flags.IsSpecified) + payload["flags"] = Flags.Value; List attachments = new(); diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs index abe059c64..4fec637bb 100644 --- a/src/Discord.Net.Rest/DiscordRestApiClient.cs +++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs @@ -1198,25 +1198,25 @@ namespace Discord.API options = RequestOptions.CreateOrClone(options); - return await TrySendApplicationCommandAsync(SendJsonAsync("POST", () => $"applications/{CurrentUserId}/commands", command, new BucketIds(), options: options)).ConfigureAwait(false); + return await SendJsonAsync("POST", () => $"applications/{CurrentUserId}/commands", command, new BucketIds(), options: options).ConfigureAwait(false); } public async Task ModifyGlobalApplicationCommandAsync(ModifyApplicationCommandParams command, ulong commandId, RequestOptions options = null) { options = RequestOptions.CreateOrClone(options); - return await TrySendApplicationCommandAsync(SendJsonAsync("PATCH", () => $"applications/{CurrentUserId}/commands/{commandId}", command, new BucketIds(), options: options)).ConfigureAwait(false); + return await SendJsonAsync("PATCH", () => $"applications/{CurrentUserId}/commands/{commandId}", command, new BucketIds(), options: options).ConfigureAwait(false); } public async Task ModifyGlobalApplicationUserCommandAsync(ModifyApplicationCommandParams command, ulong commandId, RequestOptions options = null) { options = RequestOptions.CreateOrClone(options); - return await TrySendApplicationCommandAsync(SendJsonAsync("PATCH", () => $"applications/{CurrentUserId}/commands/{commandId}", command, new BucketIds(), options: options)).ConfigureAwait(false); + return await SendJsonAsync("PATCH", () => $"applications/{CurrentUserId}/commands/{commandId}", command, new BucketIds(), options: options).ConfigureAwait(false); } public async Task ModifyGlobalApplicationMessageCommandAsync(ModifyApplicationCommandParams command, ulong commandId, RequestOptions options = null) { options = RequestOptions.CreateOrClone(options); - return await TrySendApplicationCommandAsync(SendJsonAsync("PATCH", () => $"applications/{CurrentUserId}/commands/{commandId}", command, new BucketIds(), options: options)).ConfigureAwait(false); + return await SendJsonAsync("PATCH", () => $"applications/{CurrentUserId}/commands/{commandId}", command, new BucketIds(), options: options).ConfigureAwait(false); } public async Task DeleteGlobalApplicationCommandAsync(ulong commandId, RequestOptions options = null) { @@ -1229,7 +1229,7 @@ namespace Discord.API { options = RequestOptions.CreateOrClone(options); - return await TrySendApplicationCommandAsync(SendJsonAsync("PUT", () => $"applications/{CurrentUserId}/commands", commands, new BucketIds(), options: options)).ConfigureAwait(false); + return await SendJsonAsync("PUT", () => $"applications/{CurrentUserId}/commands", commands, new BucketIds(), options: options).ConfigureAwait(false); } public async Task GetGuildApplicationCommandsAsync(ulong guildId, RequestOptions options = null) @@ -1271,7 +1271,7 @@ namespace Discord.API var bucket = new BucketIds(guildId: guildId); - return await TrySendApplicationCommandAsync(SendJsonAsync("POST", () => $"applications/{CurrentUserId}/guilds/{guildId}/commands", command, bucket, options: options)).ConfigureAwait(false); + return await SendJsonAsync("POST", () => $"applications/{CurrentUserId}/guilds/{guildId}/commands", command, bucket, options: options).ConfigureAwait(false); } public async Task ModifyGuildApplicationCommandAsync(ModifyApplicationCommandParams command, ulong guildId, ulong commandId, RequestOptions options = null) { @@ -1279,7 +1279,7 @@ namespace Discord.API var bucket = new BucketIds(guildId: guildId); - return await TrySendApplicationCommandAsync(SendJsonAsync("PATCH", () => $"applications/{CurrentUserId}/guilds/{guildId}/commands/{commandId}", command, bucket, options: options)).ConfigureAwait(false); + return await SendJsonAsync("PATCH", () => $"applications/{CurrentUserId}/guilds/{guildId}/commands/{commandId}", command, bucket, options: options).ConfigureAwait(false); } public async Task DeleteGuildApplicationCommandAsync(ulong guildId, ulong commandId, RequestOptions options = null) { @@ -1296,7 +1296,7 @@ namespace Discord.API var bucket = new BucketIds(guildId: guildId); - return await TrySendApplicationCommandAsync(SendJsonAsync("PUT", () => $"applications/{CurrentUserId}/guilds/{guildId}/commands", commands, bucket, options: options)).ConfigureAwait(false); + return await SendJsonAsync("PUT", () => $"applications/{CurrentUserId}/guilds/{guildId}/commands", commands, bucket, options: options).ConfigureAwait(false); } #endregion @@ -1316,7 +1316,7 @@ namespace Discord.API options = RequestOptions.CreateOrClone(options); - return await SendAsync("GET", () => $"webhooks/{CurrentUserId}/{interactionToken}/messages/@original", new BucketIds(), options: options).ConfigureAwait(false); + return await NullifyNotFound(SendAsync("GET", () => $"webhooks/{CurrentUserId}/{interactionToken}/messages/@original", new BucketIds(), options: options)).ConfigureAwait(false); } public async Task ModifyInteractionResponseAsync(ModifyInteractionResponseParams args, string interactionToken, RequestOptions options = null) { @@ -1347,6 +1347,21 @@ namespace Discord.API return await SendMultipartAsync("POST", () => $"webhooks/{CurrentUserId}/{token}?wait=true", args.ToDictionary(), new BucketIds(), options: options).ConfigureAwait(false); } + public async Task CreateInteractionFollowupMessageAsync(UploadWebhookFileParams args, string token, RequestOptions options = null) + { + if ((!args.Embeds.IsSpecified || args.Embeds.Value == null || args.Embeds.Value.Length == 0) && !args.Files.Any()) + Preconditions.NotNullOrEmpty(args.Content, nameof(args.Content)); + + + if (args.Content.IsSpecified && args.Content.Value.Length > DiscordConfig.MaxMessageSize) + throw new ArgumentException(message: $"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", paramName: nameof(args.Content)); + + options = RequestOptions.CreateOrClone(options); + + var ids = new BucketIds(); + return await SendMultipartAsync("POST", () => $"webhooks/{CurrentUserId}/{token}?wait=true", args.ToDictionary(), ids, clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false); + } + public async Task ModifyInteractionFollowupMessageAsync(ModifyInteractionResponseParams args, ulong id, string token, RequestOptions options = null) { Preconditions.NotNull(args, nameof(args)); @@ -2222,40 +2237,6 @@ namespace Discord.API return _serializer.Deserialize(reader); } - protected async Task TrySendApplicationCommandAsync(Task sendTask) - { - try - { - var result = await sendTask.ConfigureAwait(false); - - if (sendTask.Exception != null) - { - if (sendTask.Exception.InnerException is HttpException x) - { - if (x.HttpCode == HttpStatusCode.BadRequest) - { - var json = (x.Request as JsonRestRequest).Json; - throw new ApplicationCommandException(x); - } - } - - throw sendTask.Exception; - } - else - return result; - } - catch (HttpException x) - { - if (x.HttpCode == HttpStatusCode.BadRequest) - { - var json = (x.Request as JsonRestRequest).Json; - throw new ApplicationCommandException(x); - } - - throw; - } - } - protected async Task NullifyNotFound(Task sendTask) where T : class { try diff --git a/src/Discord.Net.Rest/Entities/Interactions/CommandBase/RestCommandBase.cs b/src/Discord.Net.Rest/Entities/Interactions/CommandBase/RestCommandBase.cs index a9efb6de1..1673fd922 100644 --- a/src/Discord.Net.Rest/Entities/Interactions/CommandBase/RestCommandBase.cs +++ b/src/Discord.Net.Rest/Entities/Interactions/CommandBase/RestCommandBase.cs @@ -76,9 +76,9 @@ namespace Discord.Rest bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, - RequestOptions options = null, MessageComponent component = null, - Embed embed = null) + Embed embed = null, + RequestOptions options = null) { if (!IsValidToken) throw new InvalidOperationException("Interaction token is no longer valid"); @@ -132,37 +132,29 @@ namespace Discord.Rest } } - lock (_lock) + try { - _hasResponded = true; + return SerializePayload(response); + } + finally + { + lock (_lock) + { + _hasResponded = true; + } } - - return SerializePayload(response); } - /// - /// Sends a followup message for this interaction. - /// - /// The text of the message to be sent. - /// A array of embeds to send with this response. Max 10. - /// if the message should be read out by a text-to-speech reader, otherwise . - /// if the response should be hidden to everyone besides the invoker of the command, otherwise . - /// The allowed mentions for this response. - /// The request options for this response. - /// A to be sent with this response. - /// A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored. - /// - /// The sent message. - /// + /// public override async Task FollowupAsync( string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, - RequestOptions options = null, MessageComponent component = null, - Embed embed = null) + Embed embed = null, + RequestOptions options = null) { if (!IsValidToken) throw new InvalidOperationException("Interaction token is no longer valid"); @@ -190,23 +182,8 @@ namespace Discord.Rest return await InteractionHelper.SendFollowupAsync(Discord, args, Token, Channel, options); } - /// - /// Sends a followup message for this interaction. - /// - /// The text of the message to be sent. - /// The file to upload. - /// The file name of the attachment. - /// A array of embeds to send with this response. Max 10. - /// if the message should be read out by a text-to-speech reader, otherwise . - /// if the response should be hidden to everyone besides the invoker of the command, otherwise . - /// The allowed mentions for this response. - /// The request options for this response. - /// A to be sent with this response. - /// A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored. - /// - /// The sent message. - /// - public override async Task FollowupWithFileAsync( + /// + public override Task FollowupWithFileAsync( Stream fileStream, string fileName, string text = null, @@ -214,9 +191,9 @@ namespace Discord.Rest bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, - RequestOptions options = null, - MessageComponent component = null, - Embed embed = null) + MessageComponent components = null, + Embed embed = null, + RequestOptions options = null) { if (!IsValidToken) throw new InvalidOperationException("Interaction token is no longer valid"); @@ -225,55 +202,59 @@ namespace Discord.Rest if (embed != null) embeds = new[] { embed }.Concat(embeds).ToArray(); - Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed."); - Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed."); - Preconditions.AtMost(embeds.Length, 10, nameof(embeds), "A max of 10 embeds are allowed."); Preconditions.NotNull(fileStream, nameof(fileStream), "File Stream must have data"); Preconditions.NotNullOrEmpty(fileName, nameof(fileName), "File Name must not be empty or null"); - var args = new API.Rest.CreateWebhookMessageParams - { - Content = text, - AllowedMentions = allowedMentions?.ToModel() ?? Optional.Unspecified, - IsTTS = isTTS, - Embeds = embeds.Select(x => x.ToModel()).ToArray(), - Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional.Unspecified, - File = fileStream is not null ? new MultipartFile(fileStream, fileName) : Optional.Unspecified - }; + return FollowupWithFileAsync(new FileAttachment(fileStream, fileName), text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options); + } - if (ephemeral) - args.Flags = MessageFlags.Ephemeral; + /// + public override 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) + { + Preconditions.NotNullOrEmpty(filePath, nameof(filePath), "Path must exist"); - return await InteractionHelper.SendFollowupAsync(Discord, args, Token, Channel, options); + fileName ??= Path.GetFileName(filePath); + Preconditions.NotNullOrEmpty(fileName, nameof(fileName), "File Name must not be empty or null"); + + return FollowupWithFileAsync(new FileAttachment(File.OpenRead(filePath), fileName), text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options); } - /// - /// Sends a followup message for this interaction. - /// - /// The text of the message to be sent. - /// The file to upload. - /// The file name of the attachment. - /// A array of embeds to send with this response. Max 10. - /// if the message should be read out by a text-to-speech reader, otherwise . - /// if the response should be hidden to everyone besides the invoker of the command, otherwise . - /// The allowed mentions for this response. - /// The request options for this response. - /// A to be sent with this response. - /// A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored. - /// - /// The sent message. - /// - public override async Task FollowupWithFileAsync( - string filePath, + /// + public override Task FollowupWithFileAsync( + FileAttachment attachment, string text = null, - string fileName = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, - RequestOptions options = null, - MessageComponent component = null, - Embed embed = null) + MessageComponent components = null, + Embed embed = null, + RequestOptions options = null) + { + return FollowupWithFilesAsync(new FileAttachment[] { attachment }, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options); + } + + /// + public override async 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) { if (!IsValidToken) throw new InvalidOperationException("Interaction token is no longer valid"); @@ -285,25 +266,35 @@ namespace Discord.Rest Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed."); Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed."); Preconditions.AtMost(embeds.Length, 10, nameof(embeds), "A max of 10 embeds are allowed."); - Preconditions.NotNullOrEmpty(filePath, nameof(filePath), "Path must exist"); - fileName ??= Path.GetFileName(filePath); - Preconditions.NotNullOrEmpty(fileName, nameof(fileName), "File Name must not be empty or null"); + foreach (var attachment in attachments) + { + Preconditions.NotNullOrEmpty(attachment.FileName, nameof(attachment.FileName), "File Name must not be empty or null"); + } - var args = new API.Rest.CreateWebhookMessageParams + // check that user flag and user Id list are exclusive, same with role flag and role Id list + if (allowedMentions != null && allowedMentions.AllowedTypes.HasValue) { - Content = text, - AllowedMentions = allowedMentions?.ToModel() ?? Optional.Unspecified, - IsTTS = isTTS, - Embeds = embeds.Select(x => x.ToModel()).ToArray(), - Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional.Unspecified, - File = !string.IsNullOrEmpty(filePath) ? new MultipartFile(new MemoryStream(File.ReadAllBytes(filePath), false), fileName) : Optional.Unspecified - }; + if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Users) && + allowedMentions.UserIds != null && allowedMentions.UserIds.Count > 0) + { + throw new ArgumentException("The Users flag is mutually exclusive with the list of User Ids.", nameof(allowedMentions)); + } + + if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Roles) && + allowedMentions.RoleIds != null && allowedMentions.RoleIds.Count > 0) + { + throw new ArgumentException("The Roles flag is mutually exclusive with the list of Role Ids.", nameof(allowedMentions)); + } + } + + var flags = MessageFlags.None; if (ephemeral) - args.Flags = MessageFlags.Ephemeral; + flags |= MessageFlags.Ephemeral; - return await InteractionHelper.SendFollowupAsync(Discord, args, Token, Channel, options); + var args = new API.Rest.UploadWebhookFileParams(attachments.ToArray()) { Flags = flags, Content = text, IsTTS = isTTS, Embeds = embeds.Any() ? embeds.Select(x => x.ToModel()).ToArray() : Optional.Unspecified, AllowedMentions = allowedMentions?.ToModel() ?? Optional.Unspecified, MessageComponents = components?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional.Unspecified }; + return await InteractionHelper.SendFollowupAsync(Discord, args, Token, Channel, options).ConfigureAwait(false); } /// diff --git a/src/Discord.Net.Rest/Entities/Interactions/ContextMenuCommands/MessageCommands/RestMessageCommand.cs b/src/Discord.Net.Rest/Entities/Interactions/ContextMenuCommands/MessageCommands/RestMessageCommand.cs index 53055cac3..7a85d2e0a 100644 --- a/src/Discord.Net.Rest/Entities/Interactions/ContextMenuCommands/MessageCommands/RestMessageCommand.cs +++ b/src/Discord.Net.Rest/Entities/Interactions/ContextMenuCommands/MessageCommands/RestMessageCommand.cs @@ -41,5 +41,8 @@ namespace Discord.Rest //IMessageCommandInteraction /// IMessageCommandInteractionData IMessageCommandInteraction.Data => Data; + //IApplicationCommandInteraction + /// + IApplicationCommandInteractionData IApplicationCommandInteraction.Data => Data; } } diff --git a/src/Discord.Net.Rest/Entities/Interactions/ContextMenuCommands/UserCommands/RestUserCommand.cs b/src/Discord.Net.Rest/Entities/Interactions/ContextMenuCommands/UserCommands/RestUserCommand.cs index 58f1ed375..7f55fd61b 100644 --- a/src/Discord.Net.Rest/Entities/Interactions/ContextMenuCommands/UserCommands/RestUserCommand.cs +++ b/src/Discord.Net.Rest/Entities/Interactions/ContextMenuCommands/UserCommands/RestUserCommand.cs @@ -44,5 +44,9 @@ namespace Discord.Rest //IUserCommandInteractionData /// IUserCommandInteractionData IUserCommandInteraction.Data => Data; + + //IApplicationCommandInteraction + /// + IApplicationCommandInteractionData IApplicationCommandInteraction.Data => Data; } } diff --git a/src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs b/src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs index ed537a062..b20cfe2ed 100644 --- a/src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs +++ b/src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs @@ -34,17 +34,20 @@ namespace Discord.Rest return client.ApiClient.BulkOverwriteGlobalApplicationCommandsAsync(Array.Empty(), options); } - public static Task SendInteractionResponseAsync(BaseDiscordClient client, InteractionResponse response, - ulong interactionId, string interactionToken, RequestOptions options = null) + public static async Task SendInteractionResponseAsync(BaseDiscordClient client, InteractionResponse response, + IDiscordInteraction interaction, IMessageChannel channel = null, RequestOptions options = null) { - return client.ApiClient.CreateInteractionResponseAsync(response, interactionId, interactionToken, options); + await client.ApiClient.CreateInteractionResponseAsync(response, interaction.Id, interaction.Token, options).ConfigureAwait(false); + return RestInteractionMessage.Create(client, response, interaction, channel); } public static async Task GetOriginalResponseAsync(BaseDiscordClient client, IMessageChannel channel, IDiscordInteraction interaction, RequestOptions options = null) { var model = await client.ApiClient.GetInteractionResponseAsync(interaction.Token, options).ConfigureAwait(false); - return RestInteractionMessage.Create(client, model, interaction.Token, channel); + if(model != null) + return RestInteractionMessage.Create(client, model, interaction.Token, channel); + return null; } public static async Task SendFollowupAsync(BaseDiscordClient client, CreateWebhookMessageParams args, @@ -55,6 +58,15 @@ namespace Discord.Rest var entity = RestFollowupMessage.Create(client, model, token, channel); return entity; } + + public static async Task SendFollowupAsync(BaseDiscordClient client, UploadWebhookFileParams args, + string token, IMessageChannel channel, RequestOptions options = null) + { + var model = await client.ApiClient.CreateInteractionFollowupMessageAsync(args, token, options).ConfigureAwait(false); + + var entity = RestFollowupMessage.Create(client, model, token, channel); + return entity; + } #endregion #region Global commands diff --git a/src/Discord.Net.Rest/Entities/Interactions/MessageComponents/RestMessageComponent.cs b/src/Discord.Net.Rest/Entities/Interactions/MessageComponents/RestMessageComponent.cs index eb47e15aa..9660bf7b0 100644 --- a/src/Discord.Net.Rest/Entities/Interactions/MessageComponents/RestMessageComponent.cs +++ b/src/Discord.Net.Rest/Entities/Interactions/MessageComponents/RestMessageComponent.cs @@ -64,9 +64,9 @@ namespace Discord.Rest /// if the message should be read out by a text-to-speech reader, otherwise . /// if the response should be hidden to everyone besides the invoker of the command, otherwise . /// The allowed mentions for this response. - /// The request options for this response. /// A to be sent with this response. /// A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored. + /// The request options for this response. /// /// A string that contains json to write back to the incoming http request. /// @@ -76,9 +76,9 @@ namespace Discord.Rest bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, - RequestOptions options = null, MessageComponent component = null, - Embed embed = null) + Embed embed = null, + RequestOptions options = null) { if (!IsValidToken) throw new InvalidOperationException("Interaction token is no longer valid"); @@ -237,29 +237,16 @@ namespace Discord.Rest return SerializePayload(response); } - /// - /// Sends a followup message for this interaction. - /// - /// The text of the message to be sent. - /// A array of embeds to send with this response. Max 10. - /// if the message should be read out by a text-to-speech reader, otherwise . - /// if the response should be hidden to everyone besides the invoker of the command, otherwise . - /// The allowed mentions for this response. - /// The request options for this response. - /// A to be sent with this response. - /// A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored. - /// - /// The sent message. - /// + /// public override async Task FollowupAsync( string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, - RequestOptions options = null, MessageComponent component = null, - Embed embed = null) + Embed embed = null, + RequestOptions options = null) { if (!IsValidToken) throw new InvalidOperationException("Interaction token is no longer valid"); @@ -284,11 +271,11 @@ namespace Discord.Rest if (ephemeral) args.Flags = MessageFlags.Ephemeral; - return await InteractionHelper.SendFollowupAsync(Discord, args, Token, Message.Channel, options).ConfigureAwait(false); + return await InteractionHelper.SendFollowupAsync(Discord, args, Token, Channel, options); } /// - public override async Task FollowupWithFileAsync( + public override Task FollowupWithFileAsync( Stream fileStream, string fileName, string text = null, @@ -296,9 +283,9 @@ namespace Discord.Rest bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, - RequestOptions options = null, - MessageComponent component = null, - Embed embed = null) + MessageComponent components = null, + Embed embed = null, + RequestOptions options = null) { if (!IsValidToken) throw new InvalidOperationException("Interaction token is no longer valid"); @@ -307,40 +294,59 @@ namespace Discord.Rest if (embed != null) embeds = new[] { embed }.Concat(embeds).ToArray(); - Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed."); - Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed."); - Preconditions.AtMost(embeds.Length, 10, nameof(embeds), "A max of 10 embeds are allowed."); Preconditions.NotNull(fileStream, nameof(fileStream), "File Stream must have data"); - Preconditions.NotNullOrWhitespace(fileName, nameof(fileName), "File Name must not be empty or null"); + Preconditions.NotNullOrEmpty(fileName, nameof(fileName), "File Name must not be empty or null"); - var args = new API.Rest.CreateWebhookMessageParams - { - Content = text, - AllowedMentions = allowedMentions?.ToModel() ?? Optional.Unspecified, - IsTTS = isTTS, - Embeds = embeds.Select(x => x.ToModel()).ToArray(), - Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional.Unspecified, - File = fileStream is not null ? new MultipartFile(fileStream, fileName) : Optional.Unspecified - }; + return FollowupWithFileAsync(new FileAttachment(fileStream, fileName), text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options); + } - if (ephemeral) - args.Flags = MessageFlags.Ephemeral; + /// + public override 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) + { + Preconditions.NotNullOrEmpty(filePath, nameof(filePath), "Path must exist"); - return await InteractionHelper.SendFollowupAsync(Discord, args, Token, Message.Channel, options).ConfigureAwait(false); + fileName ??= Path.GetFileName(filePath); + Preconditions.NotNullOrEmpty(fileName, nameof(fileName), "File Name must not be empty or null"); + + return FollowupWithFileAsync(new FileAttachment(File.OpenRead(filePath), fileName), text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options); } /// - public override async Task FollowupWithFileAsync( - string filePath, + public override Task FollowupWithFileAsync( + FileAttachment attachment, string text = null, - string fileName = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, - RequestOptions options = null, - MessageComponent component = null, - Embed embed = null) + MessageComponent components = null, + Embed embed = null, + RequestOptions options = null) + { + return FollowupWithFilesAsync(new FileAttachment[] { attachment }, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options); + } + + /// + public override async 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) { if (!IsValidToken) throw new InvalidOperationException("Interaction token is no longer valid"); @@ -352,22 +358,35 @@ namespace Discord.Rest Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed."); Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed."); Preconditions.AtMost(embeds.Length, 10, nameof(embeds), "A max of 10 embeds are allowed."); - Preconditions.NotNullOrWhitespace(filePath, nameof(filePath), "Path must exist"); - var args = new API.Rest.CreateWebhookMessageParams + foreach (var attachment in attachments) { - Content = text, - AllowedMentions = allowedMentions?.ToModel() ?? Optional.Unspecified, - IsTTS = isTTS, - Embeds = embeds.Select(x => x.ToModel()).ToArray(), - Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional.Unspecified, - File = !string.IsNullOrEmpty(filePath) ? new MultipartFile(new MemoryStream(File.ReadAllBytes(filePath), false), fileName) : Optional.Unspecified - }; + Preconditions.NotNullOrEmpty(attachment.FileName, nameof(attachment.FileName), "File Name must not be empty or null"); + } + + // check that user flag and user Id list are exclusive, same with role flag and role Id list + if (allowedMentions != null && allowedMentions.AllowedTypes.HasValue) + { + if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Users) && + allowedMentions.UserIds != null && allowedMentions.UserIds.Count > 0) + { + throw new ArgumentException("The Users flag is mutually exclusive with the list of User Ids.", nameof(allowedMentions)); + } + + if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Roles) && + allowedMentions.RoleIds != null && allowedMentions.RoleIds.Count > 0) + { + throw new ArgumentException("The Roles flag is mutually exclusive with the list of Role Ids.", nameof(allowedMentions)); + } + } + + var flags = MessageFlags.None; if (ephemeral) - args.Flags = MessageFlags.Ephemeral; + flags |= MessageFlags.Ephemeral; - return await InteractionHelper.SendFollowupAsync(Discord, args, Token, Message.Channel, options).ConfigureAwait(false); + var args = new API.Rest.UploadWebhookFileParams(attachments.ToArray()) { Flags = flags, Content = text, IsTTS = isTTS, Embeds = embeds.Any() ? embeds.Select(x => x.ToModel()).ToArray() : Optional.Unspecified, AllowedMentions = allowedMentions?.ToModel() ?? Optional.Unspecified, MessageComponents = components?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional.Unspecified }; + return await InteractionHelper.SendFollowupAsync(Discord, args, Token, Channel, options).ConfigureAwait(false); } /// diff --git a/src/Discord.Net.Rest/Entities/Interactions/RestInteraction.cs b/src/Discord.Net.Rest/Entities/Interactions/RestInteraction.cs index 37eb2a849..5c3219fa7 100644 --- a/src/Discord.Net.Rest/Entities/Interactions/RestInteraction.cs +++ b/src/Discord.Net.Rest/Entities/Interactions/RestInteraction.cs @@ -139,8 +139,7 @@ namespace Discord.Rest /// public abstract string Defer(bool ephemeral = false, RequestOptions options = null); - /// - public abstract Task FollowupAsync(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null); + /// /// Gets the original response for this interaction. /// @@ -154,14 +153,36 @@ namespace Discord.Rest /// /// A delegate containing the properties to modify the message with. /// The request options for this request. - /// A that represents the initial response. + /// + /// A task that represents an asynchronous send operation for delivering the message. The task result + /// contains the sent message. + /// public async Task ModifyOriginalResponseAsync(Action func, RequestOptions options = null) { var model = await InteractionHelper.ModifyInteractionResponseAsync(Discord, Token, func, options); return RestInteractionMessage.Create(Discord, model, Token, Channel); } /// - public abstract string Respond(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null); + public abstract string Respond(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent component = null, Embed embed = null, RequestOptions options = null); + + /// + /// Sends a followup message for this interaction. + /// + /// The text of the message to be sent. + /// A array of embeds to send with this response. Max 10. + /// if the message should be read out by a text-to-speech reader, otherwise . + /// if the response should be hidden to everyone besides the invoker of the command, otherwise . + /// The allowed mentions for this response. + /// A to be sent with this response. + /// A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored. + /// The request options for this response. + /// + /// A task that represents an asynchronous send operation for delivering the message. The task result + /// contains the sent message. + /// + public abstract Task FollowupAsync(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, + AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null); + /// /// Sends a followup message for this interaction. /// @@ -172,14 +193,16 @@ namespace Discord.Rest /// if the message should be read out by a text-to-speech reader, otherwise . /// if the response should be hidden to everyone besides the invoker of the command, otherwise . /// The allowed mentions for this response. - /// The request options for this response. - /// A to be sent with this response. + /// A to be sent with this response. /// A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored. + /// The request options for this response. /// - /// The sent message. + /// A task that represents an asynchronous send operation for delivering the message. The task result + /// contains the sent message. /// public abstract Task FollowupWithFileAsync(Stream fileStream, string fileName, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, - AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null); + AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null); + /// /// Sends a followup message for this interaction. /// @@ -190,45 +213,90 @@ namespace Discord.Rest /// if the message should be read out by a text-to-speech reader, otherwise . /// if the response should be hidden to everyone besides the invoker of the command, otherwise . /// The allowed mentions for this response. + /// A to be sent with this response. + /// A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored. /// The request options for this response. - /// A to be sent with this response. + /// + /// A task that represents an asynchronous send operation for delivering the message. The task result + /// contains the sent message. + /// + public abstract 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); + + /// + /// Sends a followup message for this interaction. + /// + /// The attachment containing the file and description. + /// The text of the message to be sent. + /// A array of embeds to send with this response. Max 10. + /// if the message should be read out by a text-to-speech reader, otherwise . + /// if the response should be hidden to everyone besides the invoker of the command, otherwise . + /// The allowed mentions for this response. + /// The request options for this response. + /// A to be sent with this response. /// A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored. /// - /// The sent message. + /// A task that represents an asynchronous send operation for delivering the message. The task result + /// contains the sent message. /// - public abstract Task FollowupWithFileAsync(string filePath, string text = null, string fileName = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, - AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null); + public abstract 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); + + /// + /// Sends a followup message for this interaction. + /// + /// A collection of attachments to upload. + /// The text of the message to be sent. + /// A array of embeds to send with this response. Max 10. + /// if the message should be read out by a text-to-speech reader, otherwise . + /// if the response should be hidden to everyone besides the invoker of the command, otherwise . + /// The allowed mentions for this response. + /// The request options for this response. + /// A to be sent with this response. + /// A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored. + /// + /// A task that represents an asynchronous send operation for delivering the message. The task result + /// contains the sent message. + /// + public abstract 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); #region IDiscordInteraction /// - Task IDiscordInteraction.RespondAsync(string text, Embed[] embeds, bool isTTS, bool ephemeral, AllowedMentions allowedMentions, RequestOptions options, MessageComponent component, Embed embed) - => Task.FromResult(Respond(text, embeds, isTTS, ephemeral, allowedMentions, options, component, embed)); + IUser IDiscordInteraction.User => User; + /// + Task IDiscordInteraction.RespondAsync(string text, Embed[] embeds, bool isTTS, bool ephemeral, AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options) + { + return Task.FromResult(null); + } + /// Task IDiscordInteraction.DeferAsync(bool ephemeral, RequestOptions options) => Task.FromResult(Defer(ephemeral, options)); - /// async Task IDiscordInteraction.FollowupAsync(string text, Embed[] embeds, bool isTTS, bool ephemeral, AllowedMentions allowedMentions, - RequestOptions options, MessageComponent component, Embed embed) - => await FollowupAsync(text, embeds, isTTS, ephemeral, allowedMentions, options, component, embed).ConfigureAwait(false); - + MessageComponent component, Embed embed, RequestOptions options) + => await FollowupAsync(text, embeds, isTTS, ephemeral, allowedMentions, component, embed, options).ConfigureAwait(false); /// async Task IDiscordInteraction.GetOriginalResponseAsync(RequestOptions options) => await GetOriginalResponseAsync(options).ConfigureAwait(false); - /// async Task IDiscordInteraction.ModifyOriginalResponseAsync(Action func, RequestOptions options) => await ModifyOriginalResponseAsync(func, options).ConfigureAwait(false); - /// - async Task IDiscordInteraction.FollowupWithFileAsync(Stream fileStream, string fileName, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, - AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null) - => await FollowupWithFileAsync(fileStream, fileName, text, embeds, isTTS, ephemeral, allowedMentions, options, component, embed).ConfigureAwait(false); - + async Task IDiscordInteraction.FollowupWithFileAsync(Stream fileStream, string fileName, string text, Embed[] embeds, bool isTTS, bool ephemeral, + AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options) + => await FollowupWithFileAsync(fileStream, fileName, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options).ConfigureAwait(false); + /// + async Task IDiscordInteraction.FollowupWithFileAsync(string filePath, string text, string fileName, Embed[] embeds, bool isTTS, bool ephemeral, + AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options) + => await FollowupWithFileAsync(filePath, text, fileName, embeds, isTTS, ephemeral, allowedMentions, components, embed, options).ConfigureAwait(false); + /// + async Task IDiscordInteraction.FollowupWithFileAsync(FileAttachment attachment, string text, Embed[] embeds, bool isTTS, bool ephemeral, AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options) + => await FollowupWithFileAsync(attachment, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options).ConfigureAwait(false); /// - async Task IDiscordInteraction.FollowupWithFileAsync(string filePath, string text = null, string fileName = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, - AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null) - => await FollowupWithFileAsync(filePath, text, fileName, embeds, isTTS, ephemeral, allowedMentions, options, component, embed).ConfigureAwait(false); + async Task IDiscordInteraction.FollowupWithFilesAsync(IEnumerable attachments, string text, Embed[] embeds, bool isTTS, bool ephemeral, AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options) + => await FollowupWithFilesAsync(attachments, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options).ConfigureAwait(false); #endregion } } diff --git a/src/Discord.Net.Rest/Entities/Interactions/RestPingInteraction.cs b/src/Discord.Net.Rest/Entities/Interactions/RestPingInteraction.cs index f979a4df2..5ed5e054a 100644 --- a/src/Discord.Net.Rest/Entities/Interactions/RestPingInteraction.cs +++ b/src/Discord.Net.Rest/Entities/Interactions/RestPingInteraction.cs @@ -38,9 +38,11 @@ namespace Discord.Rest } public override string Defer(bool ephemeral = false, RequestOptions options = null) => throw new NotSupportedException(); - public override Task FollowupAsync(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null) => throw new NotSupportedException(); - public override Task FollowupWithFileAsync(Stream fileStream, string fileName, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null) => throw new NotSupportedException(); - public override Task FollowupWithFileAsync(string filePath, string text = null, string fileName = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null) => throw new NotSupportedException(); - public override string Respond(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null) => throw new NotSupportedException(); + public override string Respond(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent component = null, Embed embed = null, RequestOptions options = null) => throw new NotSupportedException(); + public override Task FollowupAsync(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null) => throw new NotSupportedException(); + public override 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) => throw new NotSupportedException(); + public override 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) => throw new NotSupportedException(); + public override 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) => throw new NotSupportedException(); + public override 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) => throw new NotSupportedException(); } } diff --git a/src/Discord.Net.Rest/Entities/Interactions/SlashCommands/RestAutocompleteInteraction.cs b/src/Discord.Net.Rest/Entities/Interactions/SlashCommands/RestAutocompleteInteraction.cs index 8737dc5ac..1abeb4b3f 100644 --- a/src/Discord.Net.Rest/Entities/Interactions/SlashCommands/RestAutocompleteInteraction.cs +++ b/src/Discord.Net.Rest/Entities/Interactions/SlashCommands/RestAutocompleteInteraction.cs @@ -102,31 +102,21 @@ namespace Discord.Rest /// public string Respond(RequestOptions options = null, params AutocompleteResult[] result) => Respond(result, options); - - /// - [Obsolete("Autocomplete interactions cannot be deferred!", true)] public override string Defer(bool ephemeral = false, RequestOptions options = null) - => throw new NotSupportedException("Autocomplete interactions cannot be deferred!"); + => throw new NotSupportedException("Autocomplete interactions don't support this method!"); + public override string Respond(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent component = null, Embed embed = null, RequestOptions options = null) + => throw new NotSupportedException("Autocomplete interactions don't support this method!"); + public override Task FollowupAsync(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null) + => throw new NotSupportedException("Autocomplete interactions don't support this method!"); + public override 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) + => throw new NotSupportedException("Autocomplete interactions don't support this method!"); + public override 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) + => throw new NotSupportedException("Autocomplete interactions don't support this method!"); + public override 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) + => throw new NotSupportedException("Autocomplete interactions don't support this method!"); + public override 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) + => throw new NotSupportedException("Autocomplete interactions don't support this method!"); - /// - [Obsolete("Autocomplete interactions cannot have followups!", true)] - public override Task FollowupAsync(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null) - => throw new NotSupportedException("Autocomplete interactions cannot be deferred!"); - - /// - [Obsolete("Autocomplete interactions cannot have followups!", true)] - public override Task FollowupWithFileAsync(Stream fileStream, string fileName, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null) - => throw new NotSupportedException("Autocomplete interactions cannot be deferred!"); - - /// - [Obsolete("Autocomplete interactions cannot have followups!", true)] - public override Task FollowupWithFileAsync(string filePath, string text = null, string fileName = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null) - => throw new NotSupportedException("Autocomplete interactions cannot be deferred!"); - - /// - [Obsolete("Autocomplete interactions cannot have normal responses!", true)] - public override string Respond(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null) - => throw new NotSupportedException("Autocomplete interactions cannot be deferred!"); //IAutocompleteInteraction /// diff --git a/src/Discord.Net.Rest/Entities/Interactions/SlashCommands/RestSlashCommand.cs b/src/Discord.Net.Rest/Entities/Interactions/SlashCommands/RestSlashCommand.cs index 785e39a12..21184fcf6 100644 --- a/src/Discord.Net.Rest/Entities/Interactions/SlashCommands/RestSlashCommand.cs +++ b/src/Discord.Net.Rest/Entities/Interactions/SlashCommands/RestSlashCommand.cs @@ -44,5 +44,9 @@ namespace Discord.Rest //ISlashCommandInteraction /// IApplicationCommandInteractionData ISlashCommandInteraction.Data => Data; + + //IApplicationCommandInteraction + /// + IApplicationCommandInteractionData IApplicationCommandInteraction.Data => Data; } } diff --git a/src/Discord.Net.Rest/Entities/Messages/RestFollowupMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestFollowupMessage.cs index 693d36e56..aa5dd5aeb 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestFollowupMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestFollowupMessage.cs @@ -5,7 +5,7 @@ using Model = Discord.API.Message; namespace Discord.Rest { /// - /// Represents a REST-based follow up message sent by a bot responding to a slash command. + /// Represents a REST-based follow up message sent by a bot responding to an interaction. /// public class RestFollowupMessage : RestUserMessage { diff --git a/src/Discord.Net.Rest/Entities/Messages/RestInteractionMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestInteractionMessage.cs index 26beb03b6..815f1953f 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestInteractionMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestInteractionMessage.cs @@ -1,15 +1,16 @@ using System; using System.Threading.Tasks; -using Model = Discord.API.Message; +using MessageModel = Discord.API.Message; +using Model = Discord.API.InteractionResponse; namespace Discord.Rest { /// - /// Represents the initial REST-based response to a slash command. + /// Represents the initial REST-based response to an interaction. /// public class RestInteractionMessage : RestUserMessage { - // Token used to delete/modify this followup message + public InteractionResponseType ResponseType { get; private set; } internal string Token { get; } internal RestInteractionMessage(BaseDiscordClient discord, ulong id, IUser author, string token, IMessageChannel channel) @@ -18,18 +19,31 @@ namespace Discord.Rest Token = token; } - internal static RestInteractionMessage Create(BaseDiscordClient discord, Model model, string token, IMessageChannel channel) + internal static RestInteractionMessage Create(BaseDiscordClient discord, MessageModel model, string token, IMessageChannel channel) { var entity = new RestInteractionMessage(discord, model.Id, model.Author.IsSpecified ? RestUser.Create(discord, model.Author.Value) : discord.CurrentUser, token, channel); entity.Update(model); return entity; } - internal new void Update(Model model) + internal static RestInteractionMessage Create(BaseDiscordClient discord, Model model, IDiscordInteraction interaction, IMessageChannel channel) + { + var entity = new RestInteractionMessage(discord, interaction.Id, discord.CurrentUser, interaction.Token, channel); + entity.Update(model, interaction); + return entity; + } + + internal new void Update(MessageModel model) { base.Update(model); } + internal void Update(Model model, IDiscordInteraction interaction) + { + ResponseType = model.Type; + base.Update(model.ToMessage(interaction)); + } + /// /// Deletes this object and all of it's children. /// diff --git a/src/Discord.Net.Rest/Extensions/EntityExtensions.cs b/src/Discord.Net.Rest/Extensions/EntityExtensions.cs index 05bd04e64..bca2e8715 100644 --- a/src/Discord.Net.Rest/Extensions/EntityExtensions.cs +++ b/src/Discord.Net.Rest/Extensions/EntityExtensions.cs @@ -170,5 +170,48 @@ namespace Discord.Rest { return new Overwrite(model.TargetId, model.TargetType, new OverwritePermissions(model.Allow, model.Deny)); } + + public static API.Message ToMessage(this API.InteractionResponse model, IDiscordInteraction interaction) + { + if (model.Data.IsSpecified) + { + var data = model.Data.Value; + var messageModel = new API.Message + { + IsTextToSpeech = data.TTS, + Content = data.Content, + Embeds = data.Embeds, + AllowedMentions = data.AllowedMentions, + Components = data.Components, + Flags = data.Flags, + }; + + if(interaction is IApplicationCommandInteraction command) + { + messageModel.Interaction = new API.MessageInteraction + { + Id = command.Id, + Name = command.Data.Name, + Type = InteractionType.ApplicationCommand, + User = new API.User + { + Username = command.User.Username, + Avatar = command.User.AvatarId, + Bot = command.User.IsBot, + Discriminator = command.User.Discriminator, + PublicFlags = command.User.PublicFlags.HasValue ? command.User.PublicFlags.Value : Optional.Unspecified, + Id = command.User.Id, + } + }; + } + + return messageModel; + } + + return new API.Message + { + Id = interaction.Id, + }; + } } } diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/ContextMenuCommands/MessageCommands/SocketMessageCommand.cs b/src/Discord.Net.WebSocket/Entities/Interaction/ContextMenuCommands/MessageCommands/SocketMessageCommand.cs index 0aa061439..fee33f8cb 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/ContextMenuCommands/MessageCommands/SocketMessageCommand.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/ContextMenuCommands/MessageCommands/SocketMessageCommand.cs @@ -41,5 +41,9 @@ namespace Discord.WebSocket //IDiscordInteraction /// IDiscordInteractionData IDiscordInteraction.Data => Data; + + //IApplicationCommandInteraction + /// + IApplicationCommandInteractionData IApplicationCommandInteraction.Data => Data; } } diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/ContextMenuCommands/UserCommands/SocketUserCommand.cs b/src/Discord.Net.WebSocket/Entities/Interaction/ContextMenuCommands/UserCommands/SocketUserCommand.cs index 40ee5b537..75e8ebff9 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/ContextMenuCommands/UserCommands/SocketUserCommand.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/ContextMenuCommands/UserCommands/SocketUserCommand.cs @@ -41,5 +41,9 @@ namespace Discord.WebSocket //IDiscordInteraction /// IDiscordInteractionData IDiscordInteraction.Data => Data; + + //IApplicationCommandInteraction + /// + IApplicationCommandInteractionData IApplicationCommandInteraction.Data => Data; } } diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/MessageComponents/SocketMessageComponent.cs b/src/Discord.Net.WebSocket/Entities/Interaction/MessageComponents/SocketMessageComponent.cs index 928a4302a..d5d5f959d 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/MessageComponents/SocketMessageComponent.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/MessageComponents/SocketMessageComponent.cs @@ -72,15 +72,15 @@ namespace Discord.WebSocket } } /// - public override async Task RespondAsync( + public override async Task RespondAsync( string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, - RequestOptions options = null, MessageComponent component = null, - Embed embed = null) + Embed embed = null, + RequestOptions options = null) { if (!IsValidToken) throw new InvalidOperationException("Interaction token is no longer valid"); @@ -136,11 +136,16 @@ namespace Discord.WebSocket } } - await InteractionHelper.SendInteractionResponseAsync(Discord, response, Id, Token, options).ConfigureAwait(false); - - lock (_lock) + try { - HasResponded = true; + return await InteractionHelper.SendInteractionResponseAsync(Discord, response, this, Channel, options).ConfigureAwait(false); + } + finally + { + lock (_lock) + { + HasResponded = true; + } } } @@ -231,7 +236,7 @@ namespace Discord.WebSocket } } - await InteractionHelper.SendInteractionResponseAsync(Discord, response, Id, Token, options).ConfigureAwait(false); + await InteractionHelper.SendInteractionResponseAsync(Discord, response, this, Channel, options).ConfigureAwait(false); lock (_lock) { @@ -246,9 +251,9 @@ namespace Discord.WebSocket bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, - RequestOptions options = null, MessageComponent component = null, - Embed embed = null) + Embed embed = null, + RequestOptions options = null) { if (!IsValidToken) throw new InvalidOperationException("Interaction token is no longer valid"); @@ -273,11 +278,11 @@ namespace Discord.WebSocket if (ephemeral) args.Flags = MessageFlags.Ephemeral; - return await InteractionHelper.SendFollowupAsync(Discord.Rest, args, Token, Channel, options).ConfigureAwait(false); + return await InteractionHelper.SendFollowupAsync(Discord.Rest, args, Token, Channel, options); } /// - public override async Task FollowupWithFileAsync( + public override Task FollowupWithFileAsync( Stream fileStream, string fileName, string text = null, @@ -285,9 +290,9 @@ namespace Discord.WebSocket bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, - RequestOptions options = null, - MessageComponent component = null, - Embed embed = null) + MessageComponent components = null, + Embed embed = null, + RequestOptions options = null) { if (!IsValidToken) throw new InvalidOperationException("Interaction token is no longer valid"); @@ -296,40 +301,59 @@ namespace Discord.WebSocket if (embed != null) embeds = new[] { embed }.Concat(embeds).ToArray(); - Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed."); - Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed."); - Preconditions.AtMost(embeds.Length, 10, nameof(embeds), "A max of 10 embeds are allowed."); Preconditions.NotNull(fileStream, nameof(fileStream), "File Stream must have data"); - Preconditions.NotNullOrWhitespace(fileName, nameof(fileName), "File Name must not be empty or null"); + Preconditions.NotNullOrEmpty(fileName, nameof(fileName), "File Name must not be empty or null"); - var args = new API.Rest.CreateWebhookMessageParams - { - Content = text, - AllowedMentions = allowedMentions?.ToModel() ?? Optional.Unspecified, - IsTTS = isTTS, - Embeds = embeds.Select(x => x.ToModel()).ToArray(), - Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional.Unspecified, - File = fileStream is not null ? new MultipartFile(fileStream, fileName) : Optional.Unspecified - }; + return FollowupWithFileAsync(new FileAttachment(fileStream, fileName), text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options); + } - if (ephemeral) - args.Flags = MessageFlags.Ephemeral; + /// + public override 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) + { + Preconditions.NotNullOrEmpty(filePath, nameof(filePath), "Path must exist"); - return await InteractionHelper.SendFollowupAsync(Discord.Rest, args, Token, Channel, options).ConfigureAwait(false); + fileName ??= Path.GetFileName(filePath); + Preconditions.NotNullOrEmpty(fileName, nameof(fileName), "File Name must not be empty or null"); + + return FollowupWithFileAsync(new FileAttachment(File.OpenRead(filePath), fileName), text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options); } /// - public override async Task FollowupWithFileAsync( - string filePath, + public override Task FollowupWithFileAsync( + FileAttachment attachment, string text = null, - string fileName = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, - RequestOptions options = null, - MessageComponent component = null, - Embed embed = null) + MessageComponent components = null, + Embed embed = null, + RequestOptions options = null) + { + return FollowupWithFilesAsync(new FileAttachment[] { attachment }, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options); + } + + /// + public override async 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) { if (!IsValidToken) throw new InvalidOperationException("Interaction token is no longer valid"); @@ -341,22 +365,35 @@ namespace Discord.WebSocket Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed."); Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed."); Preconditions.AtMost(embeds.Length, 10, nameof(embeds), "A max of 10 embeds are allowed."); - Preconditions.NotNullOrWhitespace(filePath, nameof(filePath), "Path must exist"); - var args = new API.Rest.CreateWebhookMessageParams + foreach (var attachment in attachments) { - Content = text, - AllowedMentions = allowedMentions?.ToModel() ?? Optional.Unspecified, - IsTTS = isTTS, - Embeds = embeds.Select(x => x.ToModel()).ToArray(), - Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional.Unspecified, - File = !string.IsNullOrEmpty(filePath) ? new MultipartFile(new MemoryStream(File.ReadAllBytes(filePath), false), fileName) : Optional.Unspecified - }; + Preconditions.NotNullOrEmpty(attachment.FileName, nameof(attachment.FileName), "File Name must not be empty or null"); + } + + // check that user flag and user Id list are exclusive, same with role flag and role Id list + if (allowedMentions != null && allowedMentions.AllowedTypes.HasValue) + { + if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Users) && + allowedMentions.UserIds != null && allowedMentions.UserIds.Count > 0) + { + throw new ArgumentException("The Users flag is mutually exclusive with the list of User Ids.", nameof(allowedMentions)); + } + + if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Roles) && + allowedMentions.RoleIds != null && allowedMentions.RoleIds.Count > 0) + { + throw new ArgumentException("The Roles flag is mutually exclusive with the list of Role Ids.", nameof(allowedMentions)); + } + } + + var flags = MessageFlags.None; if (ephemeral) - args.Flags = MessageFlags.Ephemeral; + flags |= MessageFlags.Ephemeral; - return await InteractionHelper.SendFollowupAsync(Discord.Rest, args, Token, Channel, options).ConfigureAwait(false); + var args = new API.Rest.UploadWebhookFileParams(attachments.ToArray()) { Flags = flags, Content = text, IsTTS = isTTS, Embeds = embeds.Any() ? embeds.Select(x => x.ToModel()).ToArray() : Optional.Unspecified, AllowedMentions = allowedMentions?.ToModel() ?? Optional.Unspecified, MessageComponents = components?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional.Unspecified }; + return await InteractionHelper.SendFollowupAsync(Discord, args, Token, Channel, options).ConfigureAwait(false); } /// diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SlashCommands/SocketAutocompleteInteraction.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SlashCommands/SocketAutocompleteInteraction.cs index 0f598586b..955d7d53f 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/SlashCommands/SocketAutocompleteInteraction.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/SlashCommands/SocketAutocompleteInteraction.cs @@ -89,31 +89,20 @@ namespace Discord.WebSocket /// public Task RespondAsync(RequestOptions options = null, params AutocompleteResult[] result) => RespondAsync(result, options); - - /// - [Obsolete("Autocomplete interactions cannot be deferred!", true)] + public override Task RespondAsync(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null) + => throw new NotSupportedException("Autocomplete interactions don't support this method!"); + public override Task FollowupAsync(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null) + => throw new NotSupportedException("Autocomplete interactions don't support this method!"); + public override 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) + => throw new NotSupportedException("Autocomplete interactions don't support this method!"); + public override 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) + => throw new NotSupportedException("Autocomplete interactions don't support this method!"); + public override 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) + => throw new NotSupportedException("Autocomplete interactions don't support this method!"); + public override 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) + => throw new NotSupportedException("Autocomplete interactions don't support this method!"); public override Task DeferAsync(bool ephemeral = false, RequestOptions options = null) - => throw new NotSupportedException("Autocomplete interactions cannot be deferred!"); - - /// - [Obsolete("Autocomplete interactions cannot have followups!", true)] - public override Task FollowupAsync(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null) - => throw new NotSupportedException("Autocomplete interactions cannot be deferred!"); - - /// - [Obsolete("Autocomplete interactions cannot have followups!", true)] - public override Task FollowupWithFileAsync(Stream fileStream, string fileName, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null) - => throw new NotSupportedException("Autocomplete interactions cannot be deferred!"); - - /// - [Obsolete("Autocomplete interactions cannot have followups!", true)] - public override Task FollowupWithFileAsync(string filePath, string text = null, string fileName = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null) - => throw new NotSupportedException("Autocomplete interactions cannot be deferred!"); - - /// - [Obsolete("Autocomplete interactions cannot have normal responses!", true)] - public override Task RespondAsync(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null) - => throw new NotSupportedException("Autocomplete interactions cannot be deferred!"); + => throw new NotSupportedException("Autocomplete interactions don't support this method!"); //IAutocompleteInteraction /// diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SlashCommands/SocketSlashCommand.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SlashCommands/SocketSlashCommand.cs index 5343bb225..5934a3864 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/SlashCommands/SocketSlashCommand.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/SlashCommands/SocketSlashCommand.cs @@ -41,5 +41,9 @@ namespace Discord.WebSocket //IDiscordInteraction /// IDiscordInteractionData IDiscordInteraction.Data => Data; + + //IApplicationCommandInteraction + /// + IApplicationCommandInteractionData IApplicationCommandInteraction.Data => Data; } } diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBase.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBase.cs index 92303d488..80c4a93be 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBase.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBase.cs @@ -1,6 +1,7 @@ using Discord.Net.Rest; using Discord.Rest; using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; @@ -68,15 +69,15 @@ namespace Discord.WebSocket } /// - public override async Task RespondAsync( + public override async Task RespondAsync( string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, - RequestOptions options = null, MessageComponent component = null, - Embed embed = null) + Embed embed = null, + RequestOptions options = null) { if (!IsValidToken) throw new InvalidOperationException("Interaction token is no longer valid"); @@ -130,11 +131,16 @@ namespace Discord.WebSocket } } - await InteractionHelper.SendInteractionResponseAsync(Discord, response, Id, Token, options).ConfigureAwait(false); - - lock (_lock) + try { - HasResponded = true; + return await InteractionHelper.SendInteractionResponseAsync(Discord, response, this, Channel, options).ConfigureAwait(false); + } + finally + { + lock (_lock) + { + HasResponded = true; + } } } @@ -145,9 +151,9 @@ namespace Discord.WebSocket bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, - RequestOptions options = null, MessageComponent component = null, - Embed embed = null) + Embed embed = null, + RequestOptions options = null) { if (!IsValidToken) throw new InvalidOperationException("Interaction token is no longer valid"); @@ -176,7 +182,7 @@ namespace Discord.WebSocket } /// - public override async Task FollowupWithFileAsync( + public override Task FollowupWithFileAsync( Stream fileStream, string fileName, string text = null, @@ -184,9 +190,9 @@ namespace Discord.WebSocket bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, - RequestOptions options = null, - MessageComponent component = null, - Embed embed = null) + MessageComponent components = null, + Embed embed = null, + RequestOptions options = null) { if (!IsValidToken) throw new InvalidOperationException("Interaction token is no longer valid"); @@ -195,40 +201,59 @@ namespace Discord.WebSocket if (embed != null) embeds = new[] { embed }.Concat(embeds).ToArray(); - Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed."); - Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed."); - Preconditions.AtMost(embeds.Length, 10, nameof(embeds), "A max of 10 embeds are allowed."); Preconditions.NotNull(fileStream, nameof(fileStream), "File Stream must have data"); Preconditions.NotNullOrEmpty(fileName, nameof(fileName), "File Name must not be empty or null"); - var args = new API.Rest.CreateWebhookMessageParams - { - Content = text, - AllowedMentions = allowedMentions?.ToModel() ?? Optional.Unspecified, - IsTTS = isTTS, - Embeds = embeds.Select(x => x.ToModel()).ToArray(), - Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional.Unspecified, - File = fileStream is not null ? new MultipartFile(fileStream, fileName) : Optional.Unspecified - }; + return FollowupWithFileAsync(new FileAttachment(fileStream, fileName), text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options); + } - if (ephemeral) - args.Flags = MessageFlags.Ephemeral; + /// + public override 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) + { + Preconditions.NotNullOrEmpty(filePath, nameof(filePath), "Path must exist"); - return await InteractionHelper.SendFollowupAsync(Discord.Rest, args, Token, Channel, options); + fileName ??= Path.GetFileName(filePath); + Preconditions.NotNullOrEmpty(fileName, nameof(fileName), "File Name must not be empty or null"); + + return FollowupWithFileAsync(new FileAttachment(File.OpenRead(filePath), fileName), text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options); } /// - public override async Task FollowupWithFileAsync( - string filePath, + public override Task FollowupWithFileAsync( + FileAttachment attachment, string text = null, - string fileName = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, - RequestOptions options = null, - MessageComponent component = null, - Embed embed = null) + MessageComponent components = null, + Embed embed = null, + RequestOptions options = null) + { + return FollowupWithFilesAsync(new FileAttachment[] { attachment }, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options); + } + + /// + public override async 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) { if (!IsValidToken) throw new InvalidOperationException("Interaction token is no longer valid"); @@ -240,25 +265,35 @@ namespace Discord.WebSocket Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed."); Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed."); Preconditions.AtMost(embeds.Length, 10, nameof(embeds), "A max of 10 embeds are allowed."); - Preconditions.NotNullOrEmpty(filePath, nameof(filePath), "Path must exist"); - fileName ??= Path.GetFileName(filePath); - Preconditions.NotNullOrEmpty(fileName, nameof(fileName), "File Name must not be empty or null"); + foreach (var attachment in attachments) + { + Preconditions.NotNullOrEmpty(attachment.FileName, nameof(attachment.FileName), "File Name must not be empty or null"); + } - var args = new API.Rest.CreateWebhookMessageParams + // check that user flag and user Id list are exclusive, same with role flag and role Id list + if (allowedMentions != null && allowedMentions.AllowedTypes.HasValue) { - Content = text, - AllowedMentions = allowedMentions?.ToModel() ?? Optional.Unspecified, - IsTTS = isTTS, - Embeds = embeds.Select(x => x.ToModel()).ToArray(), - Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional.Unspecified, - File = !string.IsNullOrEmpty(filePath) ? new MultipartFile(new MemoryStream(File.ReadAllBytes(filePath), false), fileName) : Optional.Unspecified - }; + if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Users) && + allowedMentions.UserIds != null && allowedMentions.UserIds.Count > 0) + { + throw new ArgumentException("The Users flag is mutually exclusive with the list of User Ids.", nameof(allowedMentions)); + } + + if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Roles) && + allowedMentions.RoleIds != null && allowedMentions.RoleIds.Count > 0) + { + throw new ArgumentException("The Roles flag is mutually exclusive with the list of Role Ids.", nameof(allowedMentions)); + } + } + + var flags = MessageFlags.None; if (ephemeral) - args.Flags = MessageFlags.Ephemeral; + flags |= MessageFlags.Ephemeral; - return await InteractionHelper.SendFollowupAsync(Discord.Rest, args, Token, Channel, options); + var args = new API.Rest.UploadWebhookFileParams(attachments.ToArray()) { Flags = flags, Content = text, IsTTS = isTTS, Embeds = embeds.Any() ? embeds.Select(x => x.ToModel()).ToArray() : Optional.Unspecified, AllowedMentions = allowedMentions?.ToModel() ?? Optional.Unspecified, MessageComponents = components?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional.Unspecified }; + return await InteractionHelper.SendFollowupAsync(Discord, args, Token, Channel, options).ConfigureAwait(false); } /// diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs index 52f3254c5..1bfd77479 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using Model = Discord.API.Interaction; using DataModel = Discord.API.ApplicationCommandInteractionData; using System.IO; +using System.Collections.Generic; namespace Discord.WebSocket { @@ -130,13 +131,13 @@ namespace Discord.WebSocket /// if the message should be read out by a text-to-speech reader, otherwise . /// if the response should be hidden to everyone besides the invoker of the command, otherwise . /// The allowed mentions for this response. - /// The request options for this response. - /// A to be sent with this response. + /// A to be sent with this response. /// A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored. + /// The request options for this response. /// Message content is too long, length must be less or equal to . /// The parameters provided were invalid or the token was invalid. - public abstract Task RespondAsync(string text = null, Embed[] embeds = null, bool isTTS = false, - bool ephemeral = false, AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null); + public abstract Task RespondAsync(string text = null, Embed[] embeds = null, bool isTTS = false, + bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null); /// /// Sends a followup message for this interaction. @@ -146,14 +147,14 @@ namespace Discord.WebSocket /// if the message should be read out by a text-to-speech reader, otherwise . /// if the response should be hidden to everyone besides the invoker of the command, otherwise . /// The allowed mentions for this response. - /// The request options for this response. - /// A to be sent with this response. + /// A to be sent with this response. /// A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored. + /// The request options for this response. /// /// The sent message. /// public abstract Task FollowupAsync(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, - AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null); + AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null); /// /// Sends a followup message for this interaction. @@ -165,14 +166,14 @@ namespace Discord.WebSocket /// if the message should be read out by a text-to-speech reader, otherwise . /// if the response should be hidden to everyone besides the invoker of the command, otherwise . /// The allowed mentions for this response. - /// The request options for this response. - /// A to be sent with this response. + /// A to be sent with this response. /// A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored. + /// The request options for this response. /// /// The sent message. /// public abstract Task FollowupWithFileAsync(Stream fileStream, string fileName, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, - AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null); + AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null); /// /// Sends a followup message for this interaction. @@ -184,14 +185,52 @@ namespace Discord.WebSocket /// if the message should be read out by a text-to-speech reader, otherwise . /// if the response should be hidden to everyone besides the invoker of the command, otherwise . /// The allowed mentions for this response. - /// The request options for this response. - /// A to be sent with this response. + /// A to be sent with this response. /// A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored. + /// The request options for this response. /// /// The sent message. /// - public abstract Task FollowupWithFileAsync(string filePath, string text = null, string fileName = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, - AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null); + public abstract 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); + + /// + /// Sends a followup message for this interaction. + /// + /// The attachment containing the file and description. + /// The text of the message to be sent. + /// A array of embeds to send with this response. Max 10. + /// if the message should be read out by a text-to-speech reader, otherwise . + /// if the response should be hidden to everyone besides the invoker of the command, otherwise . + /// The allowed mentions for this response. + /// The request options for this response. + /// A to be sent with this response. + /// A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored. + /// + /// A task that represents an asynchronous send operation for delivering the message. The task result + /// contains the sent message. + /// + public abstract 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); + + /// + /// Sends a followup message for this interaction. + /// + /// A collection of attachments to upload. + /// The text of the message to be sent. + /// A array of embeds to send with this response. Max 10. + /// if the message should be read out by a text-to-speech reader, otherwise . + /// if the response should be hidden to everyone besides the invoker of the command, otherwise . + /// The allowed mentions for this response. + /// The request options for this response. + /// A to be sent with this response. + /// A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored. + /// + /// A task that represents an asynchronous send operation for delivering the message. The task result + /// contains the sent message. + /// + public abstract 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); /// /// Gets the original response for this interaction. @@ -222,32 +261,37 @@ namespace Discord.WebSocket /// A task that represents the asynchronous operation of acknowledging the interaction. /// public abstract Task DeferAsync(bool ephemeral = false, RequestOptions options = null); - + #endregion #region IDiscordInteraction /// - async Task IDiscordInteraction.FollowupAsync(string text, Embed[] embeds, bool isTTS, bool ephemeral, AllowedMentions allowedMentions, - RequestOptions options, MessageComponent component, Embed embed) - => await FollowupAsync(text, embeds, isTTS, ephemeral, allowedMentions, options, component, embed).ConfigureAwait(false); - - /// - async Task IDiscordInteraction.FollowupWithFileAsync(Stream fileStream, string fileName, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, - AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null) - => await FollowupWithFileAsync(fileStream, fileName, text, embeds, isTTS, ephemeral, allowedMentions, options, component, embed).ConfigureAwait(false); - - /// - async Task IDiscordInteraction.FollowupWithFileAsync(string filePath, string text = null, string fileName = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, - AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null) - => await FollowupWithFileAsync(filePath, text, fileName, embeds, isTTS, ephemeral, allowedMentions, options, component, embed).ConfigureAwait(false); + IUser IDiscordInteraction.User => User; /// async Task IDiscordInteraction.GetOriginalResponseAsync(RequestOptions options) => await GetOriginalResponseAsync(options).ConfigureAwait(false); - /// async Task IDiscordInteraction.ModifyOriginalResponseAsync(Action func, RequestOptions options) => await ModifyOriginalResponseAsync(func, options).ConfigureAwait(false); + /// + async Task IDiscordInteraction.RespondAsync(string text, Embed[] embeds, bool isTTS, bool ephemeral, AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options) + => await RespondAsync(text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options).ConfigureAwait(false); + /// + async Task IDiscordInteraction.FollowupAsync(string text, Embed[] embeds, bool isTTS, bool ephemeral, AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options) + => await FollowupAsync(text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options).ConfigureAwait(false); + /// + async Task IDiscordInteraction.FollowupWithFileAsync(Stream fileStream, string fileName, string text, Embed[] embeds, bool isTTS, bool ephemeral, AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options) + => await FollowupWithFileAsync(fileStream, fileName, text, embeds, isTTS, ephemeral, allowedMentions, components, embed).ConfigureAwait(false); + /// + async Task IDiscordInteraction.FollowupWithFileAsync(string filePath, string fileName, string text, Embed[] embeds, bool isTTS, bool ephemeral, AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options) + => await FollowupWithFileAsync(filePath, fileName, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options).ConfigureAwait(false); + /// + async Task IDiscordInteraction.FollowupWithFileAsync(FileAttachment attachment, string text, Embed[] embeds, bool isTTS, bool ephemeral, AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options) + => await FollowupWithFileAsync(attachment, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options).ConfigureAwait(false); + /// + async Task IDiscordInteraction.FollowupWithFilesAsync(IEnumerable attachments, string text, Embed[] embeds, bool isTTS, bool ephemeral, AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options) + => await FollowupWithFilesAsync(attachments, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options).ConfigureAwait(false); #endregion } } diff --git a/src/Discord.Net.WebSocket/Entities/Stickers/SocketSticker.cs b/src/Discord.Net.WebSocket/Entities/Stickers/SocketSticker.cs index ee45720b5..b9c122cce 100644 --- a/src/Discord.Net.WebSocket/Entities/Stickers/SocketSticker.cs +++ b/src/Discord.Net.WebSocket/Entities/Stickers/SocketSticker.cs @@ -88,5 +88,11 @@ namespace Discord.WebSocket return base.Equals(obj); } + + /// + public override int GetHashCode() + { + return base.GetHashCode(); + } } }