Browse Source

Add RestFollowupMessage and RestInteractionMessage. Guild command permission routes Bug fixes and general improvements

pull/1717/head
quin lynch 4 years ago
parent
commit
7e9ee8bb1e
21 changed files with 642 additions and 23 deletions
  1. +5
    -0
      src/Discord.Net.Core/Entities/Interactions/ApplicationCommandProperties.cs
  2. +1
    -1
      src/Discord.Net.Core/Entities/Interactions/IDiscordInteraction.cs
  3. +3
    -3
      src/Discord.Net.Core/Entities/Interactions/InteractionResponseType.cs
  4. +5
    -0
      src/Discord.Net.Core/Entities/Interactions/SlashCommandCreationProperties.cs
  5. +62
    -0
      src/Discord.Net.Core/Entities/Permissions/ApplicationCommandPermissions.cs
  6. +42
    -0
      src/Discord.Net.Core/Entities/Permissions/GuildApplicationCommandPermissions.cs
  7. +68
    -0
      src/Discord.Net.Core/Net/ApplicationCommandException.cs
  8. +9
    -1
      src/Discord.Net.Core/Net/HttpException.cs
  9. +21
    -0
      src/Discord.Net.Rest/API/Common/ApplicationCommandPermissions.cs
  10. +24
    -0
      src/Discord.Net.Rest/API/Common/GuildApplicationCommandPermissions.cs
  11. +18
    -0
      src/Discord.Net.Rest/API/Rest/ModifyGuildApplicationCommandPermissions.cs
  12. +1
    -1
      src/Discord.Net.Rest/API/Rest/ModifyInteractionResponseParams.cs
  13. +1
    -0
      src/Discord.Net.Rest/ClientHelper.cs
  14. +122
    -7
      src/Discord.Net.Rest/DiscordRestApiClient.cs
  15. +86
    -6
      src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs
  16. +3
    -0
      src/Discord.Net.Rest/Entities/Interactions/RestGuildCommand.cs
  17. +82
    -0
      src/Discord.Net.Rest/Entities/Messages/RestFollowupMessage.cs
  18. +81
    -0
      src/Discord.Net.Rest/Entities/Messages/RestInteractionMessage.cs
  19. +3
    -1
      src/Discord.Net.Rest/Net/Queue/RequestQueueBucket.cs
  20. +1
    -1
      src/Discord.Net.WebSocket/DiscordSocketConfig.cs
  21. +4
    -2
      src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs

+ 5
- 0
src/Discord.Net.Core/Entities/Interactions/ApplicationCommandProperties.cs View File

@@ -26,5 +26,10 @@ namespace Discord
/// Gets or sets the options for this command.
/// </summary>
public Optional<List<ApplicationCommandOptionProperties>> Options { get; set; }

/// <summary>
/// Whether the command is enabled by default when the app is added to a guild. Default is <see langword="true"/>
/// </summary>
public Optional<bool> DefaultPermission { get; set; }
}
}

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

@@ -18,7 +18,7 @@ namespace Discord
/// <summary>
/// The id of the interaction.
/// </summary>
ulong Id { get; }
new ulong Id { get; }

/// <summary>
/// The type of this <see cref="IDiscordInteraction"/>.


+ 3
- 3
src/Discord.Net.Core/Entities/Interactions/InteractionResponseType.cs View File

@@ -11,7 +11,7 @@ namespace Discord
/// </summary>
/// <remarks>
/// After receiving an interaction, you must respond to acknowledge it. You can choose to respond with a message immediately using <see cref="ChannelMessageWithSource"/>
/// or you can choose to send a deferred response with <see cref="ACKWithSource"/>. If choosing a deferred response, the user will see a loading state for the interaction,
/// or you can choose to send a deferred response with <see cref="DeferredChannelMessageWithSource"/>. If choosing a deferred response, the user will see a loading state for the interaction,
/// and you'll have up to 15 minutes to edit the original deferred response using Edit Original Interaction Response.
/// You can read more about Response types <see href="https://discord.com/developers/docs/interactions/slash-commands#interaction-response">Here</see>
/// </remarks>
@@ -22,13 +22,13 @@ namespace Discord
/// </summary>
Pong = 1,

[Obsolete("This response type has been depricated by discord. Either use ChannelMessageWithSource or ACKWithSource", true)]
[Obsolete("This response type has been depricated by discord. Either use ChannelMessageWithSource or DeferredChannelMessageWithSource", true)]
/// <summary>
/// ACK a command without sending a message, eating the user's input.
/// </summary>
Acknowledge = 2,

[Obsolete("This response type has been depricated by discord. Either use ChannelMessageWithSource or ACKWithSource", true)]
[Obsolete("This response type has been depricated by discord. Either use ChannelMessageWithSource or DeferredChannelMessageWithSource", true)]
/// <summary>
/// Respond with a message, showing the user's input.
/// </summary>


+ 5
- 0
src/Discord.Net.Core/Entities/Interactions/SlashCommandCreationProperties.cs View File

@@ -26,5 +26,10 @@ namespace Discord
/// Gets or sets the options for this command.
/// </summary>
public Optional<List<ApplicationCommandOptionProperties>> Options { get; set; }

/// <summary>
/// Whether the command is enabled by default when the app is added to a guild. Default is <see langword="true"/>
/// </summary>
public Optional<bool> DefaultPermission { get; set; }
}
}

+ 62
- 0
src/Discord.Net.Core/Entities/Permissions/ApplicationCommandPermissions.cs View File

@@ -0,0 +1,62 @@
namespace Discord
{
/// <summary>
/// Application command permissions allow you to enable or disable commands for specific users or roles within a guild.
/// </summary>
public class ApplicationCommandPermission
{
/// <summary>
/// The id of the role or user.
/// </summary>
public ulong Id { get; }

/// <summary>
/// The target of this permission.
/// </summary>
public PermissionTarget Type { get; }

/// <summary>
/// <see langword="true"/> to allow, otherwise <see langword="false"/>.
/// </summary>
public bool Value { get; }

internal ApplicationCommandPermission() { }

/// <summary>
/// Creates a new <see cref="ApplicationCommandPermission"/>.
/// </summary>
/// <param name="targetId">The id you want to target this permission value for.</param>
/// <param name="targetType">The type of the <b>targetId</b> parameter.</param>
/// <param name="allow">The value of this permission.</param>
public ApplicationCommandPermission(ulong targetId, PermissionTarget targetType, bool allow)
{
this.Id = targetId;
this.Type = targetType;
this.Value = allow;
}

/// <summary>
/// Creates a new <see cref="ApplicationCommandPermission"/> targeting <see cref="PermissionTarget.User"/>.
/// </summary>
/// <param name="target">The user you want to target this permission value for.</param>
/// <param name="allow">The value of this permission.</param>
public ApplicationCommandPermission(IUser target, bool allow)
{
this.Id = target.Id;
this.Value = allow;
this.Type = PermissionTarget.User;
}

/// <summary>
/// Creates a new <see cref="ApplicationCommandPermission"/> targeting <see cref="PermissionTarget.Role"/>.
/// </summary>
/// <param name="target">The role you want to target this permission value for.</param>
/// <param name="allow">The value of this permission.</param>
public ApplicationCommandPermission(IRole target, bool allow)
{
this.Id = target.Id;
this.Value = allow;
this.Type = PermissionTarget.Role;
}
}
}

+ 42
- 0
src/Discord.Net.Core/Entities/Permissions/GuildApplicationCommandPermissions.cs View File

@@ -0,0 +1,42 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Discord
{
/// <summary>
/// Returned when fetching the permissions for a command in a guild.
/// </summary>
public class GuildApplicationCommandPermissions
{
/// <summary>
/// The id of the command.
/// </summary>
public ulong Id { get; }

/// <summary>
/// The id of the application the command belongs to.
/// </summary>
public ulong ApplicationId { get; }

/// <summary>
/// The id of the guild.
/// </summary>
public ulong GuildId { get; }

/// <summary>
/// The permissions for the command in the guild.
/// </summary>
public IReadOnlyCollection<ApplicationCommandPermission> Permissions { get; }

internal GuildApplicationCommandPermissions(ulong id, ulong appId, ulong guildId, List<ApplicationCommandPermission> permissions)
{
this.Id = id;
this.ApplicationId = appId;
this.GuildId = guildId;
this.Permissions = permissions.ToReadOnlyCollection();
}
}
}

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

@@ -0,0 +1,68 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Discord.Net
{
public class ApplicationCommandException : Exception
{
/// <summary>
/// Gets the JSON error code returned by Discord.
/// </summary>
/// <returns>
/// A
/// <see href="https://discord.com/developers/docs/topics/opcodes-and-status-codes#json">JSON error code</see>
/// from Discord, or <c>null</c> if none.
/// </returns>
public int? DiscordCode { get; }

/// <summary>
/// Gets the reason of the exception.
/// </summary>
public string Reason { get; }

/// <summary>
/// Gets the request object used to send the request.
/// </summary>
public IRequest Request { get; }

/// <summary>
/// The error object returned from discord.
/// </summary>
/// <remarks>
/// Note: This object can be null if discord didn't provide it.
/// </remarks>
public object Error { get; }

/// <summary>
/// The request json used to create the application command. This is useful for checking your commands for any format errors.
/// </summary>
public string RequestJson { get; }

/// <summary>
/// The underlying <see cref="HttpException"/> that caused this exception to be thrown.
/// </summary>
public HttpException InnerHttpException { get; }
/// <summary>
/// Initializes a new instance of the <see cref="ApplicationCommandException" /> class.
/// </summary>
/// <param name="request">The request that was sent prior to the exception.</param>
/// <param name="requestJson"></param>
/// <param name="httpError"></param>
/// <param name="discordCode">The Discord status code returned.</param>
/// <param name="reason">The reason behind the exception.</param>
/// <param name="errors"></param>
public ApplicationCommandException(string requestJson, HttpException httpError)
: base("The application command failed to be created!", httpError)
{
Request = httpError.Request;
DiscordCode = httpError.DiscordCode;
Reason = httpError.Reason;
Error = httpError.Error;
RequestJson = requestJson;
InnerHttpException = httpError;
}
}
}

+ 9
- 1
src/Discord.Net.Core/Net/HttpException.cs View File

@@ -34,6 +34,13 @@ namespace Discord.Net
/// Gets the request object used to send the request.
/// </summary>
public IRequest Request { get; }
/// <summary>
/// The error object returned from discord.
/// </summary>
/// <remarks>
/// Note: This object can be null if discord didn't provide it.
/// </remarks>
public object Error { get; }

/// <summary>
/// Initializes a new instance of the <see cref="HttpException" /> class.
@@ -42,13 +49,14 @@ namespace Discord.Net
/// <param name="request">The request that was sent prior to the exception.</param>
/// <param name="discordCode">The Discord status code returned.</param>
/// <param name="reason">The reason behind the exception.</param>
public HttpException(HttpStatusCode httpCode, IRequest request, int? discordCode = null, string reason = null)
public HttpException(HttpStatusCode httpCode, IRequest request, int? discordCode = null, string reason = null, object errors = null)
: base(CreateMessage(httpCode, discordCode, reason))
{
HttpCode = httpCode;
Request = request;
DiscordCode = discordCode;
Reason = reason;
Error = errors;
}

private static string CreateMessage(HttpStatusCode httpCode, int? discordCode = null, string reason = null)


+ 21
- 0
src/Discord.Net.Rest/API/Common/ApplicationCommandPermissions.cs View File

@@ -0,0 +1,21 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Discord.API
{
public class ApplicationCommandPermissions
{
[JsonProperty("id")]
public ulong Id { get; set; }

[JsonProperty("type")]
public PermissionTarget Type { get; set; }

[JsonProperty("permission")]
public bool Permission { get; set; }
}
}

+ 24
- 0
src/Discord.Net.Rest/API/Common/GuildApplicationCommandPermissions.cs View File

@@ -0,0 +1,24 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Discord.API
{
public class GuildApplicationCommandPermission
{
[JsonProperty("id")]
public ulong Id { get; }

[JsonProperty("application_id")]
public ulong ApplicationId { get; }

[JsonProperty("guild_id")]
public ulong GuildId { get; }

[JsonProperty("permissions")]
public API.ApplicationCommandPermissions[] Permissions { get; set; }
}
}

+ 18
- 0
src/Discord.Net.Rest/API/Rest/ModifyGuildApplicationCommandPermissions.cs View File

@@ -0,0 +1,18 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Discord.API.Rest
{
internal class ModifyGuildApplicationCommandPermissions
{
[JsonProperty("id")]
public ulong Id { get; set; }

[JsonProperty("permissions")]
public ApplicationCommandPermission[] Permissions { get; set; }
}
}

+ 1
- 1
src/Discord.Net.Rest/API/Rest/ModifyInteractionResponseParams.cs View File

@@ -10,7 +10,7 @@ namespace Discord.API.Rest
internal class ModifyInteractionResponseParams
{
[JsonProperty("content")]
public string Content { get; set; }
public Optional<string> Content { get; set; }

[JsonProperty("embeds")]
public Optional<Embed[]> Embeds { get; set; }


+ 1
- 0
src/Discord.Net.Rest/ClientHelper.cs View File

@@ -211,6 +211,7 @@ namespace Discord.Rest

return response.Select(x => RestGlobalCommand.Create(client, x)).ToArray();
}

public static async Task<IReadOnlyCollection<RestGuildCommand>> GetGuildApplicationCommands(BaseDiscordClient client, ulong guildId, RequestOptions options)
{
var response = await client.ApiClient.GetGuildApplicationCommandAsync(guildId, options).ConfigureAwait(false);


+ 122
- 7
src/Discord.Net.Rest/DiscordRestApiClient.cs View File

@@ -804,7 +804,21 @@ namespace Discord.API

options = RequestOptions.CreateOrClone(options);

return await SendJsonAsync<ApplicationCommand>("POST", () => $"applications/{this.CurrentUserId}/commands", command, new BucketIds(), options: options).ConfigureAwait(false);
try
{
return await SendJsonAsync<ApplicationCommand>("POST", () => $"applications/{this.CurrentUserId}/commands", command, new BucketIds(), options: options).ConfigureAwait(false);
}
catch (HttpException x)
{
if (x.HttpCode == HttpStatusCode.BadRequest)
{
var json = (x.Request as JsonRestRequest).Json;
throw new ApplicationCommandException(json, x);
}

// Re-throw the http exception
throw;
}
}
public async Task<ApplicationCommand> ModifyGlobalApplicationCommandAsync(ModifyApplicationCommandParams command, ulong commandId, RequestOptions options = null)
{
@@ -823,7 +837,21 @@ namespace Discord.API

options = RequestOptions.CreateOrClone(options);

return await SendJsonAsync<ApplicationCommand>("PATCH", () => $"applications/{this.CurrentUserId}/commands/{commandId}", command, new BucketIds(), options: options).ConfigureAwait(false);
try
{
return await SendJsonAsync<ApplicationCommand>("PATCH", () => $"applications/{this.CurrentUserId}/commands/{commandId}", command, new BucketIds(), options: options).ConfigureAwait(false);
}
catch (HttpException x)
{
if (x.HttpCode == HttpStatusCode.BadRequest)
{
var json = (x.Request as JsonRestRequest).Json;
throw new ApplicationCommandException(json, x);
}

// Re-throw the http exception
throw;
}
}
public async Task DeleteGlobalApplicationCommandAsync(ulong commandId, RequestOptions options = null)
{
@@ -852,7 +880,21 @@ namespace Discord.API

var bucket = new BucketIds(guildId: guildId);

return await SendJsonAsync<ApplicationCommand>("POST", () => $"applications/{this.CurrentUserId}/guilds/{guildId}/commands", command, bucket, options: options).ConfigureAwait(false);
try
{
return await SendJsonAsync<ApplicationCommand>("POST", () => $"applications/{this.CurrentUserId}/guilds/{guildId}/commands", command, bucket, options: options).ConfigureAwait(false);
}
catch (HttpException x)
{
if (x.HttpCode == HttpStatusCode.BadRequest)
{
var json = (x.Request as JsonRestRequest).Json;
throw new ApplicationCommandException(json, x);
}

// Re-throw the http exception
throw;
}
}
public async Task<ApplicationCommand> ModifyGuildApplicationCommandAsync(ModifyApplicationCommandParams command, ulong guildId, ulong commandId, RequestOptions options = null)
{
@@ -873,7 +915,21 @@ namespace Discord.API

var bucket = new BucketIds(guildId: guildId);

return await SendJsonAsync<ApplicationCommand>("PATCH", () => $"applications/{this.CurrentUserId}/guilds/{guildId}/commands/{commandId}", command, bucket, options: options).ConfigureAwait(false);
try
{
return await SendJsonAsync<ApplicationCommand>("PATCH", () => $"applications/{this.CurrentUserId}/guilds/{guildId}/commands/{commandId}", command, bucket, options: options).ConfigureAwait(false);
}
catch (HttpException x)
{
if (x.HttpCode == HttpStatusCode.BadRequest)
{
var json = (x.Request as JsonRestRequest).Json;
throw new ApplicationCommandException(json, x);
}

// Re-throw the http exception
throw;
}
}
public async Task DeleteGuildApplicationCommandAsync(ulong guildId, ulong commandId, RequestOptions options = null)
{
@@ -894,6 +950,14 @@ namespace Discord.API

await SendJsonAsync("POST", () => $"interactions/{interactionId}/{interactionToken}/callback", response, new BucketIds(), options: options);
}
public async Task<Message> GetInteractionResponse(string interactionToken, RequestOptions options = null)
{
Preconditions.NotNullOrEmpty(interactionToken, nameof(interactionToken));

options = RequestOptions.CreateOrClone(options);

return await SendAsync<Message>("GET", $"webhooks/{this.CurrentUserId}/{interactionToken}/messages/@original").ConfigureAwait(false);
}
public async Task ModifyInteractionResponse(ModifyInteractionResponseParams args, string interactionToken, RequestOptions options = null)
{
options = RequestOptions.CreateOrClone(options);
@@ -920,13 +984,14 @@ namespace Discord.API
return await SendJsonAsync<Message>("POST", () => $"webhooks/{CurrentUserId}/{token}?wait=true", args, new BucketIds(), options: options).ConfigureAwait(false);
}

public async Task<Message> ModifyInteractionFollowupMessage(CreateWebhookMessageParams args, ulong id, string token, RequestOptions options = null)
public async Task<Message> ModifyInteractionFollowupMessage(ModifyInteractionResponseParams args, ulong id, string token, RequestOptions options = null)
{
Preconditions.NotNull(args, nameof(args));
Preconditions.NotEqual(id, 0, nameof(id));

if (args.Content?.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));
if(args.Content.IsSpecified)
if (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);

@@ -942,6 +1007,56 @@ namespace Discord.API
await SendAsync("DELETE", () => $"webhooks/{CurrentUserId}/{token}/messages/{id}", new BucketIds(), options: options).ConfigureAwait(false);
}

// Application Command permissions
public async Task<GuildApplicationCommandPermission[]> GetGuildApplicationCommandPermissions(ulong guildId, RequestOptions options = null)
{
Preconditions.NotEqual(guildId, 0, nameof(guildId));

options = RequestOptions.CreateOrClone(options);

return await SendAsync<GuildApplicationCommandPermission[]>("GET", () => $"/applications/{this.CurrentUserId}/guilds/{guildId}/commands/permissions", new BucketIds(), options: options).ConfigureAwait(false);
}

public async Task<GuildApplicationCommandPermission> GetGuildApplicationCommandPermission(ulong guildId, ulong commandId, RequestOptions options = null)
{
Preconditions.NotEqual(guildId, 0, nameof(guildId));
Preconditions.NotEqual(commandId, 0, nameof(commandId));

options = RequestOptions.CreateOrClone(options);

return await SendAsync<GuildApplicationCommandPermission>("GET", () => $"/applications/{this.CurrentUserId}/guilds/{guildId}/commands/{commandId}/permissions", new BucketIds(), options: options).ConfigureAwait(false);
}

public async Task ModifyApplicationCommandPermissions(ApplicationCommandPermissions[] permissions, ulong guildId, ulong commandId, RequestOptions options = null)
{
Preconditions.NotEqual(guildId, 0, nameof(guildId));
Preconditions.NotEqual(commandId, 0, nameof(commandId));

options = RequestOptions.CreateOrClone(options);

await SendJsonAsync("PUT", () => $"/applications/{this.CurrentUserId}/guilds/{guildId}/commands/{commandId}/permissions", permissions, new BucketIds(), options: options).ConfigureAwait(false);
}

public async Task BatchModifyApplicationCommandPermissions(ModifyGuildApplicationCommandPermissions[] permissions, ulong guildId, RequestOptions options = null)
{
Preconditions.NotEqual(guildId, 0, nameof(guildId));
Preconditions.NotNull(permissions, nameof(permissions));

options = RequestOptions.CreateOrClone(options);

await SendJsonAsync("PUT", () => $"/applications/{this.CurrentUserId}/guilds/{guildId}/commands/premissions", permissions, new BucketIds(), options: options).ConfigureAwait(false);
}

public async Task BulkOverrideGuildApplicationCommand(API.ApplicationCommand[] commands, ulong guildId, RequestOptions options = null)
{
Preconditions.NotEqual(guildId, 0, nameof(guildId));
Preconditions.NotNull(commands, nameof(commands));

options = RequestOptions.CreateOrClone(options);

await SendJsonAsync("PUT", () => $"/applications/{this.CurrentUserId}/guilds/{guildId}/commands", commands, new BucketIds(), options: options).ConfigureAwait(false);
}

//Guilds
public async Task<Guild> GetGuildAsync(ulong guildId, bool withCounts, RequestOptions options = null)
{


+ 86
- 6
src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs View File

@@ -10,12 +10,25 @@ namespace Discord.Rest
{
internal static class InteractionHelper
{
internal static async Task<RestUserMessage> SendFollowupAsync(BaseDiscordClient client, API.Rest.CreateWebhookMessageParams args,
internal static async Task<RestInteractionMessage> SendInteractionResponse(BaseDiscordClient client, IMessageChannel channel, InteractionResponse response,
ulong interactionId, string interactionToken, RequestOptions options = null)
{
await client.ApiClient.CreateInteractionResponse(response, interactionId, interactionToken, options).ConfigureAwait(false);

// get the original message
var msg = await client.ApiClient.GetInteractionResponse(interactionToken).ConfigureAwait(false);

var entity = RestInteractionMessage.Create(client, msg, interactionToken, channel);

return entity;
}

internal static async Task<RestFollowupMessage> SendFollowupAsync(BaseDiscordClient client, API.Rest.CreateWebhookMessageParams args,
string token, IMessageChannel channel, RequestOptions options = null)
{
var model = await client.ApiClient.CreateInteractionFollowupMessage(args, token, options).ConfigureAwait(false);

var entity = RestUserMessage.Create(client, channel, client.CurrentUser, model);
RestFollowupMessage entity = RestFollowupMessage.Create(client, model, token, channel);
return entity;
}
@@ -44,7 +57,10 @@ namespace Discord.Rest
Description = args.Description,
Options = args.Options.IsSpecified
? args.Options.Value.Select(x => new Discord.API.ApplicationCommandOption(x)).ToArray()
: Optional<Discord.API.ApplicationCommandOption[]>.Unspecified
: Optional<Discord.API.ApplicationCommandOption[]>.Unspecified,
DefaultPermission = args.DefaultPermission.IsSpecified
? args.DefaultPermission.Value
: Optional<bool>.Unspecified
};

var cmd = await client.ApiClient.CreateGlobalApplicationCommandAsync(model, options).ConfigureAwait(false);
@@ -68,7 +84,10 @@ namespace Discord.Rest
Description = args.Description,
Options = args.Options.IsSpecified
? args.Options.Value.Select(x => new Discord.API.ApplicationCommandOption(x)).ToArray()
: Optional<Discord.API.ApplicationCommandOption[]>.Unspecified
: Optional<Discord.API.ApplicationCommandOption[]>.Unspecified,
DefaultPermission = args.DefaultPermission.IsSpecified
? args.DefaultPermission.Value
: Optional<bool>.Unspecified
};

var msg = await client.ApiClient.ModifyGlobalApplicationCommandAsync(model, command.Id, options).ConfigureAwait(false);
@@ -119,7 +138,10 @@ namespace Discord.Rest
Description = args.Description,
Options = args.Options.IsSpecified
? args.Options.Value.Select(x => new Discord.API.ApplicationCommandOption(x)).ToArray()
: Optional<Discord.API.ApplicationCommandOption[]>.Unspecified
: Optional<Discord.API.ApplicationCommandOption[]>.Unspecified,
DefaultPermission = args.DefaultPermission.IsSpecified
? args.DefaultPermission.Value
: Optional<bool>.Unspecified
};

var cmd = await client.ApiClient.CreateGuildApplicationCommandAsync(model, guildId, options).ConfigureAwait(false);
@@ -143,7 +165,10 @@ namespace Discord.Rest
Description = args.Description,
Options = args.Options.IsSpecified
? args.Options.Value.Select(x => new Discord.API.ApplicationCommandOption(x)).ToArray()
: Optional<Discord.API.ApplicationCommandOption[]>.Unspecified
: Optional<Discord.API.ApplicationCommandOption[]>.Unspecified,
DefaultPermission = args.DefaultPermission.IsSpecified
? args.DefaultPermission.Value
: Optional<bool>.Unspecified
};

var msg = await client.ApiClient.ModifyGuildApplicationCommandAsync(model, command.GuildId, command.Id, options).ConfigureAwait(false);
@@ -158,5 +183,60 @@ namespace Discord.Rest

await client.ApiClient.DeleteGuildApplicationCommandAsync(command.GuildId, command.Id, options).ConfigureAwait(false);
}

internal static async Task<Discord.API.Message> ModifyFollowupMessage(BaseDiscordClient client, RestFollowupMessage message, Action<MessageProperties> func,
RequestOptions options = null)
{
var args = new MessageProperties();
func(args);

bool hasText = args.Content.IsSpecified ? !string.IsNullOrEmpty(args.Content.Value) : !string.IsNullOrEmpty(message.Content);
bool hasEmbed = args.Embed.IsSpecified ? args.Embed.Value != null : message.Embeds.Any();
if (!hasText && !hasEmbed)
Preconditions.NotNullOrEmpty(args.Content.IsSpecified ? args.Content.Value : string.Empty, nameof(args.Content));

var apiArgs = new API.Rest.ModifyInteractionResponseParams
{
Content = args.Content,
Embeds = args.Embed.IsSpecified ? new API.Embed[] { args.Embed.Value.ToModel() } : Optional.Create<API.Embed[]>()
};

return await client.ApiClient.ModifyInteractionFollowupMessage(apiArgs, message.Id, message.Token, options).ConfigureAwait(false);
}

internal static async Task DeleteFollowupMessage(BaseDiscordClient client, RestFollowupMessage message, RequestOptions options = null)
=> await client.ApiClient.DeleteInteractionFollowupMessage(message.Id, message.Token, options);

internal static async Task<Discord.API.Message> ModifyInteractionResponse(BaseDiscordClient client, RestInteractionMessage message, Action<MessageProperties> func,
RequestOptions options = null)
{
var args = new MessageProperties();
func(args);

bool hasText = args.Content.IsSpecified ? !string.IsNullOrEmpty(args.Content.Value) : !string.IsNullOrEmpty(message.Content);
bool hasEmbed = args.Embed.IsSpecified ? args.Embed.Value != null : message.Embeds.Any();
if (!hasText && !hasEmbed)
Preconditions.NotNullOrEmpty(args.Content.IsSpecified ? args.Content.Value : string.Empty, nameof(args.Content));

var apiArgs = new API.Rest.ModifyInteractionResponseParams
{
Content = args.Content,
Embeds = args.Embed.IsSpecified ? new API.Embed[] { args.Embed.Value.ToModel() } : Optional.Create<API.Embed[]>()
};

return await client.ApiClient.ModifyInteractionFollowupMessage(apiArgs, message.Id, message.Token, options).ConfigureAwait(false);
}

internal static async Task DeletedInteractionResponse(BaseDiscordClient client, RestInteractionMessage message, RequestOptions options = null)
=> await client.ApiClient.DeleteInteractionFollowupMessage(message.Id, message.Token, options);

// Guild permissions
internal static async Task<IReadOnlyCollection<Discord.GuildApplicationCommandPermissions>> GetCommandGuildPermissions(BaseDiscordClient client,
RestGuildCommand command)
{
// TODO
return null;
}

}
}

+ 3
- 0
src/Discord.Net.Rest/Entities/Interactions/RestGuildCommand.cs View File

@@ -45,5 +45,8 @@ namespace Discord.Rest
/// </returns>
public async Task<RestGuildCommand> ModifyAsync(Action<ApplicationCommandProperties> func, RequestOptions options = null)
=> await InteractionHelper.ModifyGuildCommand(Discord, this, func, options).ConfigureAwait(false);

public async Task<IReadOnlyCollection<Discord.GuildApplicationCommandPermissions>> GetCommandPermissions()
=> await InteractionHelper.GetCommandGuildPermissions(Discord, this);
}
}

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

@@ -0,0 +1,82 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
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.
/// </summary>
public class RestFollowupMessage : RestUserMessage
{
// Token used to delete/modify this followup message
internal string Token { get; }

internal RestFollowupMessage(BaseDiscordClient discord, ulong id, IUser author, string token, IMessageChannel channel)
: base(discord, id, channel, author, MessageSource.Bot)
{
this.Token = token;
}

internal static RestFollowupMessage Create(BaseDiscordClient discord, Model model, string token, IMessageChannel channel)
{
var entity = new RestFollowupMessage(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)
{
base.Update(model);
}

/// <summary>
/// Deletes this object and all of it's childern.
/// </summary>
/// <returns>A task that represents the asynchronous delete operation.</returns>
public Task DeleteAsync()
=> InteractionHelper.DeleteFollowupMessage(Discord, this);

/// <summary>
/// Modifies this interaction followup message.
/// </summary>
/// <remarks>
/// This method modifies this message with the specified properties. To see an example of this
/// method and what properties are available, please refer to <see cref="MessageProperties"/>.
/// </remarks>
/// <example>
/// <para>The following example replaces the content of the message with <c>Hello World!</c>.</para>
/// <code language="cs">
/// await msg.ModifyAsync(x =&gt; x.Content = "Hello World!");
/// </code>
/// </example>
/// <param name="func">A delegate containing the properties to modify the message with.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous modification operation.
/// </returns>
/// <exception cref="InvalidOperationException">The token used to modify/delete this message expired.</exception>
/// /// <exception cref="Discord.Net.HttpException">Somthing went wrong during the request.</exception>
public new async Task ModifyAsync(Action<MessageProperties> func, RequestOptions options = null)
{
try
{
var model = await InteractionHelper.ModifyFollowupMessage(Discord, this, func, options).ConfigureAwait(false);
this.Update(model);
}
catch (Discord.Net.HttpException x)
{
if(x.HttpCode == System.Net.HttpStatusCode.NotFound)
{
throw new InvalidOperationException("The token of this message has expired!", x);
}

throw;
}
}
}
}

+ 81
- 0
src/Discord.Net.Rest/Entities/Messages/RestInteractionMessage.cs View File

@@ -0,0 +1,81 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Model = Discord.API.Message;

namespace Discord.Rest
{
/// <summary>
/// Represents the initial REST-based response to a slash command.
/// </summary>
public class RestInteractionMessage : RestUserMessage
{
// Token used to delete/modify this followup message
internal string Token { get; }

internal RestInteractionMessage(BaseDiscordClient discord, ulong id, IUser author, string token, IMessageChannel channel)
: base(discord, id, channel, author, MessageSource.Bot)
{
this.Token = token;
}

internal static RestInteractionMessage Create(BaseDiscordClient discord, Model 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)
{
base.Update(model);
}

/// <summary>
/// Deletes this object and all of it's childern.
/// </summary>
/// <returns>A task that represents the asynchronous delete operation.</returns>
public Task DeleteAsync()
=> InteractionHelper.DeletedInteractionResponse(Discord, this);

/// <summary>
/// Modifies this interaction response
/// </summary>
/// <remarks>
/// This method modifies this message with the specified properties. To see an example of this
/// method and what properties are available, please refer to <see cref="MessageProperties"/>.
/// </remarks>
/// <example>
/// <para>The following example replaces the content of the message with <c>Hello World!</c>.</para>
/// <code language="cs">
/// await msg.ModifyAsync(x =&gt; x.Content = "Hello World!");
/// </code>
/// </example>
/// <param name="func">A delegate containing the properties to modify the message with.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous modification operation.
/// </returns>
/// <exception cref="InvalidOperationException">The token used to modify/delete this message expired.</exception>
/// /// <exception cref="Discord.Net.HttpException">Somthing went wrong during the request.</exception>
public new async Task ModifyAsync(Action<MessageProperties> func, RequestOptions options = null)
{
try
{
var model = await InteractionHelper.ModifyInteractionResponse(Discord, this, func, options).ConfigureAwait(false);
this.Update(model);
}
catch (Discord.Net.HttpException x)
{
if (x.HttpCode == System.Net.HttpStatusCode.NotFound)
{
throw new InvalidOperationException("The token of this message has expired!", x);
}

throw;
}
}
}
}

+ 3
- 1
src/Discord.Net.Rest/Net/Queue/RequestQueueBucket.cs View File

@@ -99,6 +99,7 @@ namespace Discord.Net.Queue
default:
int? code = null;
string reason = null;
object errors = null;
if (response.Stream != null)
{
try
@@ -109,11 +110,12 @@ namespace Discord.Net.Queue
var json = JToken.Load(jsonReader);
try { code = json.Value<int>("code"); } catch { };
try { reason = json.Value<string>("message"); } catch { };
try { errors = json.Value<object>("errors"); } catch { };
}
}
catch { }
}
throw new HttpException(response.StatusCode, request, code, reason);
throw new HttpException(response.StatusCode, request, code, reason, errors);
}
}
else


+ 1
- 1
src/Discord.Net.WebSocket/DiscordSocketConfig.cs View File

@@ -111,7 +111,7 @@ namespace Discord.WebSocket
/// <remarks>
/// <para>
/// Discord interactions will not appear in chat until the client responds to them. With this option set to
/// <see langword="true"/>, the client will automatically acknowledge the interaction with <see cref="InteractionResponseType.ACKWithSource"/>.
/// <see langword="true"/>, the client will automatically acknowledge the interaction with <see cref="InteractionResponseType.DeferredChannelMessageWithSource"/>.
/// See <see href="https://discord.com/developers/docs/interactions/slash-commands#interaction-interactionresponsetype">the docs</see> on
/// responding to interactions for more info.
/// </para>


+ 4
- 2
src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs View File

@@ -28,8 +28,7 @@ namespace Discord.WebSocket
/// <summary>
/// The <see cref="SocketGuildUser"/> who triggered this interaction.
/// </summary>
public SocketGuildUser User
=> Guild.GetUser(UserId);
public SocketGuildUser User { get; private set; }

/// <summary>
/// The type of this interaction.
@@ -87,6 +86,9 @@ namespace Discord.WebSocket
this.Version = model.Version;
this.UserId = model.Member.User.Id;
this.Type = model.Type;

if (this.User == null)
this.User = SocketGuildUser.Create(this.Guild, Discord.State, model.Member); // Change from getter.
}
private bool CheckToken()
{


Loading…
Cancel
Save