* Refactor Interactions * Remove ApplicationCommandExceptionpull/1958/head
@@ -3,7 +3,7 @@ namespace Discord | |||
/// <summary> | |||
/// Represents a Message Command interaction. | |||
/// </summary> | |||
public interface IMessageCommandInteraction : IDiscordInteraction | |||
public interface IMessageCommandInteraction : IApplicationCommandInteraction | |||
{ | |||
/// <summary> | |||
/// Gets the data associated with this interaction. | |||
@@ -3,7 +3,7 @@ namespace Discord | |||
/// <summary> | |||
/// Represents a User Command interaction. | |||
/// </summary> | |||
public interface IUserCommandInteraction : IDiscordInteraction | |||
public interface IUserCommandInteraction : IApplicationCommandInteraction | |||
{ | |||
/// <summary> | |||
/// Gets the data associated with this interaction. | |||
@@ -0,0 +1,19 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Text; | |||
using System.Threading.Tasks; | |||
namespace Discord | |||
{ | |||
/// <summary> | |||
/// Represents an application command interaction. | |||
/// </summary> | |||
public interface IApplicationCommandInteraction : IDiscordInteraction | |||
{ | |||
/// <summary> | |||
/// Gets the data of the application command interaction | |||
/// </summary> | |||
new IApplicationCommandInteractionData Data { get; } | |||
} | |||
} |
@@ -1,4 +1,5 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.IO; | |||
using System.Threading.Tasks; | |||
@@ -34,6 +35,11 @@ namespace Discord | |||
/// </summary> | |||
int Version { get; } | |||
/// <summary> | |||
/// Gets the user who invoked the interaction. | |||
/// </summary> | |||
IUser User { get; } | |||
/// <summary> | |||
/// Responds to an Interaction with type <see cref="InteractionResponseType.ChannelMessageWithSource"/>. | |||
/// </summary> | |||
@@ -43,10 +49,14 @@ namespace Discord | |||
/// <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="components">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> | |||
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); | |||
/// <returns> | |||
/// A task that represents an asynchronous send operation for delivering the message. The task result | |||
/// contains the sent message. | |||
/// </returns> | |||
Task<IUserMessage> 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); | |||
/// <summary> | |||
/// Sends a followup message for this interaction. | |||
@@ -57,13 +67,14 @@ namespace Discord | |||
/// <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="components">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. | |||
/// A task that represents an asynchronous send operation for delivering the message. The task result | |||
/// contains the sent message. | |||
/// </returns> | |||
Task<IUserMessage> 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); | |||
/// <summary> | |||
/// Sends a followup message for this interaction. | |||
@@ -76,13 +87,14 @@ namespace Discord | |||
/// <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="components">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. | |||
/// A task that represents an asynchronous send operation for delivering the message. The task result | |||
/// contains the sent message. | |||
/// </returns> | |||
public Task<IUserMessage> 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); | |||
/// <summary> | |||
/// Sends a followup message for this interaction. | |||
@@ -95,13 +107,50 @@ namespace Discord | |||
/// <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="components">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> | |||
/// A task that represents an asynchronous send operation for delivering the message. The task result | |||
/// contains the sent message. | |||
/// </returns> | |||
public Task<IUserMessage> FollowupWithFileAsync(string filePath, string fileName = null, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, | |||
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null); | |||
/// <summary> | |||
/// Sends a followup message for this interaction. | |||
/// </summary> | |||
/// <param name="attachment">The attachment containing the file and description.</param> | |||
/// <param name="text">The text of the message to be sent.</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="components">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> | |||
/// A task that represents an asynchronous send operation for delivering the message. The task result | |||
/// contains the sent message. | |||
/// </returns> | |||
Task<IUserMessage> FollowupWithFileAsync(FileAttachment attachment, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, | |||
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null); | |||
/// <summary> | |||
/// Sends a followup message for this interaction. | |||
/// </summary> | |||
/// <param name="attachments">A collection of attachments to upload.</param> | |||
/// <param name="text">The text of the message to be sent.</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="components">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. | |||
/// A task that represents an asynchronous send operation for delivering the message. The task result | |||
/// contains the sent message. | |||
/// </returns> | |||
public Task<IUserMessage> 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<IUserMessage> FollowupWithFilesAsync(IEnumerable<FileAttachment> attachments, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, | |||
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null); | |||
/// <summary> | |||
/// Gets the original response for this interaction. | |||
@@ -115,14 +164,17 @@ namespace Discord | |||
/// </summary> | |||
/// <param name="func">A delegate containing the properties to modify the message with.</param> | |||
/// <param name="options">The request options for this <see langword="async"/> request.</param> | |||
/// <returns>A <see cref="IUserMessage"/> that represents the initial response.</returns> | |||
/// <returns> | |||
/// A task that represents an asynchronous modification operation. The task result | |||
/// contains the updated message. | |||
/// </returns> | |||
Task<IUserMessage> ModifyOriginalResponseAsync(Action<MessageProperties> func, RequestOptions options = null); | |||
/// <summary> | |||
/// Acknowledges this interaction. | |||
/// </summary> | |||
/// <returns> | |||
/// A task that represents the asynchronous operation of acknowledging the interaction. | |||
/// A task that represents the asynchronous operation of deferring the interaction. | |||
/// </returns> | |||
Task DeferAsync(bool ephemeral = false, RequestOptions options = null); | |||
} | |||
@@ -3,7 +3,7 @@ namespace Discord | |||
/// <summary> | |||
/// Represents a slash command interaction. | |||
/// </summary> | |||
public interface ISlashCommandInteraction : IDiscordInteraction | |||
public interface ISlashCommandInteraction : IApplicationCommandInteraction | |||
{ | |||
/// <summary> | |||
/// Gets the data associated with this interaction. | |||
@@ -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()) | |||
{ | |||
} | |||
} | |||
} |
@@ -36,12 +36,12 @@ namespace Discord.Interactions | |||
/// <inheritdoc cref="IDiscordInteraction.RespondAsync(string, Embed[], bool, bool, AllowedMentions, RequestOptions, MessageComponent, Embed)"/> | |||
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); | |||
/// <inheritdoc cref="IDiscordInteraction.FollowupAsync(string, Embed[], bool, bool, AllowedMentions, RequestOptions, MessageComponent, Embed)"/> | |||
protected virtual async Task<IUserMessage> FollowupAsync (string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, | |||
AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent 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); | |||
/// <inheritdoc cref="IMessageChannel.SendMessageAsync(string, bool, Embed, RequestOptions, AllowedMentions, MessageReference, MessageComponent, ISticker[], Embed[])"/> | |||
protected virtual async Task<IUserMessage> ReplyAsync (string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, | |||
@@ -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); | |||
} | |||
} | |||
} |
@@ -21,6 +21,7 @@ namespace Discord.API.Rest | |||
public Optional<Embed[]> Embeds { get; set; } | |||
public Optional<AllowedMentions> AllowedMentions { get; set; } | |||
public Optional<ActionRowComponent[]> MessageComponents { get; set; } | |||
public Optional<MessageFlags> 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<object> attachments = new(); | |||
@@ -1198,25 +1198,25 @@ namespace Discord.API | |||
options = RequestOptions.CreateOrClone(options); | |||
return await TrySendApplicationCommandAsync(SendJsonAsync<ApplicationCommand>("POST", () => $"applications/{CurrentUserId}/commands", command, new BucketIds(), options: options)).ConfigureAwait(false); | |||
return await SendJsonAsync<ApplicationCommand>("POST", () => $"applications/{CurrentUserId}/commands", command, new BucketIds(), options: options).ConfigureAwait(false); | |||
} | |||
public async Task<ApplicationCommand> ModifyGlobalApplicationCommandAsync(ModifyApplicationCommandParams command, ulong commandId, RequestOptions options = null) | |||
{ | |||
options = RequestOptions.CreateOrClone(options); | |||
return await TrySendApplicationCommandAsync(SendJsonAsync<ApplicationCommand>("PATCH", () => $"applications/{CurrentUserId}/commands/{commandId}", command, new BucketIds(), options: options)).ConfigureAwait(false); | |||
return await SendJsonAsync<ApplicationCommand>("PATCH", () => $"applications/{CurrentUserId}/commands/{commandId}", command, new BucketIds(), options: options).ConfigureAwait(false); | |||
} | |||
public async Task<ApplicationCommand> ModifyGlobalApplicationUserCommandAsync(ModifyApplicationCommandParams command, ulong commandId, RequestOptions options = null) | |||
{ | |||
options = RequestOptions.CreateOrClone(options); | |||
return await TrySendApplicationCommandAsync(SendJsonAsync<ApplicationCommand>("PATCH", () => $"applications/{CurrentUserId}/commands/{commandId}", command, new BucketIds(), options: options)).ConfigureAwait(false); | |||
return await SendJsonAsync<ApplicationCommand>("PATCH", () => $"applications/{CurrentUserId}/commands/{commandId}", command, new BucketIds(), options: options).ConfigureAwait(false); | |||
} | |||
public async Task<ApplicationCommand> ModifyGlobalApplicationMessageCommandAsync(ModifyApplicationCommandParams command, ulong commandId, RequestOptions options = null) | |||
{ | |||
options = RequestOptions.CreateOrClone(options); | |||
return await TrySendApplicationCommandAsync(SendJsonAsync<ApplicationCommand>("PATCH", () => $"applications/{CurrentUserId}/commands/{commandId}", command, new BucketIds(), options: options)).ConfigureAwait(false); | |||
return await SendJsonAsync<ApplicationCommand>("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<ApplicationCommand[]>("PUT", () => $"applications/{CurrentUserId}/commands", commands, new BucketIds(), options: options)).ConfigureAwait(false); | |||
return await SendJsonAsync<ApplicationCommand[]>("PUT", () => $"applications/{CurrentUserId}/commands", commands, new BucketIds(), options: options).ConfigureAwait(false); | |||
} | |||
public async Task<ApplicationCommand[]> GetGuildApplicationCommandsAsync(ulong guildId, RequestOptions options = null) | |||
@@ -1271,7 +1271,7 @@ namespace Discord.API | |||
var bucket = new BucketIds(guildId: guildId); | |||
return await TrySendApplicationCommandAsync(SendJsonAsync<ApplicationCommand>("POST", () => $"applications/{CurrentUserId}/guilds/{guildId}/commands", command, bucket, options: options)).ConfigureAwait(false); | |||
return await SendJsonAsync<ApplicationCommand>("POST", () => $"applications/{CurrentUserId}/guilds/{guildId}/commands", command, bucket, options: options).ConfigureAwait(false); | |||
} | |||
public async Task<ApplicationCommand> 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<ApplicationCommand>("PATCH", () => $"applications/{CurrentUserId}/guilds/{guildId}/commands/{commandId}", command, bucket, options: options)).ConfigureAwait(false); | |||
return await SendJsonAsync<ApplicationCommand>("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<ApplicationCommand[]>("PUT", () => $"applications/{CurrentUserId}/guilds/{guildId}/commands", commands, bucket, options: options)).ConfigureAwait(false); | |||
return await SendJsonAsync<ApplicationCommand[]>("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<Message>("GET", () => $"webhooks/{CurrentUserId}/{interactionToken}/messages/@original", new BucketIds(), options: options).ConfigureAwait(false); | |||
return await NullifyNotFound(SendAsync<Message>("GET", () => $"webhooks/{CurrentUserId}/{interactionToken}/messages/@original", new BucketIds(), options: options)).ConfigureAwait(false); | |||
} | |||
public async Task<Message> ModifyInteractionResponseAsync(ModifyInteractionResponseParams args, string interactionToken, RequestOptions options = null) | |||
{ | |||
@@ -1347,6 +1347,21 @@ namespace Discord.API | |||
return await SendMultipartAsync<Message>("POST", () => $"webhooks/{CurrentUserId}/{token}?wait=true", args.ToDictionary(), new BucketIds(), options: options).ConfigureAwait(false); | |||
} | |||
public async Task<Message> 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<Message>("POST", () => $"webhooks/{CurrentUserId}/{token}?wait=true", args.ToDictionary(), ids, clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false); | |||
} | |||
public async Task<Message> 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<T>(reader); | |||
} | |||
protected async Task<T> TrySendApplicationCommandAsync<T>(Task<T> 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<T> NullifyNotFound<T>(Task<T> sendTask) where T : class | |||
{ | |||
try | |||
@@ -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); | |||
} | |||
/// <summary> | |||
/// Sends a followup message for this interaction. | |||
/// </summary> | |||
/// <param name="text">The text of the message to be sent.</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> | |||
/// <inheritdoc/> | |||
public override async 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) | |||
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); | |||
} | |||
/// <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 override async Task<RestFollowupMessage> FollowupWithFileAsync( | |||
/// <inheritdoc/> | |||
public override Task<RestFollowupMessage> 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<API.AllowedMentions>.Unspecified, | |||
IsTTS = isTTS, | |||
Embeds = embeds.Select(x => x.ToModel()).ToArray(), | |||
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 | |||
}; | |||
return FollowupWithFileAsync(new FileAttachment(fileStream, fileName), text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options); | |||
} | |||
if (ephemeral) | |||
args.Flags = MessageFlags.Ephemeral; | |||
/// <inheritdoc/> | |||
public override Task<RestFollowupMessage> 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); | |||
} | |||
/// <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 override async Task<RestFollowupMessage> FollowupWithFileAsync( | |||
string filePath, | |||
/// <inheritdoc/> | |||
public override Task<RestFollowupMessage> 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); | |||
} | |||
/// <inheritdoc/> | |||
public override async Task<RestFollowupMessage> FollowupWithFilesAsync( | |||
IEnumerable<FileAttachment> attachments, | |||
string text = null, | |||
Embed[] embeds = null, | |||
bool isTTS = false, | |||
bool ephemeral = false, | |||
AllowedMentions allowedMentions = null, | |||
MessageComponent components = null, | |||
Embed embed = null, | |||
RequestOptions options = null) | |||
{ | |||
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<API.AllowedMentions>.Unspecified, | |||
IsTTS = isTTS, | |||
Embeds = embeds.Select(x => x.ToModel()).ToArray(), | |||
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 (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<API.Embed[]>.Unspecified, AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified, MessageComponents = components?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified }; | |||
return await InteractionHelper.SendFollowupAsync(Discord, args, Token, Channel, options).ConfigureAwait(false); | |||
} | |||
/// <summary> | |||
@@ -41,5 +41,8 @@ namespace Discord.Rest | |||
//IMessageCommandInteraction | |||
/// <inheritdoc/> | |||
IMessageCommandInteractionData IMessageCommandInteraction.Data => Data; | |||
//IApplicationCommandInteraction | |||
/// <inheritdoc/> | |||
IApplicationCommandInteractionData IApplicationCommandInteraction.Data => Data; | |||
} | |||
} |
@@ -44,5 +44,9 @@ namespace Discord.Rest | |||
//IUserCommandInteractionData | |||
/// <inheritdoc/> | |||
IUserCommandInteractionData IUserCommandInteraction.Data => Data; | |||
//IApplicationCommandInteraction | |||
/// <inheritdoc/> | |||
IApplicationCommandInteractionData IApplicationCommandInteraction.Data => Data; | |||
} | |||
} |
@@ -34,17 +34,20 @@ namespace Discord.Rest | |||
return client.ApiClient.BulkOverwriteGlobalApplicationCommandsAsync(Array.Empty<CreateApplicationCommandParams>(), options); | |||
} | |||
public static Task SendInteractionResponseAsync(BaseDiscordClient client, InteractionResponse response, | |||
ulong interactionId, string interactionToken, RequestOptions options = null) | |||
public static async Task<RestInteractionMessage> 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<RestInteractionMessage> 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<RestFollowupMessage> 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<RestFollowupMessage> 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 | |||
@@ -64,9 +64,9 @@ namespace Discord.Rest | |||
/// <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> | |||
/// <param name="options">The request options for this response.</param> | |||
/// <returns> | |||
/// A string that contains json to write back to the incoming http request. | |||
/// </returns> | |||
@@ -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); | |||
} | |||
/// <summary> | |||
/// Sends a followup message for this interaction. | |||
/// </summary> | |||
/// <param name="text">The text of the message to be sent.</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> | |||
/// <inheritdoc/> | |||
public override async 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) | |||
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); | |||
} | |||
/// <inheritdoc/> | |||
public override async Task<RestFollowupMessage> FollowupWithFileAsync( | |||
public override Task<RestFollowupMessage> 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<API.AllowedMentions>.Unspecified, | |||
IsTTS = isTTS, | |||
Embeds = embeds.Select(x => x.ToModel()).ToArray(), | |||
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 | |||
}; | |||
return FollowupWithFileAsync(new FileAttachment(fileStream, fileName), text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options); | |||
} | |||
if (ephemeral) | |||
args.Flags = MessageFlags.Ephemeral; | |||
/// <inheritdoc/> | |||
public override Task<RestFollowupMessage> 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); | |||
} | |||
/// <inheritdoc/> | |||
public override async Task<RestFollowupMessage> FollowupWithFileAsync( | |||
string filePath, | |||
public override Task<RestFollowupMessage> 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); | |||
} | |||
/// <inheritdoc/> | |||
public override async Task<RestFollowupMessage> FollowupWithFilesAsync( | |||
IEnumerable<FileAttachment> attachments, | |||
string text = null, | |||
Embed[] embeds = null, | |||
bool isTTS = false, | |||
bool ephemeral = false, | |||
AllowedMentions allowedMentions = null, | |||
MessageComponent components = null, | |||
Embed embed = null, | |||
RequestOptions options = null) | |||
{ | |||
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<API.AllowedMentions>.Unspecified, | |||
IsTTS = isTTS, | |||
Embeds = embeds.Select(x => x.ToModel()).ToArray(), | |||
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 | |||
}; | |||
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<API.Embed[]>.Unspecified, AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified, MessageComponents = components?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified }; | |||
return await InteractionHelper.SendFollowupAsync(Discord, args, Token, Channel, options).ConfigureAwait(false); | |||
} | |||
/// <summary> | |||
@@ -139,8 +139,7 @@ namespace Discord.Rest | |||
/// <inheritdoc/> | |||
public abstract string Defer(bool ephemeral = false, RequestOptions options = null); | |||
/// <inheritdoc/> | |||
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> | |||
/// Gets the original response for this interaction. | |||
/// </summary> | |||
@@ -154,14 +153,36 @@ namespace Discord.Rest | |||
/// </summary> | |||
/// <param name="func">A delegate containing the properties to modify the message with.</param> | |||
/// <param name="options">The request options for this <see langword="async"/> request.</param> | |||
/// <returns>A <see cref="RestInteractionMessage"/> that represents the initial response.</returns> | |||
/// <returns> | |||
/// A task that represents an asynchronous send operation for delivering the message. The task result | |||
/// contains the sent message. | |||
/// </returns> | |||
public async Task<RestInteractionMessage> ModifyOriginalResponseAsync(Action<MessageProperties> func, RequestOptions options = null) | |||
{ | |||
var model = await InteractionHelper.ModifyInteractionResponseAsync(Discord, Token, func, options); | |||
return RestInteractionMessage.Create(Discord, model, Token, Channel); | |||
} | |||
/// <inheritdoc/> | |||
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); | |||
/// <summary> | |||
/// Sends a followup message for this interaction. | |||
/// </summary> | |||
/// <param name="text">The text of the message to be sent.</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="components">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> | |||
/// <param name="options">The request options for this response.</param> | |||
/// <returns> | |||
/// A task that represents an asynchronous send operation for delivering the message. The task result | |||
/// contains the sent message. | |||
/// </returns> | |||
public abstract Task<RestFollowupMessage> 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); | |||
/// <summary> | |||
/// Sends a followup message for this interaction. | |||
/// </summary> | |||
@@ -172,14 +193,16 @@ namespace Discord.Rest | |||
/// <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="components">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> | |||
/// <param name="options">The request options for this response.</param> | |||
/// <returns> | |||
/// The sent message. | |||
/// A task that represents an asynchronous send operation for delivering the message. The task result | |||
/// contains the sent message. | |||
/// </returns> | |||
public abstract Task<RestFollowupMessage> 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); | |||
/// <summary> | |||
/// Sends a followup message for this interaction. | |||
/// </summary> | |||
@@ -190,45 +213,90 @@ namespace Discord.Rest | |||
/// <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="components">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> | |||
/// <param name="options">The request options for this response.</param> | |||
/// <param name="component">A <see cref="MessageComponent"/> to be sent with this response.</param> | |||
/// <returns> | |||
/// A task that represents an asynchronous send operation for delivering the message. The task result | |||
/// contains the sent message. | |||
/// </returns> | |||
public abstract Task<RestFollowupMessage> 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); | |||
/// <summary> | |||
/// Sends a followup message for this interaction. | |||
/// </summary> | |||
/// <param name="attachment">The attachment containing the file and description.</param> | |||
/// <param name="text">The text of the message to be sent.</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="components">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. | |||
/// A task that represents an asynchronous send operation for delivering the message. The task result | |||
/// contains the sent message. | |||
/// </returns> | |||
public abstract Task<RestFollowupMessage> 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<RestFollowupMessage> 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); | |||
/// <summary> | |||
/// Sends a followup message for this interaction. | |||
/// </summary> | |||
/// <param name="attachments">A collection of attachments to upload.</param> | |||
/// <param name="text">The text of the message to be sent.</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="components">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> | |||
/// A task that represents an asynchronous send operation for delivering the message. The task result | |||
/// contains the sent message. | |||
/// </returns> | |||
public abstract Task<RestFollowupMessage> FollowupWithFilesAsync(IEnumerable<FileAttachment> attachments, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, | |||
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null); | |||
#region IDiscordInteraction | |||
/// <inheritdoc/> | |||
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; | |||
/// <inheritdoc/> | |||
Task<IUserMessage> IDiscordInteraction.RespondAsync(string text, Embed[] embeds, bool isTTS, bool ephemeral, AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options) | |||
{ | |||
return Task.FromResult<IUserMessage>(null); | |||
} | |||
/// <inheritdoc/> | |||
Task IDiscordInteraction.DeferAsync(bool ephemeral, RequestOptions options) | |||
=> Task.FromResult(Defer(ephemeral, options)); | |||
/// <inheritdoc/> | |||
async Task<IUserMessage> 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); | |||
/// <inheritdoc/> | |||
async Task<IUserMessage> IDiscordInteraction.GetOriginalResponseAsync(RequestOptions options) | |||
=> await GetOriginalResponseAsync(options).ConfigureAwait(false); | |||
/// <inheritdoc/> | |||
async Task<IUserMessage> IDiscordInteraction.ModifyOriginalResponseAsync(Action<MessageProperties> func, RequestOptions options) | |||
=> await ModifyOriginalResponseAsync(func, options).ConfigureAwait(false); | |||
/// <inheritdoc/> | |||
async Task<IUserMessage> 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<IUserMessage> 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); | |||
/// <inheritdoc/> | |||
async Task<IUserMessage> 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); | |||
/// <inheritdoc/> | |||
async Task<IUserMessage> 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); | |||
/// <inheritdoc/> | |||
async Task<IUserMessage> 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<IUserMessage> IDiscordInteraction.FollowupWithFilesAsync(IEnumerable<FileAttachment> 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 | |||
} | |||
} |
@@ -38,9 +38,11 @@ namespace Discord.Rest | |||
} | |||
public override string Defer(bool ephemeral = false, RequestOptions options = null) => throw new NotSupportedException(); | |||
public override 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) => throw new NotSupportedException(); | |||
public override Task<RestFollowupMessage> 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<RestFollowupMessage> 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<RestFollowupMessage> 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<RestFollowupMessage> 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<RestFollowupMessage> 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<RestFollowupMessage> 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<RestFollowupMessage> FollowupWithFilesAsync(IEnumerable<FileAttachment> attachments, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null) => throw new NotSupportedException(); | |||
} | |||
} |
@@ -102,31 +102,21 @@ namespace Discord.Rest | |||
/// </returns> | |||
public string Respond(RequestOptions options = null, params AutocompleteResult[] result) | |||
=> Respond(result, options); | |||
/// <inheritdoc/> | |||
[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<RestFollowupMessage> 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<RestFollowupMessage> 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<RestFollowupMessage> 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<RestFollowupMessage> 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<RestFollowupMessage> FollowupWithFilesAsync(IEnumerable<FileAttachment> attachments, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null) | |||
=> throw new NotSupportedException("Autocomplete interactions don't support this method!"); | |||
/// <inheritdoc/> | |||
[Obsolete("Autocomplete interactions cannot have followups!", true)] | |||
public override 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) | |||
=> throw new NotSupportedException("Autocomplete interactions cannot be deferred!"); | |||
/// <inheritdoc/> | |||
[Obsolete("Autocomplete interactions cannot have followups!", true)] | |||
public override Task<RestFollowupMessage> 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!"); | |||
/// <inheritdoc/> | |||
[Obsolete("Autocomplete interactions cannot have followups!", true)] | |||
public override Task<RestFollowupMessage> 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!"); | |||
/// <inheritdoc/> | |||
[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 | |||
/// <inheritdoc/> | |||
@@ -44,5 +44,9 @@ namespace Discord.Rest | |||
//ISlashCommandInteraction | |||
/// <inheritdoc/> | |||
IApplicationCommandInteractionData ISlashCommandInteraction.Data => Data; | |||
//IApplicationCommandInteraction | |||
/// <inheritdoc/> | |||
IApplicationCommandInteractionData IApplicationCommandInteraction.Data => Data; | |||
} | |||
} |
@@ -5,7 +5,7 @@ using Model = Discord.API.Message; | |||
namespace Discord.Rest | |||
{ | |||
/// <summary> | |||
/// 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. | |||
/// </summary> | |||
public class RestFollowupMessage : RestUserMessage | |||
{ | |||
@@ -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 | |||
{ | |||
/// <summary> | |||
/// Represents the initial REST-based response to a slash command. | |||
/// Represents the initial REST-based response to an interaction. | |||
/// </summary> | |||
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)); | |||
} | |||
/// <summary> | |||
/// Deletes this object and all of it's children. | |||
/// </summary> | |||
@@ -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<UserProperties>.Unspecified, | |||
Id = command.User.Id, | |||
} | |||
}; | |||
} | |||
return messageModel; | |||
} | |||
return new API.Message | |||
{ | |||
Id = interaction.Id, | |||
}; | |||
} | |||
} | |||
} |
@@ -41,5 +41,9 @@ namespace Discord.WebSocket | |||
//IDiscordInteraction | |||
/// <inheritdoc/> | |||
IDiscordInteractionData IDiscordInteraction.Data => Data; | |||
//IApplicationCommandInteraction | |||
/// <inheritdoc/> | |||
IApplicationCommandInteractionData IApplicationCommandInteraction.Data => Data; | |||
} | |||
} |
@@ -41,5 +41,9 @@ namespace Discord.WebSocket | |||
//IDiscordInteraction | |||
/// <inheritdoc/> | |||
IDiscordInteractionData IDiscordInteraction.Data => Data; | |||
//IApplicationCommandInteraction | |||
/// <inheritdoc/> | |||
IApplicationCommandInteractionData IApplicationCommandInteraction.Data => Data; | |||
} | |||
} |
@@ -72,15 +72,15 @@ namespace Discord.WebSocket | |||
} | |||
} | |||
/// <inheritdoc/> | |||
public override async Task RespondAsync( | |||
public override async Task<RestInteractionMessage> 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); | |||
} | |||
/// <inheritdoc/> | |||
public override async Task<RestFollowupMessage> FollowupWithFileAsync( | |||
public override Task<RestFollowupMessage> 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<API.AllowedMentions>.Unspecified, | |||
IsTTS = isTTS, | |||
Embeds = embeds.Select(x => x.ToModel()).ToArray(), | |||
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 | |||
}; | |||
return FollowupWithFileAsync(new FileAttachment(fileStream, fileName), text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options); | |||
} | |||
if (ephemeral) | |||
args.Flags = MessageFlags.Ephemeral; | |||
/// <inheritdoc/> | |||
public override Task<RestFollowupMessage> 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); | |||
} | |||
/// <inheritdoc/> | |||
public override async Task<RestFollowupMessage> FollowupWithFileAsync( | |||
string filePath, | |||
public override Task<RestFollowupMessage> 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); | |||
} | |||
/// <inheritdoc/> | |||
public override async Task<RestFollowupMessage> FollowupWithFilesAsync( | |||
IEnumerable<FileAttachment> attachments, | |||
string text = null, | |||
Embed[] embeds = null, | |||
bool isTTS = false, | |||
bool ephemeral = false, | |||
AllowedMentions allowedMentions = null, | |||
MessageComponent components = null, | |||
Embed embed = null, | |||
RequestOptions options = null) | |||
{ | |||
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<API.AllowedMentions>.Unspecified, | |||
IsTTS = isTTS, | |||
Embeds = embeds.Select(x => x.ToModel()).ToArray(), | |||
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 | |||
}; | |||
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<API.Embed[]>.Unspecified, AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified, MessageComponents = components?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified }; | |||
return await InteractionHelper.SendFollowupAsync(Discord, args, Token, Channel, options).ConfigureAwait(false); | |||
} | |||
/// <summary> | |||
@@ -89,31 +89,20 @@ namespace Discord.WebSocket | |||
/// </returns> | |||
public Task RespondAsync(RequestOptions options = null, params AutocompleteResult[] result) | |||
=> RespondAsync(result, options); | |||
/// <inheritdoc/> | |||
[Obsolete("Autocomplete interactions cannot be deferred!", true)] | |||
public override Task<RestInteractionMessage> 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<RestFollowupMessage> 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<RestFollowupMessage> 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<RestFollowupMessage> 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<RestFollowupMessage> 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<RestFollowupMessage> FollowupWithFilesAsync(IEnumerable<FileAttachment> attachments, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null) | |||
=> 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!"); | |||
/// <inheritdoc/> | |||
[Obsolete("Autocomplete interactions cannot have followups!", true)] | |||
public override 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) | |||
=> throw new NotSupportedException("Autocomplete interactions cannot be deferred!"); | |||
/// <inheritdoc/> | |||
[Obsolete("Autocomplete interactions cannot have followups!", true)] | |||
public override Task<RestFollowupMessage> 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!"); | |||
/// <inheritdoc/> | |||
[Obsolete("Autocomplete interactions cannot have followups!", true)] | |||
public override Task<RestFollowupMessage> 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!"); | |||
/// <inheritdoc/> | |||
[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 | |||
/// <inheritdoc/> | |||
@@ -41,5 +41,9 @@ namespace Discord.WebSocket | |||
//IDiscordInteraction | |||
/// <inheritdoc/> | |||
IDiscordInteractionData IDiscordInteraction.Data => Data; | |||
//IApplicationCommandInteraction | |||
/// <inheritdoc/> | |||
IApplicationCommandInteractionData IApplicationCommandInteraction.Data => Data; | |||
} | |||
} |
@@ -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 | |||
} | |||
/// <inheritdoc/> | |||
public override async Task RespondAsync( | |||
public override async Task<RestInteractionMessage> 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 | |||
} | |||
/// <inheritdoc/> | |||
public override async Task<RestFollowupMessage> FollowupWithFileAsync( | |||
public override Task<RestFollowupMessage> 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<API.AllowedMentions>.Unspecified, | |||
IsTTS = isTTS, | |||
Embeds = embeds.Select(x => x.ToModel()).ToArray(), | |||
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 | |||
}; | |||
return FollowupWithFileAsync(new FileAttachment(fileStream, fileName), text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options); | |||
} | |||
if (ephemeral) | |||
args.Flags = MessageFlags.Ephemeral; | |||
/// <inheritdoc/> | |||
public override Task<RestFollowupMessage> 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); | |||
} | |||
/// <inheritdoc/> | |||
public override async Task<RestFollowupMessage> FollowupWithFileAsync( | |||
string filePath, | |||
public override Task<RestFollowupMessage> 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); | |||
} | |||
/// <inheritdoc/> | |||
public override async Task<RestFollowupMessage> FollowupWithFilesAsync( | |||
IEnumerable<FileAttachment> attachments, | |||
string text = null, | |||
Embed[] embeds = null, | |||
bool isTTS = false, | |||
bool ephemeral = false, | |||
AllowedMentions allowedMentions = null, | |||
MessageComponent components = null, | |||
Embed embed = null, | |||
RequestOptions options = null) | |||
{ | |||
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<API.AllowedMentions>.Unspecified, | |||
IsTTS = isTTS, | |||
Embeds = embeds.Select(x => x.ToModel()).ToArray(), | |||
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 (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<API.Embed[]>.Unspecified, AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified, MessageComponents = components?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified }; | |||
return await InteractionHelper.SendFollowupAsync(Discord, args, Token, Channel, options).ConfigureAwait(false); | |||
} | |||
/// <summary> | |||
@@ -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 | |||
/// <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="components">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> | |||
/// <param name="options">The request options for this response.</param> | |||
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | |||
/// <exception cref="InvalidOperationException">The parameters provided were invalid or the token was invalid.</exception> | |||
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<RestInteractionMessage> 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); | |||
/// <summary> | |||
/// Sends a followup message for this interaction. | |||
@@ -146,14 +147,14 @@ namespace Discord.WebSocket | |||
/// <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="components">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> | |||
/// <param name="options">The request options for this response.</param> | |||
/// <returns> | |||
/// The sent message. | |||
/// </returns> | |||
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); | |||
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null); | |||
/// <summary> | |||
/// Sends a followup message for this interaction. | |||
@@ -165,14 +166,14 @@ namespace Discord.WebSocket | |||
/// <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="components">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> | |||
/// <param name="options">The request options for this response.</param> | |||
/// <returns> | |||
/// The sent message. | |||
/// </returns> | |||
public abstract Task<RestFollowupMessage> 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); | |||
/// <summary> | |||
/// Sends a followup message for this interaction. | |||
@@ -184,14 +185,52 @@ namespace Discord.WebSocket | |||
/// <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="components">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> | |||
/// <param name="options">The request options for this response.</param> | |||
/// <returns> | |||
/// The sent message. | |||
/// </returns> | |||
public abstract Task<RestFollowupMessage> 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<RestFollowupMessage> 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); | |||
/// <summary> | |||
/// Sends a followup message for this interaction. | |||
/// </summary> | |||
/// <param name="attachment">The attachment containing the file and description.</param> | |||
/// <param name="text">The text of the message to be sent.</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="components">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> | |||
/// A task that represents an asynchronous send operation for delivering the message. The task result | |||
/// contains the sent message. | |||
/// </returns> | |||
public abstract Task<RestFollowupMessage> 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); | |||
/// <summary> | |||
/// Sends a followup message for this interaction. | |||
/// </summary> | |||
/// <param name="attachments">A collection of attachments to upload.</param> | |||
/// <param name="text">The text of the message to be sent.</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="components">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> | |||
/// A task that represents an asynchronous send operation for delivering the message. The task result | |||
/// contains the sent message. | |||
/// </returns> | |||
public abstract Task<RestFollowupMessage> FollowupWithFilesAsync(IEnumerable<FileAttachment> attachments, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, | |||
AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null); | |||
/// <summary> | |||
/// 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. | |||
/// </returns> | |||
public abstract Task DeferAsync(bool ephemeral = false, RequestOptions options = null); | |||
#endregion | |||
#region IDiscordInteraction | |||
/// <inheritdoc/> | |||
async Task<IUserMessage> 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); | |||
/// <inheritdoc/> | |||
async Task<IUserMessage> 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); | |||
/// <inheritdoc/> | |||
async Task<IUserMessage> 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; | |||
/// <inheritdoc/> | |||
async Task<IUserMessage> IDiscordInteraction.GetOriginalResponseAsync(RequestOptions options) | |||
=> await GetOriginalResponseAsync(options).ConfigureAwait(false); | |||
/// <inheritdoc/> | |||
async Task<IUserMessage> IDiscordInteraction.ModifyOriginalResponseAsync(Action<MessageProperties> func, RequestOptions options) | |||
=> await ModifyOriginalResponseAsync(func, options).ConfigureAwait(false); | |||
/// <inheritdoc/> | |||
async Task<IUserMessage> 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); | |||
/// <inheritdoc/> | |||
async Task<IUserMessage> 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); | |||
/// <inheritdoc/> | |||
async Task<IUserMessage> 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); | |||
/// <inheritdoc/> | |||
async Task<IUserMessage> 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); | |||
/// <inheritdoc/> | |||
async Task<IUserMessage> 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); | |||
/// <inheritdoc/> | |||
async Task<IUserMessage> IDiscordInteraction.FollowupWithFilesAsync(IEnumerable<FileAttachment> 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 | |||
} | |||
} |
@@ -88,5 +88,11 @@ namespace Discord.WebSocket | |||
return base.Equals(obj); | |||
} | |||
/// <inheritdoc/> | |||
public override int GetHashCode() | |||
{ | |||
return base.GetHashCode(); | |||
} | |||
} | |||
} |