Browse Source

Moved SocketCommandBase in front of slash/user/message commands

pull/1923/head
drobbins329 3 years ago
parent
commit
8c1a26f697
13 changed files with 502 additions and 445 deletions
  1. +51
    -63
      src/Discord.Net.WebSocket/Discord.Net.WebSocket.xml
  2. +1
    -125
      src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/Message Commands/SocketApplicationMessageCommand.cs
  3. +1
    -1
      src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/Message Commands/SocketApplicationMessageCommandData.cs
  4. +2
    -126
      src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/User Commands/SocketApplicationUserCommand.cs
  5. +1
    -1
      src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/User Commands/SocketApplicationUserCommandData.cs
  6. +3
    -127
      src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommand.cs
  7. +0
    -0
      src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommand.cs
  8. +0
    -0
      src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommandChoice.cs
  9. +0
    -0
      src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommandOption.cs
  10. +164
    -0
      src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBase.cs
  11. +145
    -0
      src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBaseData.cs
  12. +130
    -0
      src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBaseDataOption.cs
  13. +4
    -2
      src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs

+ 51
- 63
src/Discord.Net.WebSocket/Discord.Net.WebSocket.xml View File

@@ -3609,23 +3609,9 @@
The data associated with this interaction. The data associated with this interaction.
</summary> </summary>
</member> </member>
<member name="M:Discord.WebSocket.SocketApplicationMessageCommand.RespondAsync(System.String,Discord.Embed[],System.Boolean,System.Boolean,Discord.AllowedMentions,Discord.RequestOptions,Discord.MessageComponent,Discord.Embed)">
<inheritdoc/>
</member>
<member name="M:Discord.WebSocket.SocketApplicationMessageCommand.FollowupAsync(System.String,Discord.Embed[],System.Boolean,System.Boolean,Discord.AllowedMentions,Discord.RequestOptions,Discord.MessageComponent,Discord.Embed)">
<inheritdoc/>
</member>
<member name="M:Discord.WebSocket.SocketApplicationMessageCommand.DeferAsync(Discord.RequestOptions)">
<summary>
Acknowledges this interaction with the <see cref="F:Discord.InteractionResponseType.DeferredChannelMessageWithSource"/>.
</summary>
<returns>
A task that represents the asynchronous operation of acknowledging the interaction.
</returns>
</member>
<member name="T:Discord.WebSocket.SocketApplicationMessageCommandData"> <member name="T:Discord.WebSocket.SocketApplicationMessageCommandData">
<summary> <summary>
Represents the data tied with the <see cref="T:Discord.WebSocket.SocketSlashCommand"/> interaction.
Represents the data tied with the <see cref="T:Discord.WebSocket.SocketApplicationMessageCommand"/> interaction.
</summary> </summary>
</member> </member>
<member name="P:Discord.WebSocket.SocketApplicationMessageCommandData.Name"> <member name="P:Discord.WebSocket.SocketApplicationMessageCommandData.Name">
@@ -3641,23 +3627,9 @@
The data associated with this interaction. The data associated with this interaction.
</summary> </summary>
</member> </member>
<member name="M:Discord.WebSocket.SocketApplicationUserCommand.RespondAsync(System.String,Discord.Embed[],System.Boolean,System.Boolean,Discord.AllowedMentions,Discord.RequestOptions,Discord.MessageComponent,Discord.Embed)">
<inheritdoc/>
</member>
<member name="M:Discord.WebSocket.SocketApplicationUserCommand.FollowupAsync(System.String,Discord.Embed[],System.Boolean,System.Boolean,Discord.AllowedMentions,Discord.RequestOptions,Discord.MessageComponent,Discord.Embed)">
<inheritdoc/>
</member>
<member name="M:Discord.WebSocket.SocketApplicationUserCommand.DeferAsync(Discord.RequestOptions)">
<summary>
Acknowledges this interaction with the <see cref="F:Discord.InteractionResponseType.DeferredChannelMessageWithSource"/>.
</summary>
<returns>
A task that represents the asynchronous operation of acknowledging the interaction.
</returns>
</member>
<member name="T:Discord.WebSocket.SocketApplicationUserCommandData"> <member name="T:Discord.WebSocket.SocketApplicationUserCommandData">
<summary> <summary>
Represents the data tied with the <see cref="T:Discord.WebSocket.SocketSlashCommand"/> interaction.
Represents the data tied with the <see cref="T:Discord.WebSocket.SocketApplicationUserCommand"/> interaction.
</summary> </summary>
</member> </member>
<member name="P:Discord.WebSocket.SocketApplicationUserCommandData.Name"> <member name="P:Discord.WebSocket.SocketApplicationUserCommandData.Name">
@@ -3721,6 +3693,48 @@
The value(s) of a <see cref="T:Discord.SelectMenu"/> interaction response. The value(s) of a <see cref="T:Discord.SelectMenu"/> interaction response.
</summary> </summary>
</member> </member>
<member name="T:Discord.WebSocket.SocketSlashCommand">
<summary>
Represents a Websocket-based slash command received over the gateway.
</summary>
</member>
<member name="P:Discord.WebSocket.SocketSlashCommand.Data">
<summary>
The data associated with this interaction.
</summary>
</member>
<member name="T:Discord.WebSocket.SocketSlashCommandData">
<summary>
Represents the data tied with the <see cref="T:Discord.WebSocket.SocketSlashCommand"/> interaction.
</summary>
</member>
<member name="P:Discord.WebSocket.SocketSlashCommandData.Name">
<inheritdoc/>
</member>
<member name="P:Discord.WebSocket.SocketSlashCommandData.Options">
<summary>
The <see cref="T:Discord.WebSocket.SocketSlashCommandDataOption"/>'s received with this interaction.
</summary>
</member>
<member name="T:Discord.WebSocket.SocketSlashCommandDataOption">
<summary>
Represents a Websocket-based <see cref="T:Discord.IApplicationCommandInteractionDataOption"/> recieved by the gateway
</summary>
</member>
<member name="P:Discord.WebSocket.SocketSlashCommandDataOption.Name">
<inheritdoc/>
</member>
<member name="P:Discord.WebSocket.SocketSlashCommandDataOption.Value">
<inheritdoc/>
</member>
<member name="P:Discord.WebSocket.SocketSlashCommandDataOption.Type">
<inheritdoc/>
</member>
<member name="P:Discord.WebSocket.SocketSlashCommandDataOption.Options">
<summary>
The sub command options received for this sub command group.
</summary>
</member>
<member name="T:Discord.WebSocket.SocketApplicationCommand"> <member name="T:Discord.WebSocket.SocketApplicationCommand">
<summary> <summary>
Represends a Websocket-based <see cref="T:Discord.IApplicationCommand"/> recieved over the gateway. Represends a Websocket-based <see cref="T:Discord.IApplicationCommand"/> recieved over the gateway.
@@ -3798,23 +3812,18 @@
If the option is a subcommand or subcommand group type, this nested options will be the parameters. If the option is a subcommand or subcommand group type, this nested options will be the parameters.
</summary> </summary>
</member> </member>
<member name="T:Discord.WebSocket.SocketSlashCommand">
<summary>
Represents a Websocket-based slash command received over the gateway.
</summary>
</member>
<member name="P:Discord.WebSocket.SocketSlashCommand.Data">
<member name="P:Discord.WebSocket.SocketCommandBase.Data">
<summary> <summary>
The data associated with this interaction. The data associated with this interaction.
</summary> </summary>
</member> </member>
<member name="M:Discord.WebSocket.SocketSlashCommand.RespondAsync(System.String,Discord.Embed[],System.Boolean,System.Boolean,Discord.AllowedMentions,Discord.RequestOptions,Discord.MessageComponent,Discord.Embed)">
<member name="M:Discord.WebSocket.SocketCommandBase.RespondAsync(System.String,Discord.Embed[],System.Boolean,System.Boolean,Discord.AllowedMentions,Discord.RequestOptions,Discord.MessageComponent,Discord.Embed)">
<inheritdoc/> <inheritdoc/>
</member> </member>
<member name="M:Discord.WebSocket.SocketSlashCommand.FollowupAsync(System.String,Discord.Embed[],System.Boolean,System.Boolean,Discord.AllowedMentions,Discord.RequestOptions,Discord.MessageComponent,Discord.Embed)">
<member name="M:Discord.WebSocket.SocketCommandBase.FollowupAsync(System.String,Discord.Embed[],System.Boolean,System.Boolean,Discord.AllowedMentions,Discord.RequestOptions,Discord.MessageComponent,Discord.Embed)">
<inheritdoc/> <inheritdoc/>
</member> </member>
<member name="M:Discord.WebSocket.SocketSlashCommand.DeferAsync(Discord.RequestOptions)">
<member name="M:Discord.WebSocket.SocketCommandBase.DeferAsync(Discord.RequestOptions)">
<summary> <summary>
Acknowledges this interaction with the <see cref="F:Discord.InteractionResponseType.DeferredChannelMessageWithSource"/>. Acknowledges this interaction with the <see cref="F:Discord.InteractionResponseType.DeferredChannelMessageWithSource"/>.
</summary> </summary>
@@ -3822,34 +3831,13 @@
A task that represents the asynchronous operation of acknowledging the interaction. A task that represents the asynchronous operation of acknowledging the interaction.
</returns> </returns>
</member> </member>
<member name="T:Discord.WebSocket.SocketSlashCommandData">
<summary>
Represents the data tied with the <see cref="T:Discord.WebSocket.SocketSlashCommand"/> interaction.
</summary>
</member>
<member name="P:Discord.WebSocket.SocketSlashCommandData.Name">
<inheritdoc/>
</member>
<member name="P:Discord.WebSocket.SocketSlashCommandData.Options">
<summary>
The <see cref="T:Discord.WebSocket.SocketSlashCommandDataOption"/>'s received with this interaction.
</summary>
</member>
<member name="T:Discord.WebSocket.SocketSlashCommandDataOption">
<summary>
Represents a Websocket-based <see cref="T:Discord.IApplicationCommandInteractionDataOption"/> recieved by the gateway
</summary>
</member>
<member name="P:Discord.WebSocket.SocketSlashCommandDataOption.Name">
<inheritdoc/>
</member>
<member name="P:Discord.WebSocket.SocketSlashCommandDataOption.Value">
<member name="P:Discord.WebSocket.SocketCommandBaseDataOption.Value">
<inheritdoc/> <inheritdoc/>
</member> </member>
<member name="P:Discord.WebSocket.SocketSlashCommandDataOption.Type">
<member name="P:Discord.WebSocket.SocketCommandBaseDataOption.Type">
<inheritdoc/> <inheritdoc/>
</member> </member>
<member name="P:Discord.WebSocket.SocketSlashCommandDataOption.Options">
<member name="P:Discord.WebSocket.SocketCommandBaseDataOption.Options">
<summary> <summary>
The sub command options received for this sub command group. The sub command options received for this sub command group.
</summary> </summary>


+ 1
- 125
src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/Message Commands/SocketApplicationMessageCommand.cs View File

@@ -10,7 +10,7 @@ namespace Discord.WebSocket
/// <summary> /// <summary>
/// Represents a Websocket-based slash command received over the gateway. /// Represents a Websocket-based slash command received over the gateway.
/// </summary> /// </summary>
public class SocketApplicationMessageCommand : SocketSlashCommand
public class SocketApplicationMessageCommand : SocketCommandBase
{ {
/// <summary> /// <summary>
/// The data associated with this interaction. /// The data associated with this interaction.
@@ -37,129 +37,5 @@ namespace Discord.WebSocket
entity.Update(model); entity.Update(model);
return entity; return entity;
} }

internal override void Update(Model model)
{
var data = model.Data.IsSpecified ?
(DataModel)model.Data.Value
: null;

this.Data.Update(data);

base.Update(model);
}

/// <inheritdoc/>
public override 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)
{
if (!IsValidToken)
throw new InvalidOperationException("Interaction token is no longer valid");

if (embeds == null && embed != null)
embeds = new[] { embed };

if (Discord.AlwaysAcknowledgeInteractions)
{
await FollowupAsync(text, embeds, isTTS, ephemeral, allowedMentions, options, component);
return;
}

Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed.");
Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed.");
Preconditions.AtMost(embeds?.Length ?? 0, 10, nameof(embeds), "A max of 10 embeds are allowed.");

// 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 response = new API.InteractionResponse
{
Type = InteractionResponseType.ChannelMessageWithSource,
Data = new API.InteractionCallbackData
{
Content = text,
AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified,
Embeds = embeds?.Select(x => x.ToModel()).ToArray() ?? Optional<API.Embed[]>.Unspecified,
TTS = isTTS ? true : Optional<bool>.Unspecified,
Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified
}
};

if (ephemeral)
response.Data.Value.Flags = 64;

await InteractionHelper.SendInteractionResponse(this.Discord, response, this.Id, Token, options);
}

/// <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)
{
if (!IsValidToken)
throw new InvalidOperationException("Interaction token is no longer valid");

if (embeds == null && embed != null)
embeds = new[] { embed };
Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed.");
Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed.");
Preconditions.AtMost(embeds?.Length ?? 0, 10, nameof(embeds), "A max of 10 embeds are allowed.");

var args = new API.Rest.CreateWebhookMessageParams
{
Content = text,
AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified,
IsTTS = isTTS,
Embeds = embeds?.Select(x => x.ToModel()).ToArray() ?? Optional<API.Embed[]>.Unspecified,
Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified
};

if (ephemeral)
args.Flags = 64;

return await InteractionHelper.SendFollowupAsync(Discord.Rest, args, Token, Channel, options);
}

/// <summary>
/// Acknowledges this interaction with the <see cref="InteractionResponseType.DeferredChannelMessageWithSource"/>.
/// </summary>
/// <returns>
/// A task that represents the asynchronous operation of acknowledging the interaction.
/// </returns>
public override Task DeferAsync(RequestOptions options = null)
{
var response = new API.InteractionResponse
{
Type = InteractionResponseType.DeferredChannelMessageWithSource,
};

return Discord.Rest.ApiClient.CreateInteractionResponse(response, this.Id, this.Token, options);
}
} }
} }

+ 1
- 1
src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/Message Commands/SocketApplicationMessageCommandData.cs View File

@@ -6,7 +6,7 @@ using Model = Discord.API.ApplicationCommandInteractionData;
namespace Discord.WebSocket namespace Discord.WebSocket
{ {
/// <summary> /// <summary>
/// Represents the data tied with the <see cref="SocketSlashCommand"/> interaction.
/// Represents the data tied with the <see cref="SocketApplicationMessageCommand"/> interaction.
/// </summary> /// </summary>
public class SocketApplicationMessageCommandData : SocketEntity<ulong>, IApplicationCommandInteractionData public class SocketApplicationMessageCommandData : SocketEntity<ulong>, IApplicationCommandInteractionData
{ {


+ 2
- 126
src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/User Commands/SocketApplicationUserCommand.cs View File

@@ -10,7 +10,7 @@ namespace Discord.WebSocket
/// <summary> /// <summary>
/// Represents a Websocket-based slash command received over the gateway. /// Represents a Websocket-based slash command received over the gateway.
/// </summary> /// </summary>
public class SocketApplicationUserCommand : SocketSlashCommand
public class SocketApplicationUserCommand : SocketCommandBase
{ {
/// <summary> /// <summary>
/// The data associated with this interaction. /// The data associated with this interaction.
@@ -36,130 +36,6 @@ namespace Discord.WebSocket
var entity = new SocketApplicationUserCommand(client, model, channel); var entity = new SocketApplicationUserCommand(client, model, channel);
entity.Update(model); entity.Update(model);
return entity; return entity;
}

internal override void Update(Model model)
{
var data = model.Data.IsSpecified ?
(DataModel)model.Data.Value
: null;

this.Data.Update(data);

base.Update(model);
}

/// <inheritdoc/>
public override 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)
{
if (!IsValidToken)
throw new InvalidOperationException("Interaction token is no longer valid");

if (embeds == null && embed != null)
embeds = new[] { embed };

if (Discord.AlwaysAcknowledgeInteractions)
{
await FollowupAsync(text, embeds, isTTS, ephemeral, allowedMentions, options, component);
return;
}

Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed.");
Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed.");
Preconditions.AtMost(embeds?.Length ?? 0, 10, nameof(embeds), "A max of 10 embeds are allowed.");

// 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 response = new API.InteractionResponse
{
Type = InteractionResponseType.ChannelMessageWithSource,
Data = new API.InteractionCallbackData
{
Content = text,
AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified,
Embeds = embeds?.Select(x => x.ToModel()).ToArray() ?? Optional<API.Embed[]>.Unspecified,
TTS = isTTS ? true : Optional<bool>.Unspecified,
Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified
}
};

if (ephemeral)
response.Data.Value.Flags = 64;

await InteractionHelper.SendInteractionResponse(this.Discord, response, this.Id, Token, options);
}

/// <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)
{
if (!IsValidToken)
throw new InvalidOperationException("Interaction token is no longer valid");

if (embeds == null && embed != null)
embeds = new[] { embed };
Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed.");
Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed.");
Preconditions.AtMost(embeds?.Length ?? 0, 10, nameof(embeds), "A max of 10 embeds are allowed.");

var args = new API.Rest.CreateWebhookMessageParams
{
Content = text,
AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified,
IsTTS = isTTS,
Embeds = embeds?.Select(x => x.ToModel()).ToArray() ?? Optional<API.Embed[]>.Unspecified,
Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified
};

if (ephemeral)
args.Flags = 64;

return await InteractionHelper.SendFollowupAsync(Discord.Rest, args, Token, Channel, options);
}

/// <summary>
/// Acknowledges this interaction with the <see cref="InteractionResponseType.DeferredChannelMessageWithSource"/>.
/// </summary>
/// <returns>
/// A task that represents the asynchronous operation of acknowledging the interaction.
/// </returns>
public override Task DeferAsync(RequestOptions options = null)
{
var response = new API.InteractionResponse
{
Type = InteractionResponseType.DeferredChannelMessageWithSource,
};

return Discord.Rest.ApiClient.CreateInteractionResponse(response, this.Id, this.Token, options);
}
}
} }
} }

+ 1
- 1
src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/User Commands/SocketApplicationUserCommandData.cs View File

@@ -6,7 +6,7 @@ using Model = Discord.API.ApplicationCommandInteractionData;
namespace Discord.WebSocket namespace Discord.WebSocket
{ {
/// <summary> /// <summary>
/// Represents the data tied with the <see cref="SocketSlashCommand"/> interaction.
/// Represents the data tied with the <see cref="SocketApplicationUserCommand"/> interaction.
/// </summary> /// </summary>
public class SocketApplicationUserCommandData : SocketEntity<ulong>, IApplicationCommandInteractionData public class SocketApplicationUserCommandData : SocketEntity<ulong>, IApplicationCommandInteractionData
{ {


+ 3
- 127
src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommand.cs View File

@@ -10,7 +10,7 @@ namespace Discord.WebSocket
/// <summary> /// <summary>
/// Represents a Websocket-based slash command received over the gateway. /// Represents a Websocket-based slash command received over the gateway.
/// </summary> /// </summary>
public class SocketSlashCommand : SocketInteraction
public class SocketSlashCommand : SocketCommandBase
{ {
/// <summary> /// <summary>
/// The data associated with this interaction. /// The data associated with this interaction.
@@ -18,7 +18,7 @@ namespace Discord.WebSocket
new public SocketSlashCommandData Data { get; } new public SocketSlashCommandData Data { get; }


internal SocketSlashCommand(DiscordSocketClient client, Model model, ISocketMessageChannel channel) internal SocketSlashCommand(DiscordSocketClient client, Model model, ISocketMessageChannel channel)
: base(client, model.Id, channel)
: base(client, model, channel)
{ {
var dataModel = model.Data.IsSpecified ? var dataModel = model.Data.IsSpecified ?
(DataModel)model.Data.Value (DataModel)model.Data.Value
@@ -36,130 +36,6 @@ namespace Discord.WebSocket
var entity = new SocketSlashCommand(client, model, channel); var entity = new SocketSlashCommand(client, model, channel);
entity.Update(model); entity.Update(model);
return entity; return entity;
}

internal override void Update(Model model)
{
var data = model.Data.IsSpecified ?
(DataModel)model.Data.Value
: null;

this.Data.Update(data);

base.Update(model);
}

/// <inheritdoc/>
public override 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)
{
if (!IsValidToken)
throw new InvalidOperationException("Interaction token is no longer valid");

if (embeds == null && embed != null)
embeds = new[] { embed };

if (Discord.AlwaysAcknowledgeInteractions)
{
await FollowupAsync(text, embeds, isTTS, ephemeral, allowedMentions, options, component);
return;
}

Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed.");
Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed.");
Preconditions.AtMost(embeds?.Length ?? 0, 10, nameof(embeds), "A max of 10 embeds are allowed.");

// 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 response = new API.InteractionResponse
{
Type = InteractionResponseType.ChannelMessageWithSource,
Data = new API.InteractionCallbackData
{
Content = text,
AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified,
Embeds = embeds?.Select(x => x.ToModel()).ToArray() ?? Optional<API.Embed[]>.Unspecified,
TTS = isTTS ? true : Optional<bool>.Unspecified,
Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified
}
};

if (ephemeral)
response.Data.Value.Flags = 64;

await InteractionHelper.SendInteractionResponse(this.Discord, response, this.Id, Token, options);
}

/// <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)
{
if (!IsValidToken)
throw new InvalidOperationException("Interaction token is no longer valid");

if (embeds == null && embed != null)
embeds = new[] { embed };
Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed.");
Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed.");
Preconditions.AtMost(embeds?.Length ?? 0, 10, nameof(embeds), "A max of 10 embeds are allowed.");

var args = new API.Rest.CreateWebhookMessageParams
{
Content = text,
AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified,
IsTTS = isTTS,
Embeds = embeds?.Select(x => x.ToModel()).ToArray() ?? Optional<API.Embed[]>.Unspecified,
Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified
};

if (ephemeral)
args.Flags = 64;

return await InteractionHelper.SendFollowupAsync(Discord.Rest, args, Token, Channel, options);
}

/// <summary>
/// Acknowledges this interaction with the <see cref="InteractionResponseType.DeferredChannelMessageWithSource"/>.
/// </summary>
/// <returns>
/// A task that represents the asynchronous operation of acknowledging the interaction.
/// </returns>
public override Task DeferAsync(RequestOptions options = null)
{
var response = new API.InteractionResponse
{
Type = InteractionResponseType.DeferredChannelMessageWithSource,
};

return Discord.Rest.ApiClient.CreateInteractionResponse(response, this.Id, this.Token, options);
}
}
} }
} }

src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketApplicationCommand.cs → src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommand.cs View File


src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketApplicationCommandChoice.cs → src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommandChoice.cs View File


src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketApplicationCommandOption.cs → src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommandOption.cs View File


+ 164
- 0
src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBase.cs View File

@@ -0,0 +1,164 @@
using Discord.Rest;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using DataModel = Discord.API.ApplicationCommandInteractionData;
using Model = Discord.API.Interaction;

namespace Discord.WebSocket
{
public class SocketCommandBase : SocketInteraction
{
/// <summary>
/// The data associated with this interaction.
/// </summary>
new internal SocketCommandBaseData Data { get; }

internal SocketCommandBase(DiscordSocketClient client, Model model, ISocketMessageChannel channel)
: base(client, model.Id, channel)
{
var dataModel = model.Data.IsSpecified ?
(DataModel)model.Data.Value
: null;

ulong? guildId = null;
if (this.Channel is SocketGuildChannel guildChannel)
guildId = guildChannel.Guild.Id;

Data = SocketCommandBaseData.Create(client, dataModel, model.Id, guildId);
}

new internal static SocketInteraction Create(DiscordSocketClient client, Model model, ISocketMessageChannel channel)
{
var entity = new SocketSlashCommand(client, model, channel);
entity.Update(model);
return entity;
}

internal override void Update(Model model)
{
var data = model.Data.IsSpecified ?
(DataModel)model.Data.Value
: null;

this.Data.Update(data);

base.Update(model);
}

/// <inheritdoc/>
public override 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)
{
if (!IsValidToken)
throw new InvalidOperationException("Interaction token is no longer valid");

if (embeds == null && embed != null)
embeds = new[] { embed };

if (Discord.AlwaysAcknowledgeInteractions)
{
await FollowupAsync(text, embeds, isTTS, ephemeral, allowedMentions, options, component);
return;
}

Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed.");
Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed.");
Preconditions.AtMost(embeds?.Length ?? 0, 10, nameof(embeds), "A max of 10 embeds are allowed.");

// 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 response = new API.InteractionResponse
{
Type = InteractionResponseType.ChannelMessageWithSource,
Data = new API.InteractionCallbackData
{
Content = text,
AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified,
Embeds = embeds?.Select(x => x.ToModel()).ToArray() ?? Optional<API.Embed[]>.Unspecified,
TTS = isTTS ? true : Optional<bool>.Unspecified,
Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified
}
};

if (ephemeral)
response.Data.Value.Flags = 64;

await InteractionHelper.SendInteractionResponse(this.Discord, response, this.Id, Token, options);
}

/// <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)
{
if (!IsValidToken)
throw new InvalidOperationException("Interaction token is no longer valid");

if (embeds == null && embed != null)
embeds = new[] { embed };
Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed.");
Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed.");
Preconditions.AtMost(embeds?.Length ?? 0, 10, nameof(embeds), "A max of 10 embeds are allowed.");

var args = new API.Rest.CreateWebhookMessageParams
{
Content = text,
AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified,
IsTTS = isTTS,
Embeds = embeds?.Select(x => x.ToModel()).ToArray() ?? Optional<API.Embed[]>.Unspecified,
Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified
};

if (ephemeral)
args.Flags = 64;

return await InteractionHelper.SendFollowupAsync(Discord.Rest, args, Token, Channel, options);
}

/// <summary>
/// Acknowledges this interaction with the <see cref="InteractionResponseType.DeferredChannelMessageWithSource"/>.
/// </summary>
/// <returns>
/// A task that represents the asynchronous operation of acknowledging the interaction.
/// </returns>
public override Task DeferAsync(RequestOptions options = null)
{
var response = new API.InteractionResponse
{
Type = InteractionResponseType.DeferredChannelMessageWithSource,
};

return Discord.Rest.ApiClient.CreateInteractionResponse(response, this.Id, this.Token, options);
}
}
}

+ 145
- 0
src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBaseData.cs View File

@@ -0,0 +1,145 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Model = Discord.API.ApplicationCommandInteractionData;

namespace Discord.WebSocket
{
public class SocketCommandBaseData : SocketEntity<ulong>, IApplicationCommandInteractionData
{
public string Name { get; private set; }

public IReadOnlyCollection<SocketCommandBaseDataOption> Options { get; private set; }
// id
// type
internal Dictionary<ulong, SocketGuildUser> guildMembers { get; private set; }
= new Dictionary<ulong, SocketGuildUser>();
internal Dictionary<ulong, SocketGlobalUser> users { get; private set; }
= new Dictionary<ulong, SocketGlobalUser>();
internal Dictionary<ulong, SocketChannel> channels { get; private set; }
= new Dictionary<ulong, SocketChannel>();
internal Dictionary<ulong, SocketRole> roles { get; private set; }
= new Dictionary<ulong, SocketRole>();

private ulong? guildId;

internal SocketMessage Message { get; private set; }

private ApplicationCommandType Type { get; set; }

internal SocketCommandBaseData(DiscordSocketClient client, Model model, ulong? guildId)
: base(client, model.Id)
{
this.guildId = guildId;

this.Type = (ApplicationCommandType)model.Type;

if (model.Resolved.IsSpecified)
{
var guild = this.guildId.HasValue ? Discord.GetGuild(this.guildId.Value) : null;

var resolved = model.Resolved.Value;

if (resolved.Users.IsSpecified)
{
foreach (var user in resolved.Users.Value)
{
var socketUser = Discord.GetOrCreateUser(this.Discord.State, user.Value);

this.users.Add(ulong.Parse(user.Key), socketUser);
}
}

if (resolved.Channels.IsSpecified)
{
foreach (var channel in resolved.Channels.Value)
{
SocketChannel socketChannel = guild != null
? guild.GetChannel(channel.Value.Id)
: Discord.GetChannel(channel.Value.Id);

if (socketChannel == null)
{
var channelModel = guild != null
? Discord.Rest.ApiClient.GetChannelAsync(guild.Id, channel.Value.Id).ConfigureAwait(false).GetAwaiter().GetResult()
: Discord.Rest.ApiClient.GetChannelAsync(channel.Value.Id).ConfigureAwait(false).GetAwaiter().GetResult();

socketChannel = guild != null
? SocketGuildChannel.Create(guild, Discord.State, channelModel)
: (SocketChannel)SocketChannel.CreatePrivate(Discord, Discord.State, channelModel);
}

Discord.State.AddChannel(socketChannel);
this.channels.Add(ulong.Parse(channel.Key), socketChannel);
}
}

if (resolved.Members.IsSpecified)
{
foreach (var member in resolved.Members.Value)
{
member.Value.User = resolved.Users.Value[member.Key];
var user = guild.AddOrUpdateUser(member.Value);
this.guildMembers.Add(ulong.Parse(member.Key), user);
}
}

if (resolved.Roles.IsSpecified)
{
foreach (var role in resolved.Roles.Value)
{
var socketRole = guild.AddOrUpdateRole(role.Value);
this.roles.Add(ulong.Parse(role.Key), socketRole);
}
}

if (resolved.Messages.IsSpecified)
{
foreach (var msg in resolved.Messages.Value)
{
var channel = client.GetChannel(msg.Value.ChannelId) as ISocketMessageChannel;

SocketUser author;
if (guild != null)
{
if (msg.Value.WebhookId.IsSpecified)
author = SocketWebhookUser.Create(guild, client.State, msg.Value.Author.Value, msg.Value.WebhookId.Value);
else
author = guild.GetUser(msg.Value.Author.Value.Id);
}
else
author = (channel as SocketChannel).GetUser(msg.Value.Author.Value.Id);

if (channel == null)
{
if (!msg.Value.GuildId.IsSpecified) // assume it is a DM
{
channel = client.CreateDMChannel(msg.Value.ChannelId, msg.Value.Author.Value, client.State);
}
}

this.Message = SocketMessage.Create(client, client.State, author, channel, msg.Value);
}
}
}
}

internal static SocketCommandBaseData Create(DiscordSocketClient client, Model model, ulong id, ulong? guildId)
{
var entity = new SocketCommandBaseData(client, model, guildId);
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 SocketCommandBaseDataOption(this, x)).ToImmutableArray()
: null;
}

IReadOnlyCollection<IApplicationCommandInteractionDataOption> IApplicationCommandInteractionData.Options => Options;
}
}

+ 130
- 0
src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBaseDataOption.cs View File

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

namespace Discord.WebSocket
{
public class SocketCommandBaseDataOption : IApplicationCommandInteractionDataOption
{
public string Name { get; private set; }

/// <inheritdoc/>
public object Value { get; private set; }

/// <inheritdoc/>
public ApplicationCommandOptionType Type { get; private set; }

/// <summary>
/// The sub command options received for this sub command group.
/// </summary>
public IReadOnlyCollection<IApplicationCommandInteractionDataOption> Options { get; private set; }

internal SocketCommandBaseDataOption() { }
internal SocketCommandBaseDataOption(SocketCommandBaseData data, Model model)
{
this.Name = model.Name;
this.Type = model.Type;

if (model.Value.IsSpecified)
{
switch (Type)
{
case ApplicationCommandOptionType.User:
case ApplicationCommandOptionType.Role:
case ApplicationCommandOptionType.Channel:
case ApplicationCommandOptionType.Mentionable:
if (ulong.TryParse($"{model.Value.Value}", out var valueId))
{
switch (this.Type)
{
case ApplicationCommandOptionType.User:
{
var guildUser = data.guildMembers.FirstOrDefault(x => x.Key == valueId).Value;

if (guildUser != null)
this.Value = guildUser;
else
this.Value = data.users.FirstOrDefault(x => x.Key == valueId).Value;
}
break;
case ApplicationCommandOptionType.Channel:
this.Value = data.channels.FirstOrDefault(x => x.Key == valueId).Value;
break;
case ApplicationCommandOptionType.Role:
this.Value = data.roles.FirstOrDefault(x => x.Key == valueId).Value;
break;
case ApplicationCommandOptionType.Mentionable:
{
if (data.guildMembers.Any(x => x.Key == valueId) || data.users.Any(x => x.Key == valueId))
{
var guildUser = data.guildMembers.FirstOrDefault(x => x.Key == valueId).Value;

if (guildUser != null)
this.Value = guildUser;
else
this.Value = data.users.FirstOrDefault(x => x.Key == valueId).Value;
}
else if (data.roles.Any(x => x.Key == valueId))
{
this.Value = data.roles.FirstOrDefault(x => x.Key == valueId).Value;
}
}
break;
default:
this.Value = model.Value.Value;
break;
}
}
break;
case ApplicationCommandOptionType.String:
this.Value = model.Value.ToString();
break;
case ApplicationCommandOptionType.Integer:
{
if (model.Value.Value is int val)
this.Value = val;
else if (int.TryParse(model.Value.Value.ToString(), out int res))
this.Value = res;
}
break;
case ApplicationCommandOptionType.Boolean:
{
if (model.Value.Value is bool val)
this.Value = val;
else if (bool.TryParse(model.Value.Value.ToString(), out bool res))
this.Value = res;
}
break;
case ApplicationCommandOptionType.Number:
{
if (model.Value.Value is int val)
this.Value = val;
else if (double.TryParse(model.Value.Value.ToString(), out double res))
this.Value = res;
}
break;
}

}

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

// Converters
public static explicit operator bool(SocketCommandBaseDataOption option)
=> (bool)option.Value;
public static explicit operator int(SocketCommandBaseDataOption option)
=> (int)option.Value;
public static explicit operator string(SocketCommandBaseDataOption option)
=> option.Value.ToString();

IReadOnlyCollection<IApplicationCommandInteractionDataOption> IApplicationCommandInteractionDataOption.Options => this.Options;
}
}

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

@@ -62,12 +62,13 @@ namespace Discord.WebSocket
internal static SocketInteraction Create(DiscordSocketClient client, Model model, ISocketMessageChannel channel) internal static SocketInteraction Create(DiscordSocketClient client, Model model, ISocketMessageChannel channel)
{ {
if (model.Type == InteractionType.ApplicationCommand) if (model.Type == InteractionType.ApplicationCommand)
if(model.ApplicationId != null)
{
if (model.ApplicationId != null)
{ {
var dataModel = model.Data.IsSpecified ? var dataModel = model.Data.IsSpecified ?
(DataModel)model.Data.Value (DataModel)model.Data.Value
: null; : null;
if(dataModel != null)
if (dataModel != null)
{ {
if (dataModel.Type.Equals(ApplicationCommandType.User)) if (dataModel.Type.Equals(ApplicationCommandType.User))
return SocketApplicationUserCommand.Create(client, model, channel); return SocketApplicationUserCommand.Create(client, model, channel);
@@ -76,6 +77,7 @@ namespace Discord.WebSocket
} }
} }
return SocketSlashCommand.Create(client, model, channel); return SocketSlashCommand.Create(client, model, channel);
}
if (model.Type == InteractionType.MessageComponent) if (model.Type == InteractionType.MessageComponent)
return SocketMessageComponent.Create(client, model, channel); return SocketMessageComponent.Create(client, model, channel);
else else


Loading…
Cancel
Save