[Feature] Premium subscriptions (#2781)
* what a big commit lel, add app sub enums * work * ah yup lol * `?` * events 1 * typo * `list` => `get` | remaining events * add `RespondWithPremiumRequiredAsync` to interaction module base
This commit is contained in:
@@ -237,5 +237,10 @@ namespace Discord
|
|||||||
/// Returns the max amount of tags applied to an application.
|
/// Returns the max amount of tags applied to an application.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const int MaxApplicationTagCount = 5;
|
public const int MaxApplicationTagCount = 5;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the maximum number of entitlements that can be gotten per-batch.
|
||||||
|
/// </summary>
|
||||||
|
public const int MaxEntitlementsPerBatch = 100;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
namespace Discord;
|
||||||
|
|
||||||
|
public enum EntitlementType
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The entitlement was purchased as an app subscription.
|
||||||
|
/// </summary>
|
||||||
|
ApplicationSubscription = 8
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Discord;
|
||||||
|
|
||||||
|
public interface IEntitlement : ISnowflakeEntity
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the ID of the SKU this entitlement is for.
|
||||||
|
/// </summary>
|
||||||
|
ulong SkuId { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the ID of the user that is granted access to the entitlement's SKU.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// <see langword="null"/> if the entitlement is for a guild.
|
||||||
|
/// </remarks>
|
||||||
|
ulong? UserId { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the ID of the guild that is granted access to the entitlement's SKU.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// <see langword="null"/> if the entitlement is for a user.
|
||||||
|
/// </remarks>
|
||||||
|
ulong? GuildId { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the ID of the parent application.
|
||||||
|
/// </summary>
|
||||||
|
ulong ApplicationId { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the type of the entitlement.
|
||||||
|
/// </summary>
|
||||||
|
EntitlementType Type { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets whether this entitlement has been consumed.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Not applicable for App Subscriptions.
|
||||||
|
/// </remarks>
|
||||||
|
bool IsConsumed { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the start date at which the entitlement is valid.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// <see langword="null"/> when using test entitlements.
|
||||||
|
/// </remarks>
|
||||||
|
DateTimeOffset? StartsAt { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the end date at which the entitlement is no longer valid.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// <see langword="null"/> when using test entitlements.
|
||||||
|
/// </remarks>
|
||||||
|
DateTimeOffset? EndsAt { get; }
|
||||||
|
}
|
||||||
41
src/Discord.Net.Core/Entities/AppSubscriptions/SKU.cs
Normal file
41
src/Discord.Net.Core/Entities/AppSubscriptions/SKU.cs
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Discord;
|
||||||
|
|
||||||
|
public struct SKU : ISnowflakeEntity
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ulong Id { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the type of the SKU.
|
||||||
|
/// </summary>
|
||||||
|
public SKUType Type { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the ID of the parent application.
|
||||||
|
/// </summary>
|
||||||
|
public ulong ApplicationId { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the customer-facing name of your premium offering.
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the system-generated URL slug based on the SKU's name.
|
||||||
|
/// </summary>
|
||||||
|
public string Slug { get; }
|
||||||
|
|
||||||
|
internal SKU(ulong id, SKUType type, ulong applicationId, string name, string slug)
|
||||||
|
{
|
||||||
|
Id = id;
|
||||||
|
Type = type;
|
||||||
|
ApplicationId = applicationId;
|
||||||
|
Name = name;
|
||||||
|
Slug = slug;
|
||||||
|
}
|
||||||
|
}
|
||||||
11
src/Discord.Net.Core/Entities/AppSubscriptions/SKUFlags.cs
Normal file
11
src/Discord.Net.Core/Entities/AppSubscriptions/SKUFlags.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Discord;
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
public enum SKUFlags
|
||||||
|
{
|
||||||
|
GuildSubscription = 1 << 7,
|
||||||
|
|
||||||
|
UserSubscription = 1 << 8
|
||||||
|
}
|
||||||
14
src/Discord.Net.Core/Entities/AppSubscriptions/SKUType.cs
Normal file
14
src/Discord.Net.Core/Entities/AppSubscriptions/SKUType.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
namespace Discord;
|
||||||
|
|
||||||
|
public enum SKUType
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a recurring subscription.
|
||||||
|
/// </summary>
|
||||||
|
Subscription = 5,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// System-generated group for each <see cref="SKUType.Subscription"/> SKU created.
|
||||||
|
/// </summary>
|
||||||
|
SubscriptionGroup = 6,
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
namespace Discord;
|
||||||
|
|
||||||
|
public enum SubscriptionOwnerType
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The owner of the application subscription is a guild.
|
||||||
|
/// </summary>
|
||||||
|
Guild = 1,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The owner of the application subscription is a user.
|
||||||
|
/// </summary>
|
||||||
|
User = 2,
|
||||||
|
}
|
||||||
@@ -91,6 +91,11 @@ namespace Discord
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
ulong ApplicationId { get; }
|
ulong ApplicationId { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets entitlements for the invoking user.
|
||||||
|
/// </summary>
|
||||||
|
IReadOnlyCollection<IEntitlement> Entitlements { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Responds to an Interaction with type <see cref="InteractionResponseType.ChannelMessageWithSource"/>.
|
/// Responds to an Interaction with type <see cref="InteractionResponseType.ChannelMessageWithSource"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -368,5 +373,12 @@ namespace Discord
|
|||||||
/// <param name="options">The request options for this <see langword="async"/> request.</param>
|
/// <param name="options">The request options for this <see langword="async"/> request.</param>
|
||||||
/// <returns>A task that represents the asynchronous operation of responding to the interaction.</returns>
|
/// <returns>A task that represents the asynchronous operation of responding to the interaction.</returns>
|
||||||
Task RespondWithModalAsync(Modal modal, RequestOptions options = null);
|
Task RespondWithModalAsync(Modal modal, RequestOptions options = null);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Responds to the interaction with an ephemeral message the invoking user,
|
||||||
|
/// instructing them that whatever they tried to do requires the premium benefits of your app.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A task that represents the asynchronous operation of responding to the interaction.</returns>
|
||||||
|
Task RespondWithPremiumRequiredAsync(RequestOptions options = null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,5 +47,10 @@ namespace Discord
|
|||||||
/// Respond by showing the user a modal.
|
/// Respond by showing the user a modal.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Modal = 9,
|
Modal = 9,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Respond to an interaction with an upgrade button, only available for apps with monetization enabled.
|
||||||
|
/// </summary>
|
||||||
|
PremiumRequired = 10
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -327,5 +327,27 @@ namespace Discord
|
|||||||
/// that represents the gateway information related to the bot.
|
/// that represents the gateway information related to the bot.
|
||||||
/// </returns>
|
/// </returns>
|
||||||
Task<BotGateway> GetBotGatewayAsync(RequestOptions options = null);
|
Task<BotGateway> GetBotGatewayAsync(RequestOptions options = null);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a test entitlement to a given SKU for a given guild or user.
|
||||||
|
/// </summary>
|
||||||
|
Task<IEntitlement> CreateTestEntitlementAsync(ulong skuId, ulong ownerId, SubscriptionOwnerType ownerType, RequestOptions options = null);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Deletes a currently-active test entitlement.
|
||||||
|
/// </summary>
|
||||||
|
Task DeleteTestEntitlementAsync(ulong entitlementId, RequestOptions options = null);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns all entitlements for a given app, active and expired.
|
||||||
|
/// </summary>
|
||||||
|
IAsyncEnumerable<IReadOnlyCollection<IEntitlement>> GetEntitlementsAsync(int? limit = 100,
|
||||||
|
ulong? afterId = null, ulong? beforeId = null, bool excludeEnded = false, ulong? guildId = null, ulong? userId = null,
|
||||||
|
ulong[] skuIds = null, RequestOptions options = null);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns all SKUs for a given application.
|
||||||
|
/// </summary>
|
||||||
|
Task<IReadOnlyCollection<SKU>> GetSKUsAsync(RequestOptions options = null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -123,6 +123,10 @@ namespace Discord.Interactions
|
|||||||
protected virtual async Task RespondWithModalAsync<TModal>(string customId, RequestOptions options = null) where TModal : class, IModal
|
protected virtual async Task RespondWithModalAsync<TModal>(string customId, RequestOptions options = null) where TModal : class, IModal
|
||||||
=> await Context.Interaction.RespondWithModalAsync<TModal>(customId, options);
|
=> await Context.Interaction.RespondWithModalAsync<TModal>(customId, options);
|
||||||
|
|
||||||
|
/// <inheritdoc cref="IDiscordInteraction.RespondWithPremiumRequiredAsync(RequestOptions)"/>
|
||||||
|
protected virtual Task RespondWithPremiumRequiredAsync(RequestOptions options = null)
|
||||||
|
=> Context.Interaction.RespondWithPremiumRequiredAsync(options);
|
||||||
|
|
||||||
//IInteractionModuleBase
|
//IInteractionModuleBase
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
|||||||
34
src/Discord.Net.Rest/API/Common/Entitlement.cs
Normal file
34
src/Discord.Net.Rest/API/Common/Entitlement.cs
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
using Newtonsoft.Json;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Discord.API;
|
||||||
|
|
||||||
|
internal class Entitlement
|
||||||
|
{
|
||||||
|
[JsonProperty("id")]
|
||||||
|
public ulong Id { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("sku_id")]
|
||||||
|
public ulong SkuId { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("user_id")]
|
||||||
|
public Optional<ulong> UserId { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("guild_id")]
|
||||||
|
public Optional<ulong> GuildId { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("application_id")]
|
||||||
|
public ulong ApplicationId { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("type")]
|
||||||
|
public EntitlementType Type { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("consumed")]
|
||||||
|
public bool IsConsumed { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("starts_at")]
|
||||||
|
public Optional<DateTimeOffset> StartsAt { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("ends_at")]
|
||||||
|
public Optional<DateTimeOffset> EndsAt { get; set; }
|
||||||
|
}
|
||||||
@@ -46,5 +46,8 @@ namespace Discord.API
|
|||||||
|
|
||||||
[JsonProperty("guild_locale")]
|
[JsonProperty("guild_locale")]
|
||||||
public Optional<string> GuildLocale { get; set; }
|
public Optional<string> GuildLocale { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("entitlements")]
|
||||||
|
public Entitlement[] Entitlements { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
21
src/Discord.Net.Rest/API/Common/SKU.cs
Normal file
21
src/Discord.Net.Rest/API/Common/SKU.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace Discord.API;
|
||||||
|
|
||||||
|
internal class SKU
|
||||||
|
{
|
||||||
|
[JsonProperty("id")]
|
||||||
|
public ulong Id { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("type")]
|
||||||
|
public SKUType Type { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("application_id")]
|
||||||
|
public ulong ApplicationId { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("name")]
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("slug")]
|
||||||
|
public string Slug { get; set; }
|
||||||
|
}
|
||||||
15
src/Discord.Net.Rest/API/Rest/CreateEntitlementParams.cs
Normal file
15
src/Discord.Net.Rest/API/Rest/CreateEntitlementParams.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace Discord.API.Rest;
|
||||||
|
|
||||||
|
internal class CreateEntitlementParams
|
||||||
|
{
|
||||||
|
[JsonProperty("sku_id")]
|
||||||
|
public ulong SkuId { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("owner_id")]
|
||||||
|
public ulong OwnerId { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("owner_type")]
|
||||||
|
public SubscriptionOwnerType Type { get; set; }
|
||||||
|
}
|
||||||
18
src/Discord.Net.Rest/API/Rest/ListEntitlementsParams.cs
Normal file
18
src/Discord.Net.Rest/API/Rest/ListEntitlementsParams.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
namespace Discord.API.Rest;
|
||||||
|
|
||||||
|
internal class ListEntitlementsParams
|
||||||
|
{
|
||||||
|
public Optional<ulong> UserId { get; set; }
|
||||||
|
|
||||||
|
public Optional<ulong[]> SkuIds { get; set; }
|
||||||
|
|
||||||
|
public Optional<ulong> BeforeId { get; set; }
|
||||||
|
|
||||||
|
public Optional<ulong> AfterId { get; set; }
|
||||||
|
|
||||||
|
public Optional<int> Limit { get; set; }
|
||||||
|
|
||||||
|
public Optional<ulong> GuildId { get; set; }
|
||||||
|
|
||||||
|
public Optional<bool> ExcludeEnded { get; set; }
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
@@ -258,6 +259,30 @@ namespace Discord.Rest
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
Task IDiscordClient.StopAsync()
|
Task IDiscordClient.StopAsync()
|
||||||
=> Task.Delay(0);
|
=> Task.Delay(0);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a test entitlement to a given SKU for a given guild or user.
|
||||||
|
/// </summary>
|
||||||
|
Task<IEntitlement> IDiscordClient.CreateTestEntitlementAsync(ulong skuId, ulong ownerId, SubscriptionOwnerType ownerType, RequestOptions options)
|
||||||
|
=> Task.FromResult<IEntitlement>(null);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Deletes a currently-active test entitlement.
|
||||||
|
/// </summary>
|
||||||
|
Task IDiscordClient.DeleteTestEntitlementAsync(ulong entitlementId, RequestOptions options)
|
||||||
|
=> Task.CompletedTask;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns all entitlements for a given app.
|
||||||
|
/// </summary>
|
||||||
|
IAsyncEnumerable<IReadOnlyCollection<IEntitlement>> IDiscordClient.GetEntitlementsAsync(int? limit, ulong? afterId, ulong? beforeId,
|
||||||
|
bool excludeEnded, ulong? guildId, ulong? userId, ulong[] skuIds, RequestOptions options) => AsyncEnumerable.Empty<IReadOnlyCollection<IEntitlement>>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets all SKUs for a given application.
|
||||||
|
/// </summary>
|
||||||
|
Task<IReadOnlyCollection<SKU>> IDiscordClient.GetSKUsAsync(RequestOptions options) => Task.FromResult<IReadOnlyCollection<SKU>>(Array.Empty<SKU>());
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -379,6 +379,66 @@ namespace Discord.Rest
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region App Subscriptions
|
||||||
|
|
||||||
|
public static async Task<RestEntitlement> CreateTestEntitlementAsync(BaseDiscordClient client, ulong skuId, ulong ownerId, SubscriptionOwnerType ownerType,
|
||||||
|
RequestOptions options = null)
|
||||||
|
{
|
||||||
|
var model = await client.ApiClient.CreateEntitlementAsync(new CreateEntitlementParams
|
||||||
|
{
|
||||||
|
Type = ownerType,
|
||||||
|
OwnerId = ownerId,
|
||||||
|
SkuId = skuId
|
||||||
|
}, options);
|
||||||
|
|
||||||
|
return RestEntitlement.Create(client, model);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IAsyncEnumerable<IReadOnlyCollection<RestEntitlement>> ListEntitlementsAsync(BaseDiscordClient client, int? limit = 100,
|
||||||
|
ulong? afterId = null, ulong? beforeId = null, bool excludeEnded = false, ulong? guildId = null, ulong? userId = null,
|
||||||
|
ulong[] skuIds = null, RequestOptions options = null)
|
||||||
|
{
|
||||||
|
return new PagedAsyncEnumerable<RestEntitlement>(
|
||||||
|
DiscordConfig.MaxEntitlementsPerBatch,
|
||||||
|
async (info, ct) =>
|
||||||
|
{
|
||||||
|
var args = new ListEntitlementsParams()
|
||||||
|
{
|
||||||
|
Limit = info.PageSize,
|
||||||
|
BeforeId = beforeId ?? Optional<ulong>.Unspecified,
|
||||||
|
ExcludeEnded = excludeEnded,
|
||||||
|
GuildId = guildId ?? Optional<ulong>.Unspecified,
|
||||||
|
UserId = userId ?? Optional<ulong>.Unspecified,
|
||||||
|
SkuIds = skuIds ?? Optional<ulong[]>.Unspecified,
|
||||||
|
};
|
||||||
|
if (info.Position != null)
|
||||||
|
args.AfterId = info.Position.Value;
|
||||||
|
var models = await client.ApiClient.ListEntitlementAsync(args, options).ConfigureAwait(false);
|
||||||
|
return models
|
||||||
|
.Select(x => RestEntitlement.Create(client, x))
|
||||||
|
.ToImmutableArray();
|
||||||
|
},
|
||||||
|
nextPage: (info, lastPage) =>
|
||||||
|
{
|
||||||
|
if (lastPage.Count != DiscordConfig.MaxEntitlementsPerBatch)
|
||||||
|
return false;
|
||||||
|
info.Position = lastPage.Max(x => x.Id);
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
start: afterId,
|
||||||
|
count: limit
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<IReadOnlyCollection<SKU>> ListSKUsAsync(BaseDiscordClient client, RequestOptions options = null)
|
||||||
|
{
|
||||||
|
var models = await client.ApiClient.ListSKUsAsync(options).ConfigureAwait(false);
|
||||||
|
|
||||||
|
return models.Select(x => new SKU(x.Id, x.Type, x.ApplicationId, x.Name, x.Slug)).ToImmutableArray();
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ using Discord.Net.Rest;
|
|||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel.Design;
|
using System.ComponentModel.Design;
|
||||||
@@ -2254,7 +2255,7 @@ namespace Discord.API
|
|||||||
return await SendAsync<GuildOnboarding>("GET", () => $"guilds/{guildId}/onboarding", new BucketIds(guildId: guildId), options: options);
|
return await SendAsync<GuildOnboarding>("GET", () => $"guilds/{guildId}/onboarding", new BucketIds(guildId: guildId), options: options);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<GuildOnboarding> ModifyGuildOnboardingAsync(ulong guildId, ModifyGuildOnboardingParams args, RequestOptions options)
|
public async Task<GuildOnboarding> ModifyGuildOnboardingAsync(ulong guildId, ModifyGuildOnboardingParams args, RequestOptions options)
|
||||||
{
|
{
|
||||||
Preconditions.NotEqual(guildId, 0, nameof(guildId));
|
Preconditions.NotEqual(guildId, 0, nameof(guildId));
|
||||||
|
|
||||||
@@ -2689,5 +2690,55 @@ namespace Discord.API
|
|||||||
=> await SendJsonAsync<RoleConnection>("PUT", () => $"users/@me/applications/{applicationId}/role-connection", connection, new BucketIds(), options: options);
|
=> await SendJsonAsync<RoleConnection>("PUT", () => $"users/@me/applications/{applicationId}/role-connection", connection, new BucketIds(), options: options);
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region App Monetization
|
||||||
|
|
||||||
|
public async Task<Entitlement> CreateEntitlementAsync(CreateEntitlementParams args, RequestOptions options = null)
|
||||||
|
=> await SendJsonAsync<Entitlement>("POST", () => $"applications/{CurrentApplicationId}/entitlements", args, new BucketIds(), options: options).ConfigureAwait(false);
|
||||||
|
|
||||||
|
public async Task DeleteEntitlementAsync(ulong entitlementId, RequestOptions options = null)
|
||||||
|
=> await SendAsync("DELETE", () => $"applications/{CurrentApplicationId}/entitlements/{entitlementId}", new BucketIds(), options: options).ConfigureAwait(false);
|
||||||
|
|
||||||
|
public async Task<Entitlement[]> ListEntitlementAsync(ListEntitlementsParams args, RequestOptions options = null)
|
||||||
|
{
|
||||||
|
var query = $"?limit={args.Limit.GetValueOrDefault(100)}";
|
||||||
|
|
||||||
|
if (args.UserId.IsSpecified)
|
||||||
|
{
|
||||||
|
query += $"&user_id={args.UserId.Value}";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.SkuIds.IsSpecified)
|
||||||
|
{
|
||||||
|
query += $"&sku_ids={WebUtility.UrlEncode(string.Join(",", args.SkuIds.Value))}";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.BeforeId.IsSpecified)
|
||||||
|
{
|
||||||
|
query += $"&before={args.BeforeId.Value}";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.AfterId.IsSpecified)
|
||||||
|
{
|
||||||
|
query += $"&after={args.AfterId.Value}";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.GuildId.IsSpecified)
|
||||||
|
{
|
||||||
|
query += $"&guild_id={args.GuildId.Value}";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.ExcludeEnded.IsSpecified)
|
||||||
|
{
|
||||||
|
query += $"&exclude_ended={args.ExcludeEnded.Value}";
|
||||||
|
}
|
||||||
|
|
||||||
|
return await SendAsync<Entitlement[]>("GET", () => $"applications/{CurrentApplicationId}/entitlements{query}", new BucketIds(), options: options).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<SKU[]> ListSKUsAsync(RequestOptions options = null)
|
||||||
|
=> await SendAsync<SKU[]>("GET", () => $"applications/{CurrentApplicationId}/skus", new BucketIds(), options: options).ConfigureAwait(false);
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -278,9 +278,30 @@ namespace Discord.Rest
|
|||||||
public Task<RoleConnection> ModifyUserApplicationRoleConnectionAsync(ulong applicationId, RoleConnectionProperties roleConnection, RequestOptions options = null)
|
public Task<RoleConnection> ModifyUserApplicationRoleConnectionAsync(ulong applicationId, RoleConnectionProperties roleConnection, RequestOptions options = null)
|
||||||
=> ClientHelper.ModifyUserRoleConnectionAsync(applicationId, roleConnection, this, options);
|
=> ClientHelper.ModifyUserRoleConnectionAsync(applicationId, roleConnection, this, options);
|
||||||
|
|
||||||
|
/// <inheritdoc cref="IDiscordClient.CreateTestEntitlementAsync" />
|
||||||
|
public Task<RestEntitlement> CreateTestEntitlementAsync(ulong skuId, ulong ownerId, SubscriptionOwnerType ownerType, RequestOptions options = null)
|
||||||
|
=> ClientHelper.CreateTestEntitlementAsync(this, skuId, ownerId, ownerType, options);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Task DeleteTestEntitlementAsync(ulong entitlementId, RequestOptions options = null)
|
||||||
|
=> ApiClient.DeleteEntitlementAsync(entitlementId, options);
|
||||||
|
|
||||||
|
/// <inheritdoc cref="IDiscordClient.GetEntitlementsAsync" />
|
||||||
|
public IAsyncEnumerable<IReadOnlyCollection<IEntitlement>> GetEntitlementsAsync(int? limit = 100,
|
||||||
|
ulong? afterId = null, ulong? beforeId = null, bool excludeEnded = false, ulong? guildId = null, ulong? userId = null,
|
||||||
|
ulong[] skuIds = null, RequestOptions options = null)
|
||||||
|
=> ClientHelper.ListEntitlementsAsync(this, limit, afterId, beforeId, excludeEnded, guildId, userId, skuIds, options);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Task<IReadOnlyCollection<SKU>> GetSKUsAsync(RequestOptions options = null)
|
||||||
|
=> ClientHelper.ListSKUsAsync(this, options);
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region IDiscordClient
|
#region IDiscordClient
|
||||||
|
async Task<IEntitlement> IDiscordClient.CreateTestEntitlementAsync(ulong skuId, ulong ownerId, SubscriptionOwnerType ownerType, RequestOptions options)
|
||||||
|
=> await CreateTestEntitlementAsync(skuId, ownerId, ownerType, options).ConfigureAwait(false);
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
async Task<IApplication> IDiscordClient.GetApplicationInfoAsync(RequestOptions options)
|
async Task<IApplication> IDiscordClient.GetApplicationInfoAsync(RequestOptions options)
|
||||||
=> await GetApplicationInfoAsync(options).ConfigureAwait(false);
|
=> await GetApplicationInfoAsync(options).ConfigureAwait(false);
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
using Model = Discord.API.Entitlement;
|
||||||
|
|
||||||
|
namespace Discord.Rest;
|
||||||
|
|
||||||
|
public class RestEntitlement : RestEntity<ulong>, IEntitlement
|
||||||
|
{
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public DateTimeOffset CreatedAt { get; private set; }
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public ulong SkuId { get; private set; }
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public ulong? UserId { get; private set; }
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public ulong? GuildId { get; private set; }
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public ulong ApplicationId { get; private set; }
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public EntitlementType Type { get; private set; }
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool IsConsumed { get; private set; }
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public DateTimeOffset? StartsAt { get; private set; }
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public DateTimeOffset? EndsAt { get; private set; }
|
||||||
|
|
||||||
|
internal RestEntitlement(BaseDiscordClient discord, ulong id) : base(discord, id)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static RestEntitlement Create(BaseDiscordClient discord, Model model)
|
||||||
|
{
|
||||||
|
var entity = new RestEntitlement(discord, model.Id);
|
||||||
|
entity.Update(model);
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void Update(Model model)
|
||||||
|
{
|
||||||
|
SkuId = model.SkuId;
|
||||||
|
UserId = model.UserId.IsSpecified
|
||||||
|
? model.UserId.Value
|
||||||
|
: null;
|
||||||
|
GuildId = model.GuildId.IsSpecified
|
||||||
|
? model.GuildId.Value
|
||||||
|
: null;
|
||||||
|
ApplicationId = model.ApplicationId;
|
||||||
|
Type = model.Type;
|
||||||
|
IsConsumed = model.IsConsumed;
|
||||||
|
StartsAt = model.StartsAt.IsSpecified
|
||||||
|
? model.StartsAt.Value
|
||||||
|
: null;
|
||||||
|
EndsAt = model.EndsAt.IsSpecified
|
||||||
|
? model.EndsAt.Value
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -522,6 +522,17 @@ namespace Discord.Rest
|
|||||||
|
|
||||||
return client.ApiClient.CreateInteractionResponseAsync(apiArgs, interactionId, interactionToken, options);
|
return client.ApiClient.CreateInteractionResponseAsync(apiArgs, interactionId, interactionToken, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static async Task RespondWithPremiumRequiredAsync(BaseDiscordClient client, ulong interactionId,
|
||||||
|
string interactionToken, RequestOptions options = null)
|
||||||
|
{
|
||||||
|
await client.ApiClient.CreateInteractionResponseAsync(new InteractionResponse
|
||||||
|
{
|
||||||
|
Type = InteractionResponseType.PremiumRequired,
|
||||||
|
Data = Optional<InteractionCallbackData>.Unspecified
|
||||||
|
}, interactionId, interactionToken, options);
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Guild permissions
|
#region Guild permissions
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ using Discord.Net;
|
|||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Immutable;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using DataModel = Discord.API.ApplicationCommandInteractionData;
|
using DataModel = Discord.API.ApplicationCommandInteractionData;
|
||||||
@@ -88,6 +90,9 @@ namespace Discord.Rest
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public ulong ApplicationId { get; private set; }
|
public ulong ApplicationId { get; private set; }
|
||||||
|
|
||||||
|
/// <inheritdoc cref="IDiscordInteraction.Entitlements" />
|
||||||
|
public IReadOnlyCollection<RestEntitlement> Entitlements { get; private set; }
|
||||||
|
|
||||||
internal RestInteraction(BaseDiscordClient discord, ulong id)
|
internal RestInteraction(BaseDiscordClient discord, ulong id)
|
||||||
: base(discord, id)
|
: base(discord, id)
|
||||||
{
|
{
|
||||||
@@ -223,6 +228,8 @@ namespace Discord.Rest
|
|||||||
GuildLocale = model.GuildLocale.IsSpecified
|
GuildLocale = model.GuildLocale.IsSpecified
|
||||||
? model.GuildLocale.Value
|
? model.GuildLocale.Value
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
|
Entitlements = model.Entitlements.Select(x => RestEntitlement.Create(discord, x)).ToImmutableArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal string SerializePayload(object payload)
|
internal string SerializePayload(object payload)
|
||||||
@@ -413,7 +420,14 @@ namespace Discord.Rest
|
|||||||
public Task DeleteOriginalResponseAsync(RequestOptions options = null)
|
public Task DeleteOriginalResponseAsync(RequestOptions options = null)
|
||||||
=> InteractionHelper.DeleteInteractionResponseAsync(Discord, this, options);
|
=> InteractionHelper.DeleteInteractionResponseAsync(Discord, this, options);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public Task RespondWithPremiumRequiredAsync(RequestOptions options = null)
|
||||||
|
=> InteractionHelper.RespondWithPremiumRequiredAsync(Discord, Id, Token, options);
|
||||||
|
|
||||||
#region IDiscordInteraction
|
#region IDiscordInteraction
|
||||||
|
/// <inheritdoc/>
|
||||||
|
IReadOnlyCollection<IEntitlement> IDiscordInteraction.Entitlements => Entitlements;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
IUser IDiscordInteraction.User => User;
|
IUser IDiscordInteraction.User => User;
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Discord.Rest;
|
using Discord.Rest;
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -918,7 +919,7 @@ namespace Discord.WebSocket
|
|||||||
add => _autoModRuleCreated.Add(value);
|
add => _autoModRuleCreated.Add(value);
|
||||||
remove => _autoModRuleCreated.Remove(value);
|
remove => _autoModRuleCreated.Remove(value);
|
||||||
}
|
}
|
||||||
internal readonly AsyncEvent<Func<SocketAutoModRule, Task>> _autoModRuleCreated = new ();
|
internal readonly AsyncEvent<Func<SocketAutoModRule, Task>> _autoModRuleCreated = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Fired when an auto moderation rule is modified.
|
/// Fired when an auto moderation rule is modified.
|
||||||
@@ -928,7 +929,7 @@ namespace Discord.WebSocket
|
|||||||
add => _autoModRuleUpdated.Add(value);
|
add => _autoModRuleUpdated.Add(value);
|
||||||
remove => _autoModRuleUpdated.Remove(value);
|
remove => _autoModRuleUpdated.Remove(value);
|
||||||
}
|
}
|
||||||
internal readonly AsyncEvent<Func<Cacheable<SocketAutoModRule, ulong>, SocketAutoModRule, Task>> _autoModRuleUpdated = new ();
|
internal readonly AsyncEvent<Func<Cacheable<SocketAutoModRule, ulong>, SocketAutoModRule, Task>> _autoModRuleUpdated = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Fired when an auto moderation rule is deleted.
|
/// Fired when an auto moderation rule is deleted.
|
||||||
@@ -938,7 +939,7 @@ namespace Discord.WebSocket
|
|||||||
add => _autoModRuleDeleted.Add(value);
|
add => _autoModRuleDeleted.Add(value);
|
||||||
remove => _autoModRuleDeleted.Remove(value);
|
remove => _autoModRuleDeleted.Remove(value);
|
||||||
}
|
}
|
||||||
internal readonly AsyncEvent<Func<SocketAutoModRule, Task>> _autoModRuleDeleted = new ();
|
internal readonly AsyncEvent<Func<SocketAutoModRule, Task>> _autoModRuleDeleted = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Fired when an auto moderation rule is triggered by a user.
|
/// Fired when an auto moderation rule is triggered by a user.
|
||||||
@@ -948,7 +949,47 @@ namespace Discord.WebSocket
|
|||||||
add => _autoModActionExecuted.Add(value);
|
add => _autoModActionExecuted.Add(value);
|
||||||
remove => _autoModActionExecuted.Remove(value);
|
remove => _autoModActionExecuted.Remove(value);
|
||||||
}
|
}
|
||||||
internal readonly AsyncEvent<Func<SocketGuild, AutoModRuleAction, AutoModActionExecutedData, Task>> _autoModActionExecuted = new ();
|
internal readonly AsyncEvent<Func<SocketGuild, AutoModRuleAction, AutoModActionExecutedData, Task>> _autoModActionExecuted = new();
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region App Subscriptions
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fired when a user subscribes to a SKU.
|
||||||
|
/// </summary>
|
||||||
|
public event Func<SocketEntitlement, Task> EntitlementCreated
|
||||||
|
{
|
||||||
|
add { _entitlementCreated.Add(value); }
|
||||||
|
remove { _entitlementCreated.Remove(value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
internal readonly AsyncEvent<Func<SocketEntitlement, Task>> _entitlementCreated = new();
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fired when a subscription to a SKU is updated.
|
||||||
|
/// </summary>
|
||||||
|
public event Func<Cacheable<SocketEntitlement, ulong>, SocketEntitlement, Task> EntitlementUpdated
|
||||||
|
{
|
||||||
|
add { _entitlementUpdated.Add(value); }
|
||||||
|
remove { _entitlementUpdated.Remove(value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
internal readonly AsyncEvent<Func<Cacheable<SocketEntitlement, ulong>, SocketEntitlement, Task>> _entitlementUpdated = new();
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fired when a user's entitlement is deleted.
|
||||||
|
/// </summary>
|
||||||
|
public event Func<Cacheable<SocketEntitlement, ulong>, Task> EntitlementDeleted
|
||||||
|
{
|
||||||
|
add { _entitlementDeleted.Add(value); }
|
||||||
|
remove { _entitlementDeleted.Remove(value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
internal readonly AsyncEvent<Func<Cacheable<SocketEntitlement, ulong>, Task>> _entitlementDeleted = new();
|
||||||
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ namespace Discord.WebSocket
|
|||||||
private readonly ConcurrentDictionary<ulong, SocketGlobalUser> _users;
|
private readonly ConcurrentDictionary<ulong, SocketGlobalUser> _users;
|
||||||
private readonly ConcurrentHashSet<ulong> _groupChannels;
|
private readonly ConcurrentHashSet<ulong> _groupChannels;
|
||||||
private readonly ConcurrentDictionary<ulong, SocketApplicationCommand> _commands;
|
private readonly ConcurrentDictionary<ulong, SocketApplicationCommand> _commands;
|
||||||
|
private readonly ConcurrentDictionary<ulong, SocketEntitlement> _entitlements;
|
||||||
|
|
||||||
internal IReadOnlyCollection<SocketChannel> Channels => _channels.ToReadOnlyCollection();
|
internal IReadOnlyCollection<SocketChannel> Channels => _channels.ToReadOnlyCollection();
|
||||||
internal IReadOnlyCollection<SocketDMChannel> DMChannels => _dmChannels.ToReadOnlyCollection();
|
internal IReadOnlyCollection<SocketDMChannel> DMChannels => _dmChannels.ToReadOnlyCollection();
|
||||||
@@ -24,6 +25,7 @@ namespace Discord.WebSocket
|
|||||||
internal IReadOnlyCollection<SocketGuild> Guilds => _guilds.ToReadOnlyCollection();
|
internal IReadOnlyCollection<SocketGuild> Guilds => _guilds.ToReadOnlyCollection();
|
||||||
internal IReadOnlyCollection<SocketGlobalUser> Users => _users.ToReadOnlyCollection();
|
internal IReadOnlyCollection<SocketGlobalUser> Users => _users.ToReadOnlyCollection();
|
||||||
internal IReadOnlyCollection<SocketApplicationCommand> Commands => _commands.ToReadOnlyCollection();
|
internal IReadOnlyCollection<SocketApplicationCommand> Commands => _commands.ToReadOnlyCollection();
|
||||||
|
internal IReadOnlyCollection<SocketEntitlement> Entitlements => _entitlements.ToReadOnlyCollection();
|
||||||
|
|
||||||
internal IReadOnlyCollection<ISocketPrivateChannel> PrivateChannels =>
|
internal IReadOnlyCollection<ISocketPrivateChannel> PrivateChannels =>
|
||||||
_dmChannels.Select(x => x.Value as ISocketPrivateChannel).Concat(
|
_dmChannels.Select(x => x.Value as ISocketPrivateChannel).Concat(
|
||||||
@@ -40,6 +42,7 @@ namespace Discord.WebSocket
|
|||||||
_users = new ConcurrentDictionary<ulong, SocketGlobalUser>(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(estimatedUsersCount * CollectionMultiplier));
|
_users = new ConcurrentDictionary<ulong, SocketGlobalUser>(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(estimatedUsersCount * CollectionMultiplier));
|
||||||
_groupChannels = new ConcurrentHashSet<ulong>(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(10 * CollectionMultiplier));
|
_groupChannels = new ConcurrentHashSet<ulong>(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(10 * CollectionMultiplier));
|
||||||
_commands = new ConcurrentDictionary<ulong, SocketApplicationCommand>();
|
_commands = new ConcurrentDictionary<ulong, SocketApplicationCommand>();
|
||||||
|
_entitlements = new();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal SocketChannel GetChannel(ulong id)
|
internal SocketChannel GetChannel(ulong id)
|
||||||
@@ -170,5 +173,29 @@ namespace Discord.WebSocket
|
|||||||
foreach (var id in ids)
|
foreach (var id in ids)
|
||||||
_commands.TryRemove(id, out var _);
|
_commands.TryRemove(id, out var _);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal void AddEntitlement(ulong id, SocketEntitlement entitlement)
|
||||||
|
{
|
||||||
|
_entitlements.TryAdd(id, entitlement);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal SocketEntitlement GetEntitlement(ulong id)
|
||||||
|
{
|
||||||
|
if (_entitlements.TryGetValue(id, out var entitlement))
|
||||||
|
return entitlement;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal SocketEntitlement GetOrAddEntitlement(ulong id, Func<ulong, SocketEntitlement> entitlementFactory)
|
||||||
|
{
|
||||||
|
return _entitlements.GetOrAdd(id, entitlementFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal SocketEntitlement RemoveEntitlement(ulong id)
|
||||||
|
{
|
||||||
|
if(_entitlements.TryRemove(id, out var entitlement))
|
||||||
|
return entitlement;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -428,6 +428,35 @@ namespace Discord.WebSocket
|
|||||||
public override SocketUser GetUser(string username, string discriminator = null)
|
public override SocketUser GetUser(string username, string discriminator = null)
|
||||||
=> State.Users.FirstOrDefault(x => (discriminator is null || x.Discriminator == discriminator) && x.Username == username);
|
=> State.Users.FirstOrDefault(x => (discriminator is null || x.Discriminator == discriminator) && x.Username == username);
|
||||||
|
|
||||||
|
/// <inheritdoc cref="IDiscordClient.CreateTestEntitlementAsync"/>
|
||||||
|
public Task<RestEntitlement> CreateTestEntitlementAsync(ulong skuId, ulong ownerId, SubscriptionOwnerType ownerType, RequestOptions options = null)
|
||||||
|
=> ClientHelper.CreateTestEntitlementAsync(this, skuId, ownerId, ownerType, options);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Task DeleteTestEntitlementAsync(ulong entitlementId, RequestOptions options = null)
|
||||||
|
=> ApiClient.DeleteEntitlementAsync(entitlementId, options);
|
||||||
|
|
||||||
|
/// <inheritdoc cref="IDiscordClient.GetEntitlementsAsync"/>
|
||||||
|
public IAsyncEnumerable<IReadOnlyCollection<IEntitlement>> GetEntitlementsAsync(BaseDiscordClient client, int? limit = 100,
|
||||||
|
ulong? afterId = null, ulong? beforeId = null, bool excludeEnded = false, ulong? guildId = null, ulong? userId = null,
|
||||||
|
ulong[] skuIds = null, RequestOptions options = null)
|
||||||
|
=> ClientHelper.ListEntitlementsAsync(this, limit, afterId, beforeId, excludeEnded, guildId, userId, skuIds, options);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Task<IReadOnlyCollection<SKU>> GetSKUsAsync(RequestOptions options = null)
|
||||||
|
=> ClientHelper.ListSKUsAsync(this, options);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets entitlements from cache.
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyCollection<SocketEntitlement> Entitlements => State.Entitlements;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an entitlement from cache. <see langword="null"/> if not found.
|
||||||
|
/// </summary>
|
||||||
|
public SocketEntitlement GetEntitlement(ulong id)
|
||||||
|
=> State.GetEntitlement(id);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a global application command.
|
/// Gets a global application command.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -2903,18 +2932,18 @@ namespace Discord.WebSocket
|
|||||||
#region Audit Logs
|
#region Audit Logs
|
||||||
|
|
||||||
case "GUILD_AUDIT_LOG_ENTRY_CREATE":
|
case "GUILD_AUDIT_LOG_ENTRY_CREATE":
|
||||||
{
|
{
|
||||||
var data = (payload as JToken).ToObject<AuditLogCreatedEvent>(_serializer);
|
var data = (payload as JToken).ToObject<AuditLogCreatedEvent>(_serializer);
|
||||||
type = "GUILD_AUDIT_LOG_ENTRY_CREATE";
|
type = "GUILD_AUDIT_LOG_ENTRY_CREATE";
|
||||||
await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_AUDIT_LOG_ENTRY_CREATE)").ConfigureAwait(false);
|
await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_AUDIT_LOG_ENTRY_CREATE)").ConfigureAwait(false);
|
||||||
|
|
||||||
var guild = State.GetGuild(data.GuildId);
|
var guild = State.GetGuild(data.GuildId);
|
||||||
var auditLog = SocketAuditLogEntry.Create(this, data);
|
var auditLog = SocketAuditLogEntry.Create(this, data);
|
||||||
guild.AddAuditLog(auditLog);
|
guild.AddAuditLog(auditLog);
|
||||||
|
|
||||||
await TimedInvokeAsync(_auditLogCreated, nameof(AuditLogCreated), auditLog, guild);
|
await TimedInvokeAsync(_auditLogCreated, nameof(AuditLogCreated), auditLog, guild);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Auto Moderation
|
#region Auto Moderation
|
||||||
@@ -3051,6 +3080,65 @@ namespace Discord.WebSocket
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region App Subscriptions
|
||||||
|
|
||||||
|
case "ENTITLEMENT_CREATE":
|
||||||
|
{
|
||||||
|
await _gatewayLogger.DebugAsync("Received Dispatch (ENTITLEMENT_CREATE)").ConfigureAwait(false);
|
||||||
|
var data = (payload as JToken).ToObject<Entitlement>(_serializer);
|
||||||
|
|
||||||
|
var entitlement = SocketEntitlement.Create(this, data);
|
||||||
|
State.AddEntitlement(data.Id, entitlement);
|
||||||
|
|
||||||
|
await TimedInvokeAsync(_entitlementCreated, nameof(EntitlementCreated), entitlement);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "ENTITLEMENT_UPDATE":
|
||||||
|
{
|
||||||
|
await _gatewayLogger.DebugAsync("Received Dispatch (ENTITLEMENT_UPDATE)").ConfigureAwait(false);
|
||||||
|
var data = (payload as JToken).ToObject<Entitlement>(_serializer);
|
||||||
|
|
||||||
|
var entitlement = State.GetEntitlement(data.Id);
|
||||||
|
|
||||||
|
var cacheableBefore = new Cacheable<SocketEntitlement, ulong>(entitlement?.Clone(), data.Id,
|
||||||
|
entitlement is not null, () => null);
|
||||||
|
|
||||||
|
if (entitlement is null)
|
||||||
|
{
|
||||||
|
entitlement = SocketEntitlement.Create(this, data);
|
||||||
|
State.AddEntitlement(data.Id, entitlement);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
entitlement.Update(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
await TimedInvokeAsync(_entitlementUpdated, nameof(EntitlementUpdated), cacheableBefore, entitlement);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "ENTITLEMENT_DELETE":
|
||||||
|
{
|
||||||
|
await _gatewayLogger.DebugAsync("Received Dispatch (ENTITLEMENT_DELETE)").ConfigureAwait(false);
|
||||||
|
var data = (payload as JToken).ToObject<Entitlement>(_serializer);
|
||||||
|
|
||||||
|
var entitlement = State.RemoveEntitlement(data.Id);
|
||||||
|
|
||||||
|
if (entitlement is null)
|
||||||
|
entitlement = SocketEntitlement.Create(this, data);
|
||||||
|
else
|
||||||
|
entitlement.Update(data);
|
||||||
|
|
||||||
|
var cacheableEntitlement = new Cacheable<SocketEntitlement, ulong>(entitlement, data.Id,
|
||||||
|
entitlement is not null, () => null);
|
||||||
|
|
||||||
|
await TimedInvokeAsync(_entitlementDeleted, nameof(EntitlementDeleted), cacheableEntitlement);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
#region Ignored (User only)
|
#region Ignored (User only)
|
||||||
case "CHANNEL_PINS_ACK":
|
case "CHANNEL_PINS_ACK":
|
||||||
await _gatewayLogger.DebugAsync("Ignored Dispatch (CHANNEL_PINS_ACK)").ConfigureAwait(false);
|
await _gatewayLogger.DebugAsync("Ignored Dispatch (CHANNEL_PINS_ACK)").ConfigureAwait(false);
|
||||||
@@ -3380,10 +3468,9 @@ namespace Discord.WebSocket
|
|||||||
internal int GetAudioId() => _nextAudioId++;
|
internal int GetAudioId() => _nextAudioId++;
|
||||||
|
|
||||||
#region IDiscordClient
|
#region IDiscordClient
|
||||||
/// <inheritdoc />
|
|
||||||
async Task<IApplication> IDiscordClient.GetApplicationInfoAsync(RequestOptions options)
|
|
||||||
=> await GetApplicationInfoAsync().ConfigureAwait(false);
|
|
||||||
|
|
||||||
|
async Task<IEntitlement> IDiscordClient.CreateTestEntitlementAsync(ulong skuId, ulong ownerId, SubscriptionOwnerType ownerType, RequestOptions options)
|
||||||
|
=> await CreateTestEntitlementAsync(skuId, ownerId, ownerType, options).ConfigureAwait(false);
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
async Task<IChannel> IDiscordClient.GetChannelAsync(ulong id, CacheMode mode, RequestOptions options)
|
async Task<IChannel> IDiscordClient.GetChannelAsync(ulong id, CacheMode mode, RequestOptions options)
|
||||||
=> mode == CacheMode.AllowDownload ? await GetChannelAsync(id, options).ConfigureAwait(false) : GetChannel(id);
|
=> mode == CacheMode.AllowDownload ? await GetChannelAsync(id, options).ConfigureAwait(false) : GetChannel(id);
|
||||||
|
|||||||
@@ -0,0 +1,91 @@
|
|||||||
|
using Discord.Rest;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
using Model = Discord.API.Entitlement;
|
||||||
|
|
||||||
|
namespace Discord.WebSocket;
|
||||||
|
|
||||||
|
public class SocketEntitlement : SocketEntity<ulong>, IEntitlement
|
||||||
|
{
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public DateTimeOffset CreatedAt { get; private set; }
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public ulong SkuId { get; private set; }
|
||||||
|
|
||||||
|
/// <inheritdoc cref="IEntitlement.UserId"/>
|
||||||
|
public Cacheable<SocketUser, RestUser, IUser, ulong>? User { get; private set; }
|
||||||
|
|
||||||
|
/// <inheritdoc cref="IEntitlement.GuildId"/>
|
||||||
|
public Cacheable<SocketGuild, RestGuild, IGuild, ulong>? Guild { get; private set; }
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public ulong ApplicationId { get; private set; }
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public EntitlementType Type { get; private set; }
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool IsConsumed { get; private set; }
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public DateTimeOffset? StartsAt { get; private set; }
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public DateTimeOffset? EndsAt { get; private set; }
|
||||||
|
|
||||||
|
internal SocketEntitlement(DiscordSocketClient discord, ulong id) : base(discord, id)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static SocketEntitlement Create(DiscordSocketClient discord, Model model)
|
||||||
|
{
|
||||||
|
var entity = new SocketEntitlement(discord, model.Id);
|
||||||
|
entity.Update(model);
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void Update(Model model)
|
||||||
|
{
|
||||||
|
SkuId = model.SkuId;
|
||||||
|
|
||||||
|
if (model.UserId.IsSpecified)
|
||||||
|
{
|
||||||
|
var user = Discord.GetUser(model.UserId.Value);
|
||||||
|
|
||||||
|
User = new Cacheable<SocketUser, RestUser, IUser, ulong>(user, model.UserId.Value, user is not null, async ()
|
||||||
|
=> await Discord.Rest.GetUserAsync(model.UserId.Value));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (model.GuildId.IsSpecified)
|
||||||
|
{
|
||||||
|
var guild = Discord.GetGuild(model.GuildId.Value);
|
||||||
|
|
||||||
|
Guild = new Cacheable<SocketGuild, RestGuild, IGuild, ulong>(guild, model.GuildId.Value, guild is not null, async ()
|
||||||
|
=> await Discord.Rest.GetGuildAsync(model.GuildId.Value));
|
||||||
|
}
|
||||||
|
|
||||||
|
ApplicationId = model.ApplicationId;
|
||||||
|
Type = model.Type;
|
||||||
|
IsConsumed = model.IsConsumed;
|
||||||
|
StartsAt = model.StartsAt.IsSpecified
|
||||||
|
? model.StartsAt.Value
|
||||||
|
: null;
|
||||||
|
EndsAt = model.EndsAt.IsSpecified
|
||||||
|
? model.EndsAt.Value
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal SocketEntitlement Clone() => MemberwiseClone() as SocketEntitlement;
|
||||||
|
|
||||||
|
#region IEntitlement
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
ulong? IEntitlement.GuildId => Guild?.Id;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
ulong? IEntitlement.UserId => User?.Id;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
}
|
||||||
@@ -2,7 +2,9 @@ using Discord.Net;
|
|||||||
using Discord.Rest;
|
using Discord.Rest;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Immutable;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using DataModel = Discord.API.ApplicationCommandInteractionData;
|
using DataModel = Discord.API.ApplicationCommandInteractionData;
|
||||||
using Model = Discord.API.Interaction;
|
using Model = Discord.API.Interaction;
|
||||||
@@ -71,6 +73,9 @@ namespace Discord.WebSocket
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public ulong ApplicationId { get; private set; }
|
public ulong ApplicationId { get; private set; }
|
||||||
|
|
||||||
|
/// <inheritdoc cref="IDiscordInteraction.Entitlements" />
|
||||||
|
public IReadOnlyCollection<RestEntitlement> Entitlements { get; private set; }
|
||||||
|
|
||||||
internal SocketInteraction(DiscordSocketClient client, ulong id, ISocketMessageChannel channel, SocketUser user)
|
internal SocketInteraction(DiscordSocketClient client, ulong id, ISocketMessageChannel channel, SocketUser user)
|
||||||
: base(client, id)
|
: base(client, id)
|
||||||
{
|
{
|
||||||
@@ -142,6 +147,8 @@ namespace Discord.WebSocket
|
|||||||
GuildLocale = model.GuildLocale.IsSpecified
|
GuildLocale = model.GuildLocale.IsSpecified
|
||||||
? model.GuildLocale.Value
|
? model.GuildLocale.Value
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
|
Entitlements = model.Entitlements.Select(x => RestEntitlement.Create(Discord, x)).ToImmutableArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -398,6 +405,11 @@ namespace Discord.WebSocket
|
|||||||
/// <param name="options">The request options for this <see langword="async"/> request.</param>
|
/// <param name="options">The request options for this <see langword="async"/> request.</param>
|
||||||
/// <returns>A task that represents the asynchronous operation of responding to the interaction.</returns>
|
/// <returns>A task that represents the asynchronous operation of responding to the interaction.</returns>
|
||||||
public abstract Task RespondWithModalAsync(Modal modal, RequestOptions options = null);
|
public abstract Task RespondWithModalAsync(Modal modal, RequestOptions options = null);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public Task RespondWithPremiumRequiredAsync(RequestOptions options = null)
|
||||||
|
=> InteractionHelper.RespondWithPremiumRequiredAsync(Discord, Id, Token, options);
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -423,6 +435,9 @@ namespace Discord.WebSocket
|
|||||||
}
|
}
|
||||||
|
|
||||||
#region IDiscordInteraction
|
#region IDiscordInteraction
|
||||||
|
/// <inheritdoc/>
|
||||||
|
IReadOnlyCollection<IEntitlement> IDiscordInteraction.Entitlements => Entitlements;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
IUser IDiscordInteraction.User => User;
|
IUser IDiscordInteraction.User => User;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user