| @@ -1,11 +1,18 @@ | |||
| #pragma warning disable CS1591 | |||
| using Discord.Net.Converters; | |||
| using Discord.Net.Rest; | |||
| using Newtonsoft.Json; | |||
| using System.Collections.Generic; | |||
| using System.IO; | |||
| using System.Text; | |||
| namespace Discord.API.Rest | |||
| { | |||
| [JsonObject(MemberSerialization = MemberSerialization.OptIn)] | |||
| internal class CreateWebhookMessageParams | |||
| { | |||
| private static JsonSerializer _serializer = new JsonSerializer { ContractResolver = new DiscordContractResolver() }; | |||
| [JsonProperty("content")] | |||
| public string Content { get; set; } | |||
| @@ -32,5 +39,44 @@ namespace Discord.API.Rest | |||
| [JsonProperty("components")] | |||
| public Optional<API.ActionRowComponent[]> Components { get; set; } | |||
| [JsonProperty("file")] | |||
| public Optional<MultipartFile> File { get; set; } | |||
| public IReadOnlyDictionary<string, object> ToDictionary() | |||
| { | |||
| var d = new Dictionary<string, object>(); | |||
| if (File.IsSpecified) | |||
| { | |||
| d["file"] = File.Value; | |||
| } | |||
| var payload = new Dictionary<string, object>(); | |||
| payload["content"] = Content; | |||
| if (IsTTS.IsSpecified) | |||
| payload["tts"] = IsTTS.Value.ToString(); | |||
| if (Nonce.IsSpecified) | |||
| payload["nonce"] = Nonce.Value; | |||
| if (Username.IsSpecified) | |||
| payload["username"] = Username.Value; | |||
| if (AvatarUrl.IsSpecified) | |||
| payload["avatar_url"] = AvatarUrl.Value; | |||
| if (Embeds.IsSpecified) | |||
| payload["embeds"] = Embeds.Value; | |||
| if (AllowedMentions.IsSpecified) | |||
| payload["allowed_mentions"] = AllowedMentions.Value; | |||
| var json = new StringBuilder(); | |||
| using (var text = new StringWriter(json)) | |||
| using (var writer = new JsonTextWriter(text)) | |||
| _serializer.Serialize(writer, payload); | |||
| d["payload_json"] = json.ToString(); | |||
| return d; | |||
| } | |||
| } | |||
| } | |||
| @@ -1286,7 +1286,7 @@ namespace Discord.API | |||
| public async Task<Message> CreateInteractionFollowupMessage(CreateWebhookMessageParams args, string token, RequestOptions options = null) | |||
| { | |||
| if (!args.Embeds.IsSpecified || args.Embeds.Value == null || args.Embeds.Value.Length == 0) | |||
| if ((!args.Embeds.IsSpecified || args.Embeds.Value == null || args.Embeds.Value.Length == 0) && !args.File.IsSpecified) | |||
| Preconditions.NotNullOrEmpty(args.Content, nameof(args.Content)); | |||
| if (args.Content?.Length > DiscordConfig.MaxMessageSize) | |||
| @@ -1294,7 +1294,10 @@ namespace Discord.API | |||
| options = RequestOptions.CreateOrClone(options); | |||
| return await SendJsonAsync<Message>("POST", () => $"webhooks/{CurrentUserId}/{token}?wait=true", args, new BucketIds(), options: options).ConfigureAwait(false); | |||
| if (!args.File.IsSpecified) | |||
| return await SendJsonAsync<Message>("POST", () => $"webhooks/{CurrentUserId}/{token}?wait=true", args, new BucketIds(), options: options).ConfigureAwait(false); | |||
| else | |||
| return await SendMultipartAsync<Message>("POST", () => $"webhooks/{CurrentUserId}/{token}?wait=true", args.ToDictionary(), new BucketIds(), options: options).ConfigureAwait(false); | |||
| } | |||
| public async Task<Message> ModifyInteractionFollowupMessage(ModifyInteractionResponseParams args, ulong id, string token, RequestOptions options = null) | |||
| @@ -3911,6 +3911,12 @@ | |||
| <member name="M:Discord.WebSocket.SocketMessageComponent.FollowupAsync(System.String,Discord.Embed[],System.Boolean,System.Boolean,Discord.AllowedMentions,Discord.RequestOptions,Discord.MessageComponent,Discord.Embed)"> | |||
| <inheritdoc/> | |||
| </member> | |||
| <member name="M:Discord.WebSocket.SocketMessageComponent.FollowupWithFileAsync(System.String,System.IO.Stream,System.String,Discord.Embed[],System.Boolean,System.Boolean,Discord.AllowedMentions,Discord.RequestOptions,Discord.MessageComponent,Discord.Embed)"> | |||
| <inheritdoc/> | |||
| </member> | |||
| <member name="M:Discord.WebSocket.SocketMessageComponent.FollowupWithFileAsync(System.String,System.String,System.String,Discord.Embed[],System.Boolean,System.Boolean,Discord.AllowedMentions,Discord.RequestOptions,Discord.MessageComponent,Discord.Embed)"> | |||
| <inheritdoc/> | |||
| </member> | |||
| <member name="M:Discord.WebSocket.SocketMessageComponent.DeferLoadingAsync(System.Boolean,Discord.RequestOptions)"> | |||
| <summary> | |||
| Defers an interaction and responds with type 5 (<see cref="F:Discord.InteractionResponseType.DeferredChannelMessageWithSource"/>) | |||
| @@ -4090,6 +4096,12 @@ | |||
| <member name="M:Discord.WebSocket.SocketCommandBase.FollowupAsync(System.String,Discord.Embed[],System.Boolean,System.Boolean,Discord.AllowedMentions,Discord.RequestOptions,Discord.MessageComponent,Discord.Embed)"> | |||
| <inheritdoc/> | |||
| </member> | |||
| <member name="M:Discord.WebSocket.SocketCommandBase.FollowupWithFileAsync(System.String,System.IO.Stream,System.String,Discord.Embed[],System.Boolean,System.Boolean,Discord.AllowedMentions,Discord.RequestOptions,Discord.MessageComponent,Discord.Embed)"> | |||
| <inheritdoc/> | |||
| </member> | |||
| <member name="M:Discord.WebSocket.SocketCommandBase.FollowupWithFileAsync(System.String,System.String,System.String,Discord.Embed[],System.Boolean,System.Boolean,Discord.AllowedMentions,Discord.RequestOptions,Discord.MessageComponent,Discord.Embed)"> | |||
| <inheritdoc/> | |||
| </member> | |||
| <member name="M:Discord.WebSocket.SocketCommandBase.DeferAsync(System.Boolean,Discord.RequestOptions)"> | |||
| <summary> | |||
| Acknowledges this interaction with the <see cref="F:Discord.InteractionResponseType.DeferredChannelMessageWithSource"/>. | |||
| @@ -4194,6 +4206,42 @@ | |||
| The sent message. | |||
| </returns> | |||
| </member> | |||
| <member name="M:Discord.WebSocket.SocketInteraction.FollowupWithFileAsync(System.String,System.IO.Stream,System.String,Discord.Embed[],System.Boolean,System.Boolean,Discord.AllowedMentions,Discord.RequestOptions,Discord.MessageComponent,Discord.Embed)"> | |||
| <summary> | |||
| Sends a followup message for this interaction. | |||
| </summary> | |||
| <param name="text">The text of the message to be sent</param> | |||
| <param name="fileStream">The file to upload</param> | |||
| <param name="fileName">The file name of the attachment</param> | |||
| <param name="embeds">A array of embeds to send with this response. Max 10</param> | |||
| <param name="isTTS"><see langword="true"/> if the message should be read out by a text-to-speech reader, otherwise <see langword="false"/>.</param> | |||
| <param name="ephemeral"><see langword="true"/> if the response should be hidden to everyone besides the invoker of the command, otherwise <see langword="false"/>.</param> | |||
| <param name="allowedMentions">The allowed mentions for this response.</param> | |||
| <param name="options">The request options for this response.</param> | |||
| <param name="component">A <see cref="T:Discord.MessageComponent"/> to be sent with this response</param> | |||
| <param name="embed">A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored.</param> | |||
| <returns> | |||
| The sent message. | |||
| </returns> | |||
| </member> | |||
| <member name="M:Discord.WebSocket.SocketInteraction.FollowupWithFileAsync(System.String,System.String,System.String,Discord.Embed[],System.Boolean,System.Boolean,Discord.AllowedMentions,Discord.RequestOptions,Discord.MessageComponent,Discord.Embed)"> | |||
| <summary> | |||
| Sends a followup message for this interaction. | |||
| </summary> | |||
| <param name="text">The text of the message to be sent</param> | |||
| <param name="filePath">The file to upload</param> | |||
| <param name="fileName">The file name of the attachment</param> | |||
| <param name="embeds">A array of embeds to send with this response. Max 10</param> | |||
| <param name="isTTS"><see langword="true"/> if the message should be read out by a text-to-speech reader, otherwise <see langword="false"/>.</param> | |||
| <param name="ephemeral"><see langword="true"/> if the response should be hidden to everyone besides the invoker of the command, otherwise <see langword="false"/>.</param> | |||
| <param name="allowedMentions">The allowed mentions for this response.</param> | |||
| <param name="options">The request options for this response.</param> | |||
| <param name="component">A <see cref="T:Discord.MessageComponent"/> to be sent with this response</param> | |||
| <param name="embed">A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored.</param> | |||
| <returns> | |||
| The sent message. | |||
| </returns> | |||
| </member> | |||
| <member name="M:Discord.WebSocket.SocketInteraction.GetOriginalResponseAsync(Discord.RequestOptions)"> | |||
| <summary> | |||
| Gets the original response for this interaction. | |||
| @@ -5,6 +5,8 @@ using Model = Discord.API.Interaction; | |||
| using DataModel = Discord.API.MessageComponentInteractionData; | |||
| using Discord.Rest; | |||
| using System.Collections.Generic; | |||
| using Discord.Net.Rest; | |||
| using System.IO; | |||
| namespace Discord.WebSocket | |||
| { | |||
| @@ -243,6 +245,85 @@ namespace Discord.WebSocket | |||
| return await InteractionHelper.SendFollowupAsync(Discord.Rest, args, Token, Channel, options); | |||
| } | |||
| /// <inheritdoc/> | |||
| public override async Task<RestFollowupMessage> FollowupWithFileAsync( | |||
| string text = null, | |||
| Stream fileStream = 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) | |||
| { | |||
| if (!IsValidToken) | |||
| throw new InvalidOperationException("Interaction token is no longer valid"); | |||
| if (embeds == null && embed != null) | |||
| embeds = new[] { embed }; | |||
| 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 ?? 0, 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"); | |||
| var args = new API.Rest.CreateWebhookMessageParams | |||
| { | |||
| Content = text, | |||
| AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified, | |||
| IsTTS = isTTS, | |||
| Embeds = embeds?.Select(x => x.ToModel()).ToArray() ?? Optional<API.Embed[]>.Unspecified, | |||
| Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified, | |||
| File = fileStream is not null ? new MultipartFile(fileStream, fileName) : Optional<MultipartFile>.Unspecified | |||
| }; | |||
| if (ephemeral) | |||
| args.Flags = MessageFlags.Ephemeral; | |||
| return await InteractionHelper.SendFollowupAsync(Discord.Rest, args, Token, Channel, options); | |||
| } | |||
| /// <inheritdoc/> | |||
| public override async Task<RestFollowupMessage> FollowupWithFileAsync( | |||
| string text = null, | |||
| string filePath = 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) | |||
| { | |||
| if (!IsValidToken) | |||
| throw new InvalidOperationException("Interaction token is no longer valid"); | |||
| if (embeds == null && embed != null) | |||
| embeds = new[] { embed }; | |||
| 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 ?? 0, 10, nameof(embeds), "A max of 10 embeds are allowed."); | |||
| Preconditions.NotNullOrWhitespace(filePath, nameof(filePath), "Path must exist"); | |||
| var args = new API.Rest.CreateWebhookMessageParams | |||
| { | |||
| Content = text, | |||
| AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified, | |||
| IsTTS = isTTS, | |||
| Embeds = embeds?.Select(x => x.ToModel()).ToArray() ?? Optional<API.Embed[]>.Unspecified, | |||
| Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified, | |||
| File = !string.IsNullOrEmpty(filePath) ? new MultipartFile(new MemoryStream(File.ReadAllBytes(filePath), false), fileName) : Optional<MultipartFile>.Unspecified | |||
| }; | |||
| if (ephemeral) | |||
| args.Flags = MessageFlags.Ephemeral; | |||
| return await InteractionHelper.SendFollowupAsync(Discord.Rest, args, Token, Channel, options); | |||
| } | |||
| /// <summary> | |||
| /// Defers an interaction and responds with type 5 (<see cref="InteractionResponseType.DeferredChannelMessageWithSource"/>) | |||
| /// </summary> | |||
| @@ -1,6 +1,8 @@ | |||
| using Discord.Net.Rest; | |||
| using Discord.Rest; | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.IO; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| @@ -146,6 +148,85 @@ namespace Discord.WebSocket | |||
| return await InteractionHelper.SendFollowupAsync(Discord.Rest, args, Token, Channel, options); | |||
| } | |||
| /// <inheritdoc/> | |||
| public override async Task<RestFollowupMessage> FollowupWithFileAsync( | |||
| string text = null, | |||
| Stream fileStream = 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) | |||
| { | |||
| if (!IsValidToken) | |||
| throw new InvalidOperationException("Interaction token is no longer valid"); | |||
| if (embeds == null && embed != null) | |||
| embeds = new[] { embed }; | |||
| 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 ?? 0, 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"); | |||
| var args = new API.Rest.CreateWebhookMessageParams | |||
| { | |||
| Content = text, | |||
| AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified, | |||
| IsTTS = isTTS, | |||
| Embeds = embeds?.Select(x => x.ToModel()).ToArray() ?? Optional<API.Embed[]>.Unspecified, | |||
| Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified, | |||
| File = fileStream is not null ? new MultipartFile(fileStream, fileName) : Optional<MultipartFile>.Unspecified | |||
| }; | |||
| if (ephemeral) | |||
| args.Flags = MessageFlags.Ephemeral; | |||
| return await InteractionHelper.SendFollowupAsync(Discord.Rest, args, Token, Channel, options); | |||
| } | |||
| /// <inheritdoc/> | |||
| public override async Task<RestFollowupMessage> FollowupWithFileAsync( | |||
| string text = null, | |||
| string filePath = 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) | |||
| { | |||
| if (!IsValidToken) | |||
| throw new InvalidOperationException("Interaction token is no longer valid"); | |||
| if (embeds == null && embed != null) | |||
| embeds = new[] { embed }; | |||
| 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 ?? 0, 10, nameof(embeds), "A max of 10 embeds are allowed."); | |||
| Preconditions.NotNullOrWhitespace(filePath, nameof(filePath), "Path must exist"); | |||
| var args = new API.Rest.CreateWebhookMessageParams | |||
| { | |||
| Content = text, | |||
| AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified, | |||
| IsTTS = isTTS, | |||
| Embeds = embeds?.Select(x => x.ToModel()).ToArray() ?? Optional<API.Embed[]>.Unspecified, | |||
| Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified, | |||
| File = !string.IsNullOrEmpty(filePath) ? new MultipartFile(new MemoryStream(File.ReadAllBytes(filePath), false), fileName) : Optional<MultipartFile>.Unspecified | |||
| }; | |||
| if (ephemeral) | |||
| args.Flags = MessageFlags.Ephemeral; | |||
| return await InteractionHelper.SendFollowupAsync(Discord.Rest, args, Token, Channel, options); | |||
| } | |||
| /// <summary> | |||
| /// Acknowledges this interaction with the <see cref="InteractionResponseType.DeferredChannelMessageWithSource"/>. | |||
| /// </summary> | |||
| @@ -3,6 +3,7 @@ using System; | |||
| using System.Threading.Tasks; | |||
| using Model = Discord.API.Interaction; | |||
| using DataModel = Discord.API.ApplicationCommandInteractionData; | |||
| using System.IO; | |||
| namespace Discord.WebSocket | |||
| { | |||
| @@ -145,6 +146,44 @@ namespace Discord.WebSocket | |||
| public abstract Task<RestFollowupMessage> 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); | |||
| /// <summary> | |||
| /// Sends a followup message for this interaction. | |||
| /// </summary> | |||
| /// <param name="text">The text of the message to be sent</param> | |||
| /// <param name="fileStream">The file to upload</param> | |||
| /// <param name="fileName">The file name of the attachment</param> | |||
| /// <param name="embeds">A array of embeds to send with this response. Max 10</param> | |||
| /// <param name="isTTS"><see langword="true"/> if the message should be read out by a text-to-speech reader, otherwise <see langword="false"/>.</param> | |||
| /// <param name="ephemeral"><see langword="true"/> if the response should be hidden to everyone besides the invoker of the command, otherwise <see langword="false"/>.</param> | |||
| /// <param name="allowedMentions">The allowed mentions for this response.</param> | |||
| /// <param name="options">The request options for this response.</param> | |||
| /// <param name="component">A <see cref="MessageComponent"/> to be sent with this response</param> | |||
| /// <param name="embed">A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored.</param> | |||
| /// <returns> | |||
| /// The sent message. | |||
| /// </returns> | |||
| public abstract Task<RestFollowupMessage> FollowupWithFileAsync(string text = null, Stream fileStream = 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); | |||
| /// <summary> | |||
| /// Sends a followup message for this interaction. | |||
| /// </summary> | |||
| /// <param name="text">The text of the message to be sent</param> | |||
| /// <param name="filePath">The file to upload</param> | |||
| /// <param name="fileName">The file name of the attachment</param> | |||
| /// <param name="embeds">A array of embeds to send with this response. Max 10</param> | |||
| /// <param name="isTTS"><see langword="true"/> if the message should be read out by a text-to-speech reader, otherwise <see langword="false"/>.</param> | |||
| /// <param name="ephemeral"><see langword="true"/> if the response should be hidden to everyone besides the invoker of the command, otherwise <see langword="false"/>.</param> | |||
| /// <param name="allowedMentions">The allowed mentions for this response.</param> | |||
| /// <param name="options">The request options for this response.</param> | |||
| /// <param name="component">A <see cref="MessageComponent"/> to be sent with this response</param> | |||
| /// <param name="embed">A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored.</param> | |||
| /// <returns> | |||
| /// The sent message. | |||
| /// </returns> | |||
| public abstract Task<RestFollowupMessage> FollowupWithFileAsync(string text = null, string filePath = 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); | |||
| /// <summary> | |||
| /// Gets the original response for this interaction. | |||
| /// </summary> | |||