Browse Source

Implement checks for interaction respond times and multiple interaction responses. closes #236, #235

pull/1923/head
quin lynch 3 years ago
parent
commit
4c9b396fcc
4 changed files with 131 additions and 19 deletions
  1. +12
    -0
      src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs
  2. +76
    -9
      src/Discord.Net.WebSocket/Entities/Interaction/Message Components/SocketMessageComponent.cs
  3. +39
    -3
      src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBase.cs
  4. +4
    -7
      src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs

+ 12
- 0
src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs View File

@@ -11,7 +11,19 @@ namespace Discord.Rest
{
internal static class InteractionHelper
{
public const double ResponseTimeLimit = 3;
public const double ResponseAndFollowupLimit = 15;

#region InteractionHelper
public static bool CanSendResponse(IDiscordInteraction interaction)
{
return (DateTime.UtcNow - interaction.CreatedAt).TotalSeconds < ResponseTimeLimit;
}
public static bool CanRespondOrFollowup(IDiscordInteraction interaction)
{
return (DateTime.UtcNow - interaction.CreatedAt).TotalMinutes <= ResponseAndFollowupLimit;
}

public static Task DeleteAllGuildCommandsAsync(BaseDiscordClient client, ulong guildId, RequestOptions options = null)
{
return client.ApiClient.BulkOverwriteGuildApplicationCommandsAsync(guildId, Array.Empty<CreateApplicationCommandParams>(), options);


+ 76
- 9
src/Discord.Net.WebSocket/Entities/Interaction/Message Components/SocketMessageComponent.cs View File

@@ -25,6 +25,9 @@ namespace Discord.WebSocket
/// </summary>
public SocketUserMessage Message { get; private set; }

private object _lock = new object();
internal override bool _hasResponded { get; set; } = false;

internal SocketMessageComponent(DiscordSocketClient client, Model model, ISocketMessageChannel channel)
: base(client, model.Id, channel)
{
@@ -82,6 +85,9 @@ namespace Discord.WebSocket
if (!IsValidToken)
throw new InvalidOperationException("Interaction token is no longer valid");

if (!InteractionHelper.CanSendResponse(this))
throw new TimeoutException($"Cannot respond to an interaction after {InteractionHelper.ResponseTimeLimit} seconds!");

embeds ??= Array.Empty<Embed>();
if (embed != null)
embeds = new[] { embed }.Concat(embeds).ToArray();
@@ -122,7 +128,20 @@ namespace Discord.WebSocket
if (ephemeral)
response.Data.Value.Flags = MessageFlags.Ephemeral;

await InteractionHelper.SendInteractionResponseAsync(Discord, response, Id, Token, options);
lock (_lock)
{
if (_hasResponded)
{
throw new InvalidOperationException("Cannot respond, update, or defer twice to the same interaction");
}
}

await InteractionHelper.SendInteractionResponseAsync(Discord, response, Id, Token, options).ConfigureAwait(false);

lock (_lock)
{
_hasResponded = true;
}
}

/// <summary>
@@ -139,6 +158,9 @@ namespace Discord.WebSocket
if (!IsValidToken)
throw new InvalidOperationException("Interaction token is no longer valid");

if (!InteractionHelper.CanSendResponse(this))
throw new TimeoutException($"Cannot respond to an interaction after {InteractionHelper.ResponseTimeLimit} seconds!");

if (args.AllowedMentions.IsSpecified)
{
var allowedMentions = args.AllowedMentions.Value;
@@ -201,7 +223,20 @@ namespace Discord.WebSocket
}
};

await InteractionHelper.SendInteractionResponseAsync(Discord, response, Id, Token, options);
lock (_lock)
{
if (_hasResponded)
{
throw new InvalidOperationException("Cannot respond, update, or defer twice to the same interaction");
}
}

await InteractionHelper.SendInteractionResponseAsync(Discord, response, Id, Token, options).ConfigureAwait(false);

lock (_lock)
{
_hasResponded = true;
}
}

/// <inheritdoc/>
@@ -238,7 +273,7 @@ namespace Discord.WebSocket
if (ephemeral)
args.Flags = MessageFlags.Ephemeral;

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

/// <inheritdoc/>
@@ -280,7 +315,7 @@ namespace Discord.WebSocket
if (ephemeral)
args.Flags = MessageFlags.Ephemeral;

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

/// <inheritdoc/>
@@ -321,7 +356,7 @@ namespace Discord.WebSocket
if (ephemeral)
args.Flags = MessageFlags.Ephemeral;

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

/// <summary>
@@ -332,27 +367,59 @@ namespace Discord.WebSocket
/// <returns>
/// A task that represents the asynchronous operation of acknowledging the interaction.
/// </returns>
public Task DeferLoadingAsync(bool ephemeral = false, RequestOptions options = null)
public async Task DeferLoadingAsync(bool ephemeral = false, RequestOptions options = null)
{
if (!InteractionHelper.CanSendResponse(this))
throw new TimeoutException($"Cannot defer an interaction after {InteractionHelper.ResponseTimeLimit} seconds of no response/acknowledgement");

var response = new API.InteractionResponse
{
Type = InteractionResponseType.DeferredChannelMessageWithSource,
Data = ephemeral ? new API.InteractionCallbackData { Flags = MessageFlags.Ephemeral } : Optional<API.InteractionCallbackData>.Unspecified
};

return Discord.Rest.ApiClient.CreateInteractionResponseAsync(response, Id, Token, options);
lock (_lock)
{
if (_hasResponded)
{
throw new InvalidOperationException("Cannot respond or defer twice to the same interaction");
}
}

await Discord.Rest.ApiClient.CreateInteractionResponseAsync(response, Id, Token, options).ConfigureAwait(false);

lock (_lock)
{
_hasResponded = true;
}
}

/// <inheritdoc/>
public override Task DeferAsync(bool ephemeral = false, RequestOptions options = null)
public override async Task DeferAsync(bool ephemeral = false, RequestOptions options = null)
{
if (!InteractionHelper.CanSendResponse(this))
throw new TimeoutException($"Cannot defer an interaction after {InteractionHelper.ResponseTimeLimit} seconds of no response/acknowledgement");

var response = new API.InteractionResponse
{
Type = InteractionResponseType.DeferredUpdateMessage,
Data = ephemeral ? new API.InteractionCallbackData { Flags = MessageFlags.Ephemeral } : Optional<API.InteractionCallbackData>.Unspecified
};

return Discord.Rest.ApiClient.CreateInteractionResponseAsync(response, Id, Token, options);
lock (_lock)
{
if (_hasResponded)
{
throw new InvalidOperationException("Cannot respond or defer twice to the same interaction");
}
}

await Discord.Rest.ApiClient.CreateInteractionResponseAsync(response, Id, Token, options).ConfigureAwait(false);

lock (_lock)
{
_hasResponded = true;
}
}
}
}

+ 39
- 3
src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBase.cs View File

@@ -31,6 +31,10 @@ namespace Discord.WebSocket
/// </summary>
internal new SocketCommandBaseData Data { get; }

internal override bool _hasResponded { get; set; }

private object _lock = new object();

internal SocketCommandBase(DiscordSocketClient client, Model model, ISocketMessageChannel channel)
: base(client, model.Id, channel)
{
@@ -77,6 +81,9 @@ namespace Discord.WebSocket
if (!IsValidToken)
throw new InvalidOperationException("Interaction token is no longer valid");

if (!InteractionHelper.CanSendResponse(this))
throw new TimeoutException($"Cannot respond to an interaction after {InteractionHelper.ResponseTimeLimit} seconds!");

embeds ??= Array.Empty<Embed>();
if (embed != null)
embeds = new[] { embed }.Concat(embeds).ToArray();
@@ -115,7 +122,20 @@ namespace Discord.WebSocket
}
};

await InteractionHelper.SendInteractionResponseAsync(Discord, response, Id, Token, options);
lock (_lock)
{
if (_hasResponded)
{
throw new InvalidOperationException("Cannot respond twice to the same interaction");
}
}

await InteractionHelper.SendInteractionResponseAsync(Discord, response, Id, Token, options).ConfigureAwait(false);

lock (_lock)
{
_hasResponded = true;
}
}

/// <inheritdoc/>
@@ -247,8 +267,11 @@ namespace Discord.WebSocket
/// <returns>
/// A task that represents the asynchronous operation of acknowledging the interaction.
/// </returns>
public override Task DeferAsync(bool ephemeral = false, RequestOptions options = null)
public override async Task DeferAsync(bool ephemeral = false, RequestOptions options = null)
{
if (!InteractionHelper.CanSendResponse(this))
throw new TimeoutException($"Cannot defer an interaction after {InteractionHelper.ResponseTimeLimit} seconds!");

var response = new API.InteractionResponse
{
Type = InteractionResponseType.DeferredChannelMessageWithSource,
@@ -258,7 +281,20 @@ namespace Discord.WebSocket
}
};

return Discord.Rest.ApiClient.CreateInteractionResponseAsync(response, Id, Token, options);
lock (_lock)
{
if (_hasResponded)
{
throw new InvalidOperationException("Cannot respond or defer twice to the same interaction");
}
}

await Discord.Rest.ApiClient.CreateInteractionResponseAsync(response, Id, Token, options).ConfigureAwait(false);

lock (_lock)
{
_hasResponded = true;
}
}
}
}

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

@@ -47,11 +47,13 @@ namespace Discord.WebSocket
public DateTimeOffset CreatedAt
=> SnowflakeUtils.FromSnowflake(Id);

internal abstract bool _hasResponded { get; set; }

/// <summary>
/// <see langword="true"/> if the token is valid for replying to, otherwise <see langword="false"/>.
/// </summary>
public bool IsValidToken
=> CheckToken();
=> InteractionHelper.CanRespondOrFollowup(this);

internal SocketInteraction(DiscordSocketClient client, ulong id, ISocketMessageChannel channel)
: base(client, id)
@@ -210,12 +212,7 @@ namespace Discord.WebSocket
/// A task that represents the asynchronous operation of acknowledging the interaction.
/// </returns>
public abstract Task DeferAsync(bool ephemeral = false, RequestOptions options = null);

private bool CheckToken()
{
// Tokens last for 15 minutes according to https://discord.com/developers/docs/interactions/slash-commands#responding-to-an-interaction
return (DateTime.UtcNow - CreatedAt.UtcDateTime).TotalMinutes <= 15d;
}
#endregion

#region IDiscordInteraction


Loading…
Cancel
Save