[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:
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")]
|
||||
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.Immutable;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@@ -258,6 +259,30 @@ namespace Discord.Rest
|
||||
/// <inheritdoc />
|
||||
Task IDiscordClient.StopAsync()
|
||||
=> 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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ using Discord.Net.Rest;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
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);
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
@@ -2689,5 +2690,55 @@ namespace Discord.API
|
||||
=> await SendJsonAsync<RoleConnection>("PUT", () => $"users/@me/applications/{applicationId}/role-connection", connection, new BucketIds(), options: options);
|
||||
|
||||
#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)
|
||||
=> 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
|
||||
|
||||
#region IDiscordClient
|
||||
async Task<IEntitlement> IDiscordClient.CreateTestEntitlementAsync(ulong skuId, ulong ownerId, SubscriptionOwnerType ownerType, RequestOptions options)
|
||||
=> await CreateTestEntitlementAsync(skuId, ownerId, ownerType, options).ConfigureAwait(false);
|
||||
|
||||
/// <inheritdoc />
|
||||
async Task<IApplication> IDiscordClient.GetApplicationInfoAsync(RequestOptions options)
|
||||
=> 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);
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
#region Guild permissions
|
||||
|
||||
@@ -2,7 +2,9 @@ using Discord.Net;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using DataModel = Discord.API.ApplicationCommandInteractionData;
|
||||
@@ -88,6 +90,9 @@ namespace Discord.Rest
|
||||
/// <inheritdoc/>
|
||||
public ulong ApplicationId { get; private set; }
|
||||
|
||||
/// <inheritdoc cref="IDiscordInteraction.Entitlements" />
|
||||
public IReadOnlyCollection<RestEntitlement> Entitlements { get; private set; }
|
||||
|
||||
internal RestInteraction(BaseDiscordClient discord, ulong id)
|
||||
: base(discord, id)
|
||||
{
|
||||
@@ -223,6 +228,8 @@ namespace Discord.Rest
|
||||
GuildLocale = model.GuildLocale.IsSpecified
|
||||
? model.GuildLocale.Value
|
||||
: null;
|
||||
|
||||
Entitlements = model.Entitlements.Select(x => RestEntitlement.Create(discord, x)).ToImmutableArray();
|
||||
}
|
||||
|
||||
internal string SerializePayload(object payload)
|
||||
@@ -413,7 +420,14 @@ namespace Discord.Rest
|
||||
public Task DeleteOriginalResponseAsync(RequestOptions options = null)
|
||||
=> InteractionHelper.DeleteInteractionResponseAsync(Discord, this, options);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task RespondWithPremiumRequiredAsync(RequestOptions options = null)
|
||||
=> InteractionHelper.RespondWithPremiumRequiredAsync(Discord, Id, Token, options);
|
||||
|
||||
#region IDiscordInteraction
|
||||
/// <inheritdoc/>
|
||||
IReadOnlyCollection<IEntitlement> IDiscordInteraction.Entitlements => Entitlements;
|
||||
|
||||
/// <inheritdoc/>
|
||||
IUser IDiscordInteraction.User => User;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user