Skip to content

Commit

Permalink
more monetizatios stuffz (#3002)
Browse files Browse the repository at this point in the history
* uhhhhhhhhhhhhhhhhhhh yes

* uhhhhhhhhhhhhhhhhhhh yes

* ~~i love git~~
  • Loading branch information
Misha-133 authored Sep 13, 2024
1 parent b87ec6e commit 88ea2ed
Show file tree
Hide file tree
Showing 20 changed files with 2,973 additions and 2,466 deletions.
5 changes: 5 additions & 0 deletions src/Discord.Net.Core/DiscordConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,11 @@ public class DiscordConfig
/// </summary>
public const int MaxEntitlementsPerBatch = 100;

/// <summary>
/// Returns the maximum number of subscriptions that can be gotten per-batch.
/// </summary>
public const int MaxSubscriptionsPerBatch = 100;

/// <summary>
/// Returns the maximum number of poll answer voters that can be gotten per-batch.
/// </summary>
Expand Down
56 changes: 56 additions & 0 deletions src/Discord.Net.Core/Entities/AppSubscriptions/ISubscription.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
using System;
using System.Collections.Generic;

namespace Discord;

/// <summary>
/// Represents a subscription object.
/// </summary>
public interface ISubscription : ISnowflakeEntity
{
/// <summary>
/// Gets the ID of the user who is subscribed.
/// </summary>
ulong UserId { get; }

/// <summary>
/// Gets the SKUs subscribed to.
/// </summary>
IReadOnlyCollection<ulong> SKUIds { get; }

/// <summary>
/// Gets the entitlements granted for this subscription.
/// </summary>
IReadOnlyCollection<ulong> EntitlementIds { get; }

/// <summary>
/// Gets the start of the current subscription period.
/// </summary>
DateTimeOffset CurrentPeriodStart { get; }

/// <summary>
/// Gets end of the current subscription period.
/// </summary>
DateTimeOffset CurrentPeriodEnd { get; }

/// <summary>
/// Gets the current status of the subscription.
/// </summary>
SubscriptionStatus Status { get; }

/// <summary>
/// Gets when the subscription was canceled.
/// </summary>
/// <remarks>
/// <see langword="null"/> if the subscription has not been canceled.
/// </remarks>
DateTimeOffset? CanceledAt { get; }

/// <summary>
/// Gets country code of the payment source used to purchase the subscription.
/// </summary>
/// <remarks>
/// Requires an oauth scope.
/// </remarks>
string Country { get; }
}
8 changes: 7 additions & 1 deletion src/Discord.Net.Core/Entities/AppSubscriptions/SKU.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,18 @@ public struct SKU : ISnowflakeEntity
/// </summary>
public string Slug { get; }

internal SKU(ulong id, SKUType type, ulong applicationId, string name, string slug)
/// <summary>
/// Gets the flags for this SKU.
/// </summary>
public SKUFlags Flags { get; }

internal SKU(ulong id, SKUType type, ulong applicationId, string name, string slug, SKUFlags flags)
{
Id = id;
Type = type;
ApplicationId = applicationId;
Name = name;
Slug = slug;
Flags = flags;
}
}
5 changes: 5 additions & 0 deletions src/Discord.Net.Core/Entities/AppSubscriptions/SKUFlags.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ namespace Discord;
[Flags]
public enum SKUFlags
{
/// <summary>
/// The SKU is available for purchase.
/// </summary>
IsAvailable = 1 << 2,

/// <summary>
/// The SKU is a guild subscription.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
namespace Discord;

public enum SubscriptionStatus
{
/// <summary>
/// Subscription is active and scheduled to renew.
/// </summary>
Active = 0,

/// <summary>
/// Subscription is active but will not renew.
/// </summary>
Ending = 1,

/// <summary>
/// Subscription is inactive and not being charged.
/// </summary>
Inactive = 2
}
13 changes: 12 additions & 1 deletion src/Discord.Net.Core/IDiscordClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,7 @@ public interface IDiscordClient : IDisposable, IAsyncDisposable
/// <summary>
/// Returns all entitlements for a given app, active and expired.
/// </summary>
IAsyncEnumerable<IReadOnlyCollection<IEntitlement>> GetEntitlementsAsync(int? limit = 100,
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);

Expand All @@ -357,6 +357,17 @@ IAsyncEnumerable<IReadOnlyCollection<IEntitlement>> GetEntitlementsAsync(int? li
/// <param name="options">The options to be used when sending the request.</param>
Task ConsumeEntitlementAsync(ulong entitlementId, RequestOptions options = null);

/// <summary>
/// Returns all subscriptions for a given SKU.
/// </summary>
IAsyncEnumerable<IReadOnlyCollection<ISubscription>> GetSKUSubscriptionsAsync(ulong skuId, int limit = 100, ulong? afterId = null,
ulong? beforeId = null, ulong? userId = null, RequestOptions options = null);

/// <summary>
/// Gets a subscription by its id.
/// </summary>
Task<ISubscription> GetSKUSubscriptionAsync(ulong skuId, ulong subscriptionId, RequestOptions options = null);

/// <summary>
/// Gets an emote for the current application.
/// </summary>
Expand Down
2 changes: 1 addition & 1 deletion src/Discord.Net.Rest/API/Common/Entitlement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,5 @@ internal class Entitlement
public Optional<DateTimeOffset> StartsAt { get; set; }

[JsonProperty("ends_at")]
public Optional<DateTimeOffset> EndsAt { get; set; }
public Optional<DateTimeOffset?> EndsAt { get; set; }
}
3 changes: 3 additions & 0 deletions src/Discord.Net.Rest/API/Common/SKU.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,7 @@ internal class SKU

[JsonProperty("slug")]
public string Slug { get; set; }

[JsonProperty("flags")]
public SKUFlags Flags { get; set; }
}
34 changes: 34 additions & 0 deletions src/Discord.Net.Rest/API/Common/Subscription.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using Newtonsoft.Json;
using System;

namespace Discord.API;

internal class Subscription
{
[JsonProperty("id")]
public ulong Id { get; set; }

[JsonProperty("user_id")]
public ulong UserId { get; set; }

[JsonProperty("sku_ids")]
public ulong[] SKUIds { get; set; }

[JsonProperty("entitlement_ids")]
public ulong[] EntitlementIds { get; set; }

[JsonProperty("current_period_start")]
public DateTimeOffset CurrentPeriodStart { get; set; }

[JsonProperty("current_period_end")]
public DateTimeOffset CurrentPeriodEnd { get; set; }

[JsonProperty("status")]
public SubscriptionStatus Status { get; set; }

[JsonProperty("canceled_at")]
public DateTimeOffset? CanceledAt { get; set; }

[JsonProperty("country")]
public string Country { get; set; }
}
13 changes: 12 additions & 1 deletion src/Discord.Net.Rest/BaseDiscordClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@ Task IDiscordClient.DeleteTestEntitlementAsync(ulong entitlementId, RequestOptio
/// <summary>
/// Returns all entitlements for a given app.
/// </summary>
IAsyncEnumerable<IReadOnlyCollection<IEntitlement>> IDiscordClient.GetEntitlementsAsync(int? limit, ulong? afterId, ulong? beforeId,
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>
Expand All @@ -299,6 +299,17 @@ IAsyncEnumerable<IReadOnlyCollection<IEntitlement>> IDiscordClient.GetEntitlemen
/// </summary>
Task IDiscordClient.ConsumeEntitlementAsync(ulong entitlementId, RequestOptions options) => Task.CompletedTask;

/// <summary>
/// Returns all subscriptions for a given SKU.
/// </summary>
IAsyncEnumerable<IReadOnlyCollection<ISubscription>> IDiscordClient.GetSKUSubscriptionsAsync(ulong skuId, int limit, ulong? afterId,
ulong? beforeId, ulong? userId, RequestOptions options) => AsyncEnumerable.Empty<IReadOnlyCollection<ISubscription>>();

/// <summary>
/// Gets a subscription by its id.
/// </summary>
Task<ISubscription> IDiscordClient.GetSKUSubscriptionAsync(ulong skuId, ulong subscriptionId, RequestOptions options) => Task.FromResult<ISubscription>(null);


/// <inheritdoc />
Task<Emote> IDiscordClient.GetApplicationEmoteAsync(ulong emoteId, RequestOptions options) => Task.FromResult<Emote>(null);
Expand Down
36 changes: 35 additions & 1 deletion src/Discord.Net.Rest/ClientHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -443,12 +443,46 @@ public static async Task<IReadOnlyCollection<SKU>> ListSKUsAsync(BaseDiscordClie
{
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();
return models.Select(x => new SKU(x.Id, x.Type, x.ApplicationId, x.Name, x.Slug, x.Flags)).ToImmutableArray();
}

public static Task ConsumeEntitlementAsync(BaseDiscordClient client, ulong entitlementId, RequestOptions options = null)
=> client.ApiClient.ConsumeEntitlementAsync(entitlementId, options);

public static async Task<RestSubscription> GetSKUSubscriptionAsync(BaseDiscordClient client, ulong skuId, ulong subscriptionId, RequestOptions options = null)
{
var model = await client.ApiClient.GetSKUSubscriptionAsync(skuId, subscriptionId, options);

return RestSubscription.Create(client, model);
}

public static IAsyncEnumerable<IReadOnlyCollection<RestSubscription>> ListSubscriptionsAsync(BaseDiscordClient client, ulong skuId, int limit = 100,
ulong? afterId = null, ulong? beforeId = null, ulong? userId = null, RequestOptions options = null)
{
return new PagedAsyncEnumerable<RestSubscription>(
DiscordConfig.MaxSubscriptionsPerBatch,
async (info, ct) =>
{
var _afterId = afterId;
if (info.Position != null)
_afterId = info.Position.Value;
var models = await client.ApiClient.ListSKUSubscriptionsAsync(skuId, beforeId, _afterId, limit, userId, options).ConfigureAwait(false);
return models
.Select(x => RestSubscription.Create(client, x))
.ToImmutableArray();
},
nextPage: (info, lastPage) =>
{
if (lastPage.Count != DiscordConfig.MaxSubscriptionsPerBatch)
return false;
info.Position = lastPage.Max(x => x.Id);
return true;
},
start: afterId,
count: limit
);
}

#endregion

#region Application Emojis
Expand Down
18 changes: 18 additions & 0 deletions src/Discord.Net.Rest/DiscordRestApiClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2852,6 +2852,24 @@ public Task<SKU[]> ListSKUsAsync(RequestOptions options = null)
public Task ConsumeEntitlementAsync(ulong entitlementId, RequestOptions options = null)
=> SendAsync("POST", () => $"applications/{CurrentApplicationId}/entitlements/{entitlementId}/consume", new BucketIds(), options: options);

public Task<Subscription> GetSKUSubscriptionAsync(ulong skuId, ulong subscriptionId, RequestOptions options = null)
=> SendAsync<Subscription>("GET", () => $"skus/{skuId}/subscriptions/{subscriptionId}", new BucketIds(), options: options);

public Task<Subscription[]> ListSKUSubscriptionsAsync(ulong skuId, ulong? before = null, ulong? after = null, int limit = 100, ulong? userId = null, RequestOptions options = null)
{
Preconditions.AtMost(100, limit, "Limit must be less or equal to 100.");
Preconditions.AtLeast(1, limit, "Limit must be greater or equal to 1.");

var args = $"?limit={limit}";
if (before is not null)
args += $"&before={before}";
if (after is not null)
args += $"&after={after}";
if (userId is not null)
args += $"&user_id={userId}";

return SendAsync<Subscription[]>("GET", () => $"skus/{skuId}/subscriptions{args}", new BucketIds(), options: options);
}
#endregion

#region Polls
Expand Down
17 changes: 17 additions & 0 deletions src/Discord.Net.Rest/DiscordRestClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,15 @@ public Task<IReadOnlyCollection<SKU>> GetSKUsAsync(RequestOptions options = null
public Task ConsumeEntitlementAsync(ulong entitlementId, RequestOptions options = null)
=> ClientHelper.ConsumeEntitlementAsync(this, entitlementId, options);

/// <inheritdoc cref="IDiscordClient.GetSKUSubscriptionAsync" />
public Task<RestSubscription> GetSKUSubscriptionAsync(ulong skuId, ulong subscriptionId, RequestOptions options = null)
=> ClientHelper.GetSKUSubscriptionAsync(this, skuId, subscriptionId, options);

/// <inheritdoc cref="IDiscordClient.GetSKUSubscriptionsAsync" />
public IAsyncEnumerable<IReadOnlyCollection<RestSubscription>> GetSKUSubscriptionsAsync(ulong skuId, int limit = 100, ulong? afterId = null,
ulong? beforeId = null, ulong? userId = null, RequestOptions options = null)
=> ClientHelper.ListSubscriptionsAsync(this, skuId, limit, afterId, beforeId, userId, options);

/// <inheritdoc />
public Task<Emote> GetApplicationEmoteAsync(ulong emoteId, RequestOptions options = null)
=> ClientHelper.GetApplicationEmojiAsync(this, emoteId, options);
Expand Down Expand Up @@ -330,6 +339,14 @@ async Task<IEntitlement> IDiscordClient.CreateTestEntitlementAsync(ulong skuId,
async Task<IApplication> IDiscordClient.GetApplicationInfoAsync(RequestOptions options)
=> await GetApplicationInfoAsync(options).ConfigureAwait(false);

/// <inheritdoc />
async Task<ISubscription> IDiscordClient.GetSKUSubscriptionAsync(ulong skuId, ulong subscriptionId, RequestOptions options)
=> await GetSKUSubscriptionAsync(skuId, subscriptionId, options);

/// <inheritdoc />
IAsyncEnumerable<IReadOnlyCollection<ISubscription>> IDiscordClient.GetSKUSubscriptionsAsync(ulong skuId, int limit, ulong? afterId,
ulong? beforeId, ulong? userId, RequestOptions options) => GetSKUSubscriptionsAsync(skuId, limit, afterId, beforeId, userId, options);

/// <inheritdoc />
async Task<IChannel> IDiscordClient.GetChannelAsync(ulong id, CacheMode mode, RequestOptions options)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ namespace Discord.Rest;

public class RestEntitlement : RestEntity<ulong>, IEntitlement
{
/// <inheritdoc/>
public DateTimeOffset CreatedAt { get; private set; }
/// <inheritdoc />
public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id);

/// <inheritdoc/>
public ulong SkuId { get; private set; }
Expand Down
Loading

0 comments on commit 88ea2ed

Please sign in to comment.