Skip to content
Merged
27 changes: 27 additions & 0 deletions DSharpPlus/Clients/DiscordClient.Dispatch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -582,6 +582,20 @@ internal async Task HandleDispatchAsync(GatewayPayload payload)
await OnAutoModerationRuleExecutedAsync(dat.ToDiscordObject<DiscordAutoModerationActionExecution>());
break;
#endregion

#region Entitlements
case "entitlement_create":
await OnEntitlementCreatedAsync(dat.ToDiscordObject<DiscordEntitlement>());
break;

case "entitlement_update":
await OnEntitlementUpdatedAsync(dat.ToDiscordObject<DiscordEntitlement>());
break;

case "entitlement_delete":
await OnEntitlementDeletedAsync(dat.ToDiscordObject<DiscordEntitlement>());
break;
#endregion
}
}

Expand Down Expand Up @@ -2839,5 +2853,18 @@ internal async Task OnAutoModerationRuleExecutedAsync(DiscordAutoModerationActio
}
#endregion

#region Entitlements

private async Task OnEntitlementCreatedAsync(DiscordEntitlement entitlement)
=> await this.dispatcher.DispatchAsync(this, new EntitlementCreatedEventArgs { Entitlement = entitlement });

private async Task OnEntitlementUpdatedAsync(DiscordEntitlement entitlement)
=> await this.dispatcher.DispatchAsync(this, new EntitlementUpdatedEventArgs { Entitlement = entitlement });

private async Task OnEntitlementDeletedAsync(DiscordEntitlement entitlement)
=> await this.dispatcher.DispatchAsync(this, new EntitlementDeletedEventArgs { Entitlement = entitlement });

#endregion

#endregion
}
48 changes: 48 additions & 0 deletions DSharpPlus/Clients/EventHandlingBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -821,4 +821,52 @@ Func<DiscordClient, AutoModerationRuleExecutedEventArgs, Task> handler
this.Services.Configure<EventHandlerCollection>(c => c.Register(handler));
return this;
}

/// <summary>
/// Fired when an entitlement was created.
/// </summary>
public EventHandlingBuilder HandleEntitlementCreated
(
Func<DiscordClient, EntitlementCreatedEventArgs, Task> handler
)
{
this.Services.Configure<EventHandlerCollection>
(
c => c.Register(handler)
);

return this;
}

/// <summary>
/// Fired when an entitlement was updated.
/// </summary>
public EventHandlingBuilder HandleEntitlementUpdated
(
Func<DiscordClient, EntitlementUpdatedEventArgs, Task> handler
)
{
this.Services.Configure<EventHandlerCollection>
(
c => c.Register(handler)
);

return this;
}

/// <summary>
/// Fired when an entitlement was deleted.
/// </summary>
public EventHandlingBuilder HandleEntitlementDeleted
(
Func<DiscordClient, EntitlementDeletedEventArgs, Task> handler
)
{
this.Services.Configure<EventHandlerCollection>
(
c => c.Register(handler)
);

return this;
}
}
82 changes: 82 additions & 0 deletions DSharpPlus/Entities/Application/DiscordApplication.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using DSharpPlus.Net.Abstractions;
using Newtonsoft.Json;
Expand Down Expand Up @@ -417,4 +419,84 @@ public string GenerateOAuthUri(string? redirectUri = null, DiscordPermissions? p
DiscordOAuthScope.RelationshipsRead => "relationships.read",
_ => null
};

/// <summary>
/// List all stock keeping units belonging to this application
/// </summary>
/// <returns></returns>
public async ValueTask<IReadOnlyList<DiscordStockKeepingUnit>> ListStockKeepingUnitsAsync()
=> await this.Discord.ApiClient.ListStockKeepingUnitsAsync(this.Id);

/// <summary>
/// List all Entitlements belonging to this application.
/// </summary>
/// <param name="userId">Filters the entitlements by a user.</param>
/// <param name="skuIds">Filters the entitlements by specific SKUs.</param>
/// <param name="before">Filters the entitlements to be before a specific snowflake. Can be used to filter by time. Mutually exclusive with parameter "after"</param>
/// <param name="after">Filters the entitlements to be after a specific snowflake. Can be used to filter by time. Mutually exclusive with parameter "before"</param>
/// <param name="limit">Limits how many Entitlements should be returned. One API call per 100 entitlements</param>
/// <param name="guildId">Filters the entitlements by a specific Guild.</param>
/// <param name="excludeEnded">Wheter or not to return time limited entitlements which have ended</param>
/// <param name="cancellationToken">CT to cancel the method before the next api call</param>
/// <returns>Returns the list of entitlements fitting to the filters</returns>
/// <exception cref="ArgumentException">Thrown when both "before" and "after" is set</exception>
public async IAsyncEnumerable<DiscordEntitlement> ListEntitlementsAsync
(
ulong? userId = null,
IEnumerable<ulong>? skuIds = null,
ulong? before = null,
ulong? after = null,
int limit = 100,
ulong? guildId = null,
bool? excludeEnded = null,
[EnumeratorCancellation]
CancellationToken cancellationToken = default
)
{
if (before is not null && after is not null)
{
throw new ArgumentException("before and after are mutually exclusive.");
}

bool isAscending = before is null;

while (limit > 0 && !cancellationToken.IsCancellationRequested)
{
int entitlementsThisRequest = Math.Min(100, limit);
limit -= entitlementsThisRequest;

IReadOnlyList<DiscordEntitlement> entitlements
= await this.Discord.ApiClient.ListEntitlementsAsync(this.Id, userId, skuIds, before, after, guildId, excludeEnded, limit);

if (entitlements.Count == 0)
{
yield break;
}

if (isAscending)
{
foreach (DiscordEntitlement entitlement in entitlements)
{
yield return entitlement;
}

after = entitlements.Last().Id;
}
else
{
for (int i = entitlements.Count - 1; i >= 0; i--)
{
yield return entitlements[i];
}

before = entitlements.First().Id;
}

if (entitlements.Count != entitlementsThisRequest)
{
yield break;
}
}

}
}
59 changes: 59 additions & 0 deletions DSharpPlus/Entities/Application/DiscordEntitlement.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using System;

namespace DSharpPlus.Entities;

/// <summary>
/// Entitlement owned by a user or guild
/// </summary>
public sealed class DiscordEntitlement
{
/// <summary>
/// ID of the entitlement
/// </summary>
public ulong Id { get; internal set; }

/// <summary>
/// ID of the SKU
/// </summary>
public ulong StockKeepingUnitId { get; internal set; }

/// <summary>
/// ID of the parent application
/// </summary>
public ulong ApplicationId { get; internal set; }

/// <summary>
/// ID of the user that is granted access to the entitlement's sku
/// </summary>
public ulong? UserId { get; internal set; }

/// <summary>
/// Type of entitlement
/// </summary>
public DiscordEntitlementType Type { get; internal set; }

/// <summary>
/// Entitlement was deleted
/// </summary>
public bool Deleted { get; internal set; }

/// <summary>
/// Start date at which the entitlement is valid. Not present when using test entitlements.
/// </summary>
public DateTimeOffset? StartsAt { get; internal set; }

/// <summary>
/// Date at which the entitlement is no longer valid. Not present when using test entitlements.
/// </summary>
public DateTimeOffset? EndsAt { get; internal set; }

/// <summary>
/// ID of the guild that is granted access to the entitlement's sku
/// </summary>
public ulong? GuildId { get; internal set; }

/// <summary>
/// For consumable items, whether or not the entitlement has been consumed
/// </summary>
public bool? Consumed { get; internal set; }
}
47 changes: 47 additions & 0 deletions DSharpPlus/Entities/Application/DiscordEntitlementType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
namespace DSharpPlus.Entities;

/// <summary>
/// Type of Entitlement
/// </summary>
public enum DiscordEntitlementType
{
/// <summary>
/// Entitlement was purchased by user
/// </summary>
Purchase,

/// <summary>
/// Entitlement for Discord Nitro subscription
/// </summary>
PremiumSubscription,

/// <summary>
/// Entitlement was gifted by developer
/// </summary>
DeveloperGift,

/// <summary>
/// Entitlement was purchased by a dev in application test mode
/// </summary>
TestModePurchase,

/// <summary>
/// Entitlement was granted when the SKU was free
/// </summary>
FreePurchase,

/// <summary>
/// Entitlement was gifted by another user
/// </summary>
UserGift,

/// <summary>
/// Entitlement was claimed by user for free as a Nitro Subscriber
/// </summary>
PremiumPurchase,

/// <summary>
/// Entitlement was purchased as an app subscription
/// </summary>
ApplicationSubscription
}
37 changes: 37 additions & 0 deletions DSharpPlus/Entities/Application/DiscordStockKeepingUnit.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
namespace DSharpPlus.Entities;

/// <summary>
/// SKUs (stock-keeping units) in Discord represent premium offerings that can be made available to your application's users or guilds.
/// </summary>
public sealed class DiscordStockKeepingUnit
{
/// <summary>
/// Id of this entity
/// </summary>
public ulong Id { get; internal set; }

/// <summary>
/// Type of stock keeping unit
/// </summary>
public DiscordStockKeepingUnitType Type { get; internal set; }

/// <summary>
/// ID of the parent application
/// </summary>
public ulong ApplicationId { get; internal set; }

/// <summary>
/// Customer-facing name of your premium offering
/// </summary>
public string Name { get; internal set; }

/// <summary>
/// System-generated URL slug based on the SKU's name
/// </summary>
public string Slug { get; internal set; }

/// <summary>
/// Stock keeping unit flags
/// </summary>
public DiscordStockKeepingUnitFlags Flags { get; internal set; }
}
25 changes: 25 additions & 0 deletions DSharpPlus/Entities/Application/DiscordStockKeepingUnitFlags.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System;

namespace DSharpPlus.Entities;

/// <summary>
/// Represent additional information about the SKU
/// </summary>
[Flags]
public enum DiscordStockKeepingUnitFlags
{
/// <summary>
/// SKU is available for purchase
/// </summary>
Available = 1 << 2,

/// <summary>
/// Recurring SKU that can be purchased by a user and applied to a single server. Grants access to every user in that server.
/// </summary>
GuildSubscription = 1 << 7,

/// <summary>
/// Recurring SKU purchased by a user for themselves. Grants access to the purchasing user in every server.
/// </summary>
UserSubscription = 1 << 8
}
32 changes: 32 additions & 0 deletions DSharpPlus/Entities/Application/DiscordStockKeepingUnitType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
namespace DSharpPlus.Entities;

/// <summary>
/// For subscriptions, SKUs will have a type of either <c>Subscription</c> represented by <c>type: 5</c> or
/// <c>SubscriptionGroup</c> represented by <c>type:6</c> .
/// <br/>
/// For any current implementations, you will want to use the SKU
/// defined by <c>type: 5</c> . A <c>SubscriptionGroup</c> is automatically created for each <c>Subscription</c> SKU
/// and are not used at this time.
/// </summary>
public enum DiscordStockKeepingUnitType
{
/// <summary>
/// Durable one-time purchase
/// </summary>
Durable = 2,

/// <summary>
/// Consumable one-time purchase
/// </summary>
Consumable = 3,

/// <summary>
/// Represents a recurring subscription
/// </summary>
Subscription = 5,

/// <summary>
/// System-generated group for each SUBSCRIPTION SKU created
/// </summary>
SubscriptionGroup = 6
}
Loading