Browse Source

Refactor Interactions (#340)

* Refactor Interactions

* Remove ApplicationCommandException
pull/1958/head
Quin Lynch GitHub 3 years ago
parent
commit
8eeeb179c3
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 753 additions and 440 deletions
  1. +1
    -1
      src/Discord.Net.Core/Entities/Interactions/ContextMenus/IMessageCommandInteraction.cs
  2. +1
    -1
      src/Discord.Net.Core/Entities/Interactions/ContextMenus/IUserCommandInteraction.cs
  3. +19
    -0
      src/Discord.Net.Core/Entities/Interactions/IApplicationCommandInteraction.cs
  4. +67
    -15
      src/Discord.Net.Core/Entities/Interactions/IDiscordInteraction.cs
  5. +1
    -1
      src/Discord.Net.Core/Entities/Interactions/SlashCommands/ISlashCommandInteraction.cs
  6. +0
    -15
      src/Discord.Net.Core/Net/ApplicationCommandException.cs
  7. +2
    -2
      src/Discord.Net.Interactions/InteractionModuleBase.cs
  8. +1
    -1
      src/Discord.Net.Interactions/RestInteractionModuleBase.cs
  9. +3
    -0
      src/Discord.Net.Rest/API/Rest/UploadWebhookFileParams.cs
  10. +24
    -43
      src/Discord.Net.Rest/DiscordRestApiClient.cs
  11. +83
    -92
      src/Discord.Net.Rest/Entities/Interactions/CommandBase/RestCommandBase.cs
  12. +3
    -0
      src/Discord.Net.Rest/Entities/Interactions/ContextMenuCommands/MessageCommands/RestMessageCommand.cs
  13. +4
    -0
      src/Discord.Net.Rest/Entities/Interactions/ContextMenuCommands/UserCommands/RestUserCommand.cs
  14. +16
    -4
      src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs
  15. +76
    -57
      src/Discord.Net.Rest/Entities/Interactions/MessageComponents/RestMessageComponent.cs
  16. +95
    -27
      src/Discord.Net.Rest/Entities/Interactions/RestInteraction.cs
  17. +6
    -4
      src/Discord.Net.Rest/Entities/Interactions/RestPingInteraction.cs
  18. +13
    -23
      src/Discord.Net.Rest/Entities/Interactions/SlashCommands/RestAutocompleteInteraction.cs
  19. +4
    -0
      src/Discord.Net.Rest/Entities/Interactions/SlashCommands/RestSlashCommand.cs
  20. +1
    -1
      src/Discord.Net.Rest/Entities/Messages/RestFollowupMessage.cs
  21. +19
    -5
      src/Discord.Net.Rest/Entities/Messages/RestInteractionMessage.cs
  22. +43
    -0
      src/Discord.Net.Rest/Extensions/EntityExtensions.cs
  23. +4
    -0
      src/Discord.Net.WebSocket/Entities/Interaction/ContextMenuCommands/MessageCommands/SocketMessageCommand.cs
  24. +4
    -0
      src/Discord.Net.WebSocket/Entities/Interaction/ContextMenuCommands/UserCommands/SocketUserCommand.cs
  25. +85
    -48
      src/Discord.Net.WebSocket/Entities/Interaction/MessageComponents/SocketMessageComponent.cs
  26. +13
    -24
      src/Discord.Net.WebSocket/Entities/Interaction/SlashCommands/SocketAutocompleteInteraction.cs
  27. +4
    -0
      src/Discord.Net.WebSocket/Entities/Interaction/SlashCommands/SocketSlashCommand.cs
  28. +82
    -47
      src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBase.cs
  29. +73
    -29
      src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs
  30. +6
    -0
      src/Discord.Net.WebSocket/Entities/Stickers/SocketSticker.cs

+ 1
- 1
src/Discord.Net.Core/Entities/Interactions/ContextMenus/IMessageCommandInteraction.cs View File

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


+ 1
- 1
src/Discord.Net.Core/Entities/Interactions/ContextMenus/IUserCommandInteraction.cs View File

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


+ 19
- 0
src/Discord.Net.Core/Entities/Interactions/IApplicationCommandInteraction.cs View File

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

+ 67
- 15
src/Discord.Net.Core/Entities/Interactions/IDiscordInteraction.cs View File

@@ -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);
}


+ 1
- 1
src/Discord.Net.Core/Entities/Interactions/SlashCommands/ISlashCommandInteraction.cs View File

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


+ 0
- 15
src/Discord.Net.Core/Net/ApplicationCommandException.cs View File

@@ -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())
{
}
}
}

+ 2
- 2
src/Discord.Net.Interactions/InteractionModuleBase.cs View File

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


+ 1
- 1
src/Discord.Net.Interactions/RestInteractionModuleBase.cs View File

@@ -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);
}
}
}

+ 3
- 0
src/Discord.Net.Rest/API/Rest/UploadWebhookFileParams.cs View File

@@ -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();



+ 24
- 43
src/Discord.Net.Rest/DiscordRestApiClient.cs View File

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


+ 83
- 92
src/Discord.Net.Rest/Entities/Interactions/CommandBase/RestCommandBase.cs View File

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


+ 3
- 0
src/Discord.Net.Rest/Entities/Interactions/ContextMenuCommands/MessageCommands/RestMessageCommand.cs View File

@@ -41,5 +41,8 @@ namespace Discord.Rest
//IMessageCommandInteraction
/// <inheritdoc/>
IMessageCommandInteractionData IMessageCommandInteraction.Data => Data;
//IApplicationCommandInteraction
/// <inheritdoc/>
IApplicationCommandInteractionData IApplicationCommandInteraction.Data => Data;
}
}

+ 4
- 0
src/Discord.Net.Rest/Entities/Interactions/ContextMenuCommands/UserCommands/RestUserCommand.cs View File

@@ -44,5 +44,9 @@ namespace Discord.Rest
//IUserCommandInteractionData
/// <inheritdoc/>
IUserCommandInteractionData IUserCommandInteraction.Data => Data;

//IApplicationCommandInteraction
/// <inheritdoc/>
IApplicationCommandInteractionData IApplicationCommandInteraction.Data => Data;
}
}

+ 16
- 4
src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs View File

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


+ 76
- 57
src/Discord.Net.Rest/Entities/Interactions/MessageComponents/RestMessageComponent.cs View File

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


+ 95
- 27
src/Discord.Net.Rest/Entities/Interactions/RestInteraction.cs View File

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

+ 6
- 4
src/Discord.Net.Rest/Entities/Interactions/RestPingInteraction.cs View File

@@ -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();
}
}

+ 13
- 23
src/Discord.Net.Rest/Entities/Interactions/SlashCommands/RestAutocompleteInteraction.cs View File

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


+ 4
- 0
src/Discord.Net.Rest/Entities/Interactions/SlashCommands/RestSlashCommand.cs View File

@@ -44,5 +44,9 @@ namespace Discord.Rest
//ISlashCommandInteraction
/// <inheritdoc/>
IApplicationCommandInteractionData ISlashCommandInteraction.Data => Data;

//IApplicationCommandInteraction
/// <inheritdoc/>
IApplicationCommandInteractionData IApplicationCommandInteraction.Data => Data;
}
}

+ 1
- 1
src/Discord.Net.Rest/Entities/Messages/RestFollowupMessage.cs View File

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


+ 19
- 5
src/Discord.Net.Rest/Entities/Messages/RestInteractionMessage.cs View File

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


+ 43
- 0
src/Discord.Net.Rest/Extensions/EntityExtensions.cs View File

@@ -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,
};
}
}
}

+ 4
- 0
src/Discord.Net.WebSocket/Entities/Interaction/ContextMenuCommands/MessageCommands/SocketMessageCommand.cs View File

@@ -41,5 +41,9 @@ namespace Discord.WebSocket
//IDiscordInteraction
/// <inheritdoc/>
IDiscordInteractionData IDiscordInteraction.Data => Data;

//IApplicationCommandInteraction
/// <inheritdoc/>
IApplicationCommandInteractionData IApplicationCommandInteraction.Data => Data;
}
}

+ 4
- 0
src/Discord.Net.WebSocket/Entities/Interaction/ContextMenuCommands/UserCommands/SocketUserCommand.cs View File

@@ -41,5 +41,9 @@ namespace Discord.WebSocket
//IDiscordInteraction
/// <inheritdoc/>
IDiscordInteractionData IDiscordInteraction.Data => Data;

//IApplicationCommandInteraction
/// <inheritdoc/>
IApplicationCommandInteractionData IApplicationCommandInteraction.Data => Data;
}
}

+ 85
- 48
src/Discord.Net.WebSocket/Entities/Interaction/MessageComponents/SocketMessageComponent.cs View File

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


+ 13
- 24
src/Discord.Net.WebSocket/Entities/Interaction/SlashCommands/SocketAutocompleteInteraction.cs View File

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


+ 4
- 0
src/Discord.Net.WebSocket/Entities/Interaction/SlashCommands/SocketSlashCommand.cs View File

@@ -41,5 +41,9 @@ namespace Discord.WebSocket
//IDiscordInteraction
/// <inheritdoc/>
IDiscordInteractionData IDiscordInteraction.Data => Data;

//IApplicationCommandInteraction
/// <inheritdoc/>
IApplicationCommandInteractionData IApplicationCommandInteraction.Data => Data;
}
}

+ 82
- 47
src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBase.cs View File

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


+ 73
- 29
src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs View File

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

+ 6
- 0
src/Discord.Net.WebSocket/Entities/Stickers/SocketSticker.cs View File

@@ -88,5 +88,11 @@ namespace Discord.WebSocket

return base.Equals(obj);
}

/// <inheritdoc/>
public override int GetHashCode()
{
return base.GetHashCode();
}
}
}

Loading…
Cancel
Save