Browse Source

Worked on more rest routes for responding to application commands. Worked on SocketInteraction

pull/1717/head
quin lynch 4 years ago
parent
commit
fae3fbeda8
20 changed files with 374 additions and 65 deletions
  1. +2
    -2
      StyleAnalyzer.targets
  2. +31
    -0
      src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOptionType.cs
  3. +4
    -0
      src/Discord.Net.Core/Entities/Interactions/ApplicationCommandProperties.cs
  4. +10
    -0
      src/Discord.Net.Core/Entities/Interactions/IApplicationCommand.cs
  5. +15
    -1
      src/Discord.Net.Core/Entities/Interactions/IApplicationCommandInteractionData.cs
  6. +16
    -2
      src/Discord.Net.Core/Entities/Interactions/IApplicationCommandInteractionDataOption.cs
  7. +3
    -0
      src/Discord.Net.Core/Entities/Interactions/IApplicationCommandOptionChoice.cs
  8. +29
    -1
      src/Discord.Net.Core/Entities/Interactions/IDiscordInteraction.cs
  9. +39
    -0
      src/Discord.Net.Core/Entities/Interactions/InteractionResponseType.cs
  10. +10
    -0
      src/Discord.Net.Core/Entities/Interactions/InteractionType.cs
  11. +24
    -0
      src/Discord.Net.Rest/API/Common/InteractionApplicationCommandCallbackData.cs
  12. +20
    -0
      src/Discord.Net.Rest/API/Common/InteractionFollowupMessage.cs
  13. +18
    -0
      src/Discord.Net.Rest/API/Common/InteractionResponse.cs
  14. +21
    -0
      src/Discord.Net.Rest/API/Rest/ModifyInteractionResponseParams.cs
  15. +26
    -41
      src/Discord.Net.Rest/DiscordRestApiClient.cs
  16. +1
    -1
      src/Discord.Net.Rest/Entities/Interactions/ApplicationCommands/ApplicationCommandHelper.cs
  17. +1
    -1
      src/Discord.Net.WebSocket/DiscordSocketClient.cs
  18. +39
    -16
      src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs
  19. +37
    -0
      src/Discord.Net.WebSocket/Entities/Interaction/SocketInteractionData.cs
  20. +28
    -0
      src/Discord.Net.WebSocket/Entities/Interaction/SocketInteractionDataOption.cs

+ 2
- 2
StyleAnalyzer.targets View File

@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<!-- <ItemGroup>
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.205" PrivateAssets="all" />
</ItemGroup>
<PropertyGroup>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
</PropertyGroup>
</PropertyGroup> -->
</Project>

+ 31
- 0
src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOptionType.cs View File

@@ -11,13 +11,44 @@ namespace Discord
/// </summary>
public enum ApplicationCommandOptionType : byte
{
/// <summary>
/// A sub command
/// </summary>
SubCommand = 1,

/// <summary>
/// A group of sub commands
/// </summary>
SubCommandGroup = 2,

/// <summary>
/// A <see langword="string"/> of text
/// </summary>
String = 3,

/// <summary>
/// An <see langword="int"/>
/// </summary>
Integer = 4,

/// <summary>
/// A <see langword="bool"/>
/// </summary>
Boolean = 5,

/// <summary>
/// A <see cref="IGuildUser"/>
/// </summary>
User = 6,

/// <summary>
/// A <see cref="IGuildChannel"/>
/// </summary>
Channel = 7,

/// <summary>
/// A <see cref="IRole"/>
/// </summary>
Role = 8
}
}

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

@@ -6,6 +6,10 @@ using System.Threading.Tasks;

namespace Discord
{
/// <summary>
/// Provides properties that are used to modify a <see cref="IApplicationCommand" /> with the specified changes.
/// </summary>
/// <see cref="Ia"/>
public class ApplicationCommandProperties
{
public string Name { get; set; }


+ 10
- 0
src/Discord.Net.Core/Entities/Interactions/IApplicationCommand.cs View File

@@ -31,6 +31,16 @@ namespace Discord
/// </summary>
string Description { get; }

/// <summary>
/// Modifies this command
/// </summary>
/// <param name="func">The delegate containing the properties to modify the command 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>
Task ModifyAsync(Action<ApplicationCommandProperties> func, RequestOptions options = null);

IEnumerable<IApplicationCommandOption>? Options { get; }
}
}

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

@@ -6,10 +6,24 @@ using System.Threading.Tasks;

namespace Discord
{
/// <summary>
/// Represents data of an Interaction Command, see <see href="https://discord.com/developers/docs/interactions/slash-commands#interaction-applicationcommandinteractiondata"/>
/// </summary>
public interface IApplicationCommandInteractionData
{
/// <summary>
/// The snowflake id of this command
/// </summary>
ulong Id { get; }

/// <summary>
/// The name of this command
/// </summary>
string Name { get; }
IEnumerable<IApplicationCommandInteractionDataOption> Options { get; }

/// <summary>
/// The params + values from the user
/// </summary>
IReadOnlyCollection<IApplicationCommandInteractionDataOption> Options { get; }
}
}

+ 16
- 2
src/Discord.Net.Core/Entities/Interactions/IApplicationCommandInteractionDataOption.cs View File

@@ -6,11 +6,25 @@ using System.Threading.Tasks;

namespace Discord
{
/// <summary>
/// Represents a option group for a command, see <see href="https://discord.com/developers/docs/interactions/slash-commands#interaction-applicationcommandinteractiondataoption"/>
/// </summary>
public interface IApplicationCommandInteractionDataOption
{
/// <summary>
/// The name of the parameter
/// </summary>
string Name { get; }
ApplicationCommandOptionType Value { get; }
IEnumerable<IApplicationCommandInteractionDataOption> Options { get; }

/// <summary>
/// The value of the pair
/// </summary>
ApplicationCommandOptionType? Value { get; }

/// <summary>
/// Present if this option is a group or subcommand
/// </summary>
IReadOnlyCollection<IApplicationCommandInteractionDataOption> Options { get; }

}
}

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

@@ -6,6 +6,9 @@ using System.Threading.Tasks;

namespace Discord
{
/// <summary>
/// Specifies choices for command group
/// </summary>
public interface IApplicationCommandOptionChoice
{
/// <summary>


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

@@ -16,12 +16,40 @@ namespace Discord
/// id of the interaction
/// </summary>
ulong Id { get; }

/// <summary>
/// The type of this <see cref="IDiscordInteraction"/>
/// </summary>
InteractionType Type { get; }

/// <summary>
/// The command data payload
/// </summary>
IApplicationCommandInteractionData? Data { get; }

/// <summary>
/// The guild it was sent from
/// </summary>
ulong GuildId { get; }

/// <summary>
/// The channel it was sent from
/// </summary>
ulong ChannelId { get; }
IGuildUser Member { get; }

/// <summary>
/// Guild member id for the invoking user
/// </summary>
ulong MemberId { get; }

/// <summary>
/// A continuation token for responding to the interaction
/// </summary>
string Token { get; }

/// <summary>
/// read-only property, always 1
/// </summary>
int Version { get; }
}
}

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

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

namespace Discord
{
/// <summary>
/// The response type for an <see cref="IDiscordInteraction"/>
/// </summary>
public enum InteractionResponseType : byte
{
/// <summary>
/// ACK a Ping
/// </summary>
Pong = 1,

/// <summary>
/// ACK a command without sending a message, eating the user's input
/// </summary>
Acknowledge = 2,

/// <summary>
/// Respond with a message, eating the user's input
/// </summary>
ChannelMessage = 3,

/// <summary>
/// respond with a message, showing the user's input
/// </summary>
ChannelMessageWithSource = 4,

/// <summary>
/// ACK a command without sending a message, showing the user's input
/// </summary>
ACKWithSource = 5
}
}

+ 10
- 0
src/Discord.Net.Core/Entities/Interactions/InteractionType.cs View File

@@ -6,9 +6,19 @@ using System.Threading.Tasks;

namespace Discord
{
/// <summary>
/// Represents a type of Interaction from discord.
/// </summary>
public enum InteractionType : byte
{
/// <summary>
/// A ping from discord
/// </summary>
Ping = 1,

/// <summary>
/// An <see cref="IApplicationCommand"/> sent from discord
/// </summary>
ApplicationCommand = 2
}
}

+ 24
- 0
src/Discord.Net.Rest/API/Common/InteractionApplicationCommandCallbackData.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
{
internal class InteractionApplicationCommandCallbackData
{
[JsonProperty("tts")]
public Optional<bool> TTS { get; set; }

[JsonProperty("content")]
public string Content { get; set; }

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

[JsonProperty("allowed_mentions")]
public Optional<AllowedMentions> AllowedMentions { get; set; }
}
}

+ 20
- 0
src/Discord.Net.Rest/API/Common/InteractionFollowupMessage.cs View File

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

namespace Discord.API
{
internal class InteractionFollowupMessage
{
public string Content { get; set; }
public Optional<string> Username { get; set; }
public Optional<string> AvatarUrl { get; set; }
public Optional<bool> TTS { get; set; }
public Optional<Stream> File { get; set; }
public Embed[] Embeds { get; set; }

}
}

+ 18
- 0
src/Discord.Net.Rest/API/Common/InteractionResponse.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
{
internal class InteractionResponse
{
[JsonProperty("type")]
public InteractionResponseType Type { get; set; }

[JsonProperty("data")]
public Optional<InteractionApplicationCommandCallbackData> Data { get; set; }
}
}

+ 21
- 0
src/Discord.Net.Rest/API/Rest/ModifyInteractionResponseParams.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.Rest
{
internal class ModifyInteractionResponseParams
{
[JsonProperty("content")]
public string Content { get; set; }

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

[JsonProperty("allowed_mentions")]
public Optional<AllowedMentions> AllowedMentions { get; set; }
}
}

+ 26
- 41
src/Discord.Net.Rest/DiscordRestApiClient.cs View File

@@ -803,13 +803,9 @@ namespace Discord.API
Preconditions.AtMost(command.Description.Length, 100, nameof(command.Description));
Preconditions.AtLeast(command.Description.Length, 1, nameof(command.Description));

try
{
return await SendJsonAsync<ApplicationCommand>("POST", $"applications/{this.CurrentUserId}/commands", command, options: options).ConfigureAwait(false);
}
catch (HttpException ex) { return null; }
return await SendJsonAsync<ApplicationCommand>("POST", $"applications/{this.CurrentUserId}/commands", command, options: options).ConfigureAwait(false);
}
public async Task<ApplicationCommand> EditGlobalApplicationCommandAsync(ApplicationCommandParams command, ulong commandId, RequestOptions options = null)
public async Task<ApplicationCommand> ModifyGlobalApplicationCommandAsync(ApplicationCommandParams command, ulong commandId, RequestOptions options = null)
{
Preconditions.NotNull(command, nameof(command));
Preconditions.AtMost(command.Name.Length, 32, nameof(command.Name));
@@ -817,28 +813,13 @@ namespace Discord.API
Preconditions.AtMost(command.Description.Length, 100, nameof(command.Description));
Preconditions.AtLeast(command.Description.Length, 1, nameof(command.Description));

try
{
return await SendJsonAsync<ApplicationCommand>("PATCH", $"applications/{this.CurrentUserId}/commands/{commandId}", command, options: options).ConfigureAwait(false);
}
catch (HttpException ex) { return null; }
return await SendJsonAsync<ApplicationCommand>("PATCH", $"applications/{this.CurrentUserId}/commands/{commandId}", command, options: options).ConfigureAwait(false);
}
public async Task DeleteGlobalApplicationCommandAsync(ulong commandId, RequestOptions options = null)
{
try
{
await SendAsync("DELETE", $"applications/{this.CurrentUserId}/commands/{commandId}", options: options).ConfigureAwait(false);
}
catch (HttpException ex) { return; }
}
=> await SendAsync("DELETE", $"applications/{this.CurrentUserId}/commands/{commandId}", options: options).ConfigureAwait(false);

public async Task<ApplicationCommand[]> GetGuildApplicationCommandAsync(ulong guildId, RequestOptions options = null)
{
try
{
return await SendAsync<ApplicationCommand[]>("GET", $"applications/{this.CurrentUserId}/guilds/{guildId}/commands", options: options).ConfigureAwait(false);
}
catch (HttpException ex) { return null; }
}
=> await SendAsync<ApplicationCommand[]>("GET", $"applications/{this.CurrentUserId}/guilds/{guildId}/commands", options: options).ConfigureAwait(false);
public async Task<ApplicationCommand> CreateGuildApplicationCommandAsync(ApplicationCommandParams command, ulong guildId, RequestOptions options = null)
{
Preconditions.NotNull(command, nameof(command));
@@ -847,13 +828,9 @@ namespace Discord.API
Preconditions.AtMost(command.Description.Length, 100, nameof(command.Description));
Preconditions.AtLeast(command.Description.Length, 1, nameof(command.Description));

try
{
return await SendJsonAsync<ApplicationCommand>("POST", $"applications/{this.CurrentUserId}/guilds/{guildId}/commands", command, options: options).ConfigureAwait(false);
}
catch (HttpException ex) { return null; }
return await SendJsonAsync<ApplicationCommand>("POST", $"applications/{this.CurrentUserId}/guilds/{guildId}/commands", command, options: options).ConfigureAwait(false);
}
public async Task<ApplicationCommand> EditGuildApplicationCommandAsync(ApplicationCommandParams command, ulong guildId, ulong commandId, RequestOptions options = null)
public async Task<ApplicationCommand> ModifyGuildApplicationCommandAsync(ApplicationCommandParams command, ulong guildId, ulong commandId, RequestOptions options = null)
{
Preconditions.NotNull(command, nameof(command));
Preconditions.AtMost(command.Name.Length, 32, nameof(command.Name));
@@ -861,19 +838,27 @@ namespace Discord.API
Preconditions.AtMost(command.Description.Length, 100, nameof(command.Description));
Preconditions.AtLeast(command.Description.Length, 1, nameof(command.Description));

try
{
return await SendJsonAsync<ApplicationCommand>("PATCH", $"applications/{this.CurrentUserId}/guilds/{guildId}/commands/{commandId}", command, options: options).ConfigureAwait(false);
}
catch (HttpException ex) { return null; }
return await SendJsonAsync<ApplicationCommand>("PATCH", $"applications/{this.CurrentUserId}/guilds/{guildId}/commands/{commandId}", command, options: options).ConfigureAwait(false);
}
public async Task DeleteGuildApplicationCommandAsync(ulong guildId, ulong commandId, RequestOptions options = null)
=> await SendAsync<ApplicationCommand>("DELETE", $"applications/{this.CurrentUserId}/guilds/{guildId}/commands/{commandId}", options: options).ConfigureAwait(false);

//Interaction Responses
public async Task CreateInteractionResponse(InteractionResponse response, string interactionId, string interactionToken, RequestOptions options = null)
{
try
{
await SendAsync<ApplicationCommand>("DELETE", $"applications/{this.CurrentUserId}/guilds/{guildId}/commands/{commandId}", options: options).ConfigureAwait(false);
}
catch (HttpException ex) { return; }
if(response.Data.IsSpecified)
Preconditions.AtMost(response.Data.Value.Content.Length, 2000, nameof(response.Data.Value.Content));
await SendJsonAsync("POST", $"/interactions/{interactionId}/{interactionToken}/callback", response, options: options);
}
public async Task ModifyInteractionResponse(ModifyInteractionResponseParams args, string interactionToken, RequestOptions options = null)
=> await SendJsonAsync("POST", $"/webhooks/{this.CurrentUserId}/{interactionToken}/messages/@original", args, options: options);
public async Task DeleteInteractionResponse(string interactionToken, RequestOptions options = null)
=> await SendAsync("DELETE", $"/webhooks/{this.CurrentUserId}/{interactionToken}/messages/@original", options: options);

public async Task CreateInteractionFollowupMessage()
{

}

//Guilds


+ 1
- 1
src/Discord.Net.Rest/Entities/Interactions/ApplicationCommands/ApplicationCommandHelper.cs View File

@@ -27,7 +27,7 @@ namespace Discord.Rest
: Optional<API.ApplicationCommandOption[]>.Unspecified,
};

return await client.ApiClient.EditGlobalApplicationCommandAsync(apiArgs, command.Id, options);
return await client.ApiClient.ModifyGlobalApplicationCommandAsync(apiArgs, command.Id, options);
}
}
}

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

@@ -1780,7 +1780,7 @@ namespace Discord.WebSocket
break;
case "INTERACTION_CREATE":
{
await _gatewayLogger.DebugAsync("Received Dispatch (INVITE_DELETE)").ConfigureAwait(false);
await _gatewayLogger.DebugAsync("Received Dispatch (INTERACTION_CREATE)").ConfigureAwait(false);

var data = (payload as JToken).ToObject<API.Gateway.InteractionCreated>(_serializer);
if (State.GetChannel(data.ChannelId) is SocketGuildChannel channel)


+ 39
- 16
src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs View File

@@ -3,32 +3,55 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Model = Discord.API.Gateway.InteractionCreated;

namespace Discord.WebSocket.Entities.Interaction
{
public class SocketInteraction : SocketEntity<ulong>, IDiscordInteraction
{
public ulong Id { get; }

public InteractionType Type { get; }

public IApplicationCommandInteractionData Data { get; }

public ulong GuildId { get; }

public ulong ChannelId { get; }

public IGuildUser Member { get; }

public string Token { get; }
public SocketGuild Guild
=> Discord.GetGuild(GuildId);
public SocketTextChannel Channel
=> Guild.GetTextChannel(ChannelId);
public SocketGuildUser Member
=> Guild.GetUser(MemberId);

public InteractionType Type { get; private set; }
public IApplicationCommandInteractionData Data { get; private set; }
public string Token { get; private set; }
public int Version { get; private set; }
public DateTimeOffset CreatedAt { get; }

public int Version { get; }
public ulong GuildId { get; private set; }
public ulong ChannelId { get; private set; }
public ulong MemberId { get; private set; }

public DateTimeOffset CreatedAt { get; }
public SocketInteraction(DiscordSocketClient client, ulong id)
internal SocketInteraction(DiscordSocketClient client, ulong id)
: base(client, id)
{
}

internal static SocketInteraction Create(DiscordSocketClient client, Model model)
{
var entitiy = new SocketInteraction(client, model.Id);
entitiy.Update(model);
return entitiy;
}

internal void Update(Model model)
{
this.Data = model.Data.IsSpecified
? SocketInteractionData.Create(this.Discord, model.Data.Value)
: null;

this.GuildId = model.GuildId;
this.ChannelId = model.ChannelId;
this.Token = model.Token;
this.Version = model.Version;
this.MemberId = model.Member.User.Id;
this.Type = model.Type;
}

}
}

+ 37
- 0
src/Discord.Net.WebSocket/Entities/Interaction/SocketInteractionData.cs View File

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

namespace Discord.WebSocket.Entities.Interaction
{
public class SocketInteractionData : SocketEntity<ulong>, IApplicationCommandInteractionData
{
public string Name { get; private set; }
public IReadOnlyCollection<IApplicationCommandInteractionDataOption> Options { get; private set; }

internal SocketInteractionData(DiscordSocketClient client, ulong id)
: base(client, id)
{

}

internal static SocketInteractionData Create(DiscordSocketClient client, Model model)
{
var entity = new SocketInteractionData(client, model.Id);
entity.Update(model);
return entity;
}
internal void Update(Model model)
{
this.Name = model.Name;
this.Options = model.Options.IsSpecified
? model.Options.Value.Select(x => new SocketInteractionDataOption(x)).ToImmutableArray()
: null;

}
}
}

+ 28
- 0
src/Discord.Net.WebSocket/Entities/Interaction/SocketInteractionDataOption.cs View File

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

namespace Discord.WebSocket.Entities.Interaction
{
public class SocketInteractionDataOption : IApplicationCommandInteractionDataOption
{
public string Name { get; private set; }
public ApplicationCommandOptionType? Value { get; private set; }

public IReadOnlyCollection<IApplicationCommandInteractionDataOption> Options { get; private set; }

internal SocketInteractionDataOption(Model model)
{
this.Name = Name;
this.Value = model.Value.IsSpecified ? model.Value.Value : null;

this.Options = model.Options.IsSpecified
? model.Options.Value.Select(x => new SocketInteractionDataOption(x)).ToImmutableArray()
: null;
}
}
}

Loading…
Cancel
Save