diff --git a/src/http/client.rs b/src/http/client.rs index 3bfdb351b70..f8d73ab5462 100644 --- a/src/http/client.rs +++ b/src/http/client.rs @@ -873,6 +873,33 @@ impl Http { .await } + pub async fn create_test_entitlement( + &self, + sku_id: SkuId, + owner: EntitlementOwner, + ) -> Result { + let (owner_id, owner_type) = match owner { + EntitlementOwner::Guild(id) => (id.get(), 1), + EntitlementOwner::User(id) => (id.get(), 2), + }; + let map = json!({ + "sku_id": sku_id, + "owner_id": owner_id, + "owner_type": owner_type + }); + self.fire(Request { + body: Some(to_vec(&map)?), + multipart: None, + headers: None, + method: LightMethod::Post, + route: Route::Entitlements { + application_id: self.try_application_id()?, + }, + params: None, + }) + .await + } + /// Creates a webhook for the given [channel][`GuildChannel`]'s Id, passing in the given data. /// /// This method requires authentication. @@ -1349,6 +1376,21 @@ impl Http { .await } + pub async fn delete_test_entitlement(&self, entitlement_id: EntitlementId) -> Result<()> { + self.wind(204, Request { + body: None, + multipart: None, + headers: None, + method: LightMethod::Delete, + route: Route::Entitlement { + application_id: self.try_application_id()?, + entitlement_id, + }, + params: None, + }) + .await + } + /// Deletes a [`Webhook`] given its Id. /// /// This method requires authentication, whereas [`Self::delete_webhook_with_token`] does not. @@ -3071,6 +3113,49 @@ impl Http { .await } + pub async fn get_entitlements( + &self, + user_id: Option, + sku_ids: Option>, + // before: ? + // after: ? + limit: Option, + guild_id: Option, + exclude_ended: Option, + ) -> Result> { + let mut params = vec![]; + if let Some(user_id) = user_id { + params.push(("user_id", user_id.to_string())); + } + if let Some(sku_ids) = sku_ids { + params.push(( + "sku_ids", + sku_ids.iter().map(ToString::to_string).collect::>().join(","), + )); + } + if let Some(limit) = limit { + params.push(("limit", limit.to_string())); + } + if let Some(guild_id) = guild_id { + params.push(("guild_id", guild_id.to_string())); + } + if let Some(exclude_ended) = exclude_ended { + params.push(("exclude_ended", exclude_ended.to_string())); + } + + self.fire(Request { + body: None, + multipart: None, + headers: None, + method: LightMethod::Get, + route: Route::Entitlements { + application_id: self.try_application_id()?, + }, + params: Some(params), + }) + .await + } + /// Gets current gateway. pub async fn get_gateway(&self) -> Result { self.fire(Request { @@ -3924,6 +4009,20 @@ impl Http { .await } + pub async fn get_skus(&self) -> Result> { + self.fire(Request { + body: None, + multipart: None, + headers: None, + method: LightMethod::Get, + route: Route::Skus { + application_id: self.try_application_id()?, + }, + params: None, + }) + .await + } + /// Gets a sticker. pub async fn get_sticker(&self, sticker_id: StickerId) -> Result { self.fire(Request { diff --git a/src/http/routing.rs b/src/http/routing.rs index 03b54169959..1998ad86e06 100644 --- a/src/http/routing.rs +++ b/src/http/routing.rs @@ -450,6 +450,18 @@ routes! ('a, { api!("/applications/{}/guilds/{}/commands/permissions", application_id, guild_id), Some(RatelimitingKind::PathAndId(application_id.0)); + Skus { application_id: ApplicationId }, + api!("/applications/{}/skus", application_id), + Some(RatelimitingKind::PathAndId(application_id.0)); + + Entitlement { application_id: ApplicationId, entitlement_id: EntitlementId }, + api!("/applications/{}/entitlements/{}", application_id, entitlement_id), + Some(RatelimitingKind::PathAndId(application_id.0)); + + Entitlements { application_id: ApplicationId }, + api!("/applications/{}/entitlements", application_id), + Some(RatelimitingKind::PathAndId(application_id.0)); + StageInstances, api!("/stage-instances"), Some(RatelimitingKind::Path); diff --git a/src/model/id.rs b/src/model/id.rs index b6543891182..be59a536dbf 100644 --- a/src/model/id.rs +++ b/src/model/id.rs @@ -238,6 +238,10 @@ pub struct StageInstanceId(#[serde(with = "snowflake")] pub NonZeroU64); #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord, Deserialize, Serialize)] pub struct ForumTagId(#[serde(with = "snowflake")] pub NonZeroU64); +/// An identifier for an entitlement. +#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord, Deserialize, Serialize)] +pub struct EntitlementId(#[serde(with = "snowflake")] pub NonZeroU64); + id_u64! { AttachmentId; ApplicationId; @@ -264,6 +268,7 @@ id_u64! { StageInstanceId; RuleId; ForumTagId; + EntitlementId; } id_from_str! { diff --git a/src/model/mod.rs b/src/model/mod.rs index 55c610c2729..a994b203178 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -32,6 +32,7 @@ pub mod id; pub mod invite; pub mod mention; pub mod misc; +pub mod monetization; pub mod permissions; pub mod sticker; pub mod timestamp; @@ -90,6 +91,7 @@ pub mod prelude { invite::*, mention::*, misc::*, + monetization::*, permissions::*, sticker::*, user::*, diff --git a/src/model/monetization.rs b/src/model/monetization.rs new file mode 100644 index 00000000000..59411d16091 --- /dev/null +++ b/src/model/monetization.rs @@ -0,0 +1,78 @@ +use crate::model::prelude::*; + +/// A premium offering that can be made available to an application's users and guilds. +/// +/// [Discord docs](https://discord.com/developers/docs/monetization/skus#sku-object) +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Sku { + /// The unique ID of the SKU. + pub id: SkuId, + /// The type of the SKU. + #[serde(rename = "type")] + pub kind: SkuKind, + /// Id of the SKU's parent application. + pub application_id: ApplicationId, + /// The customer-facing name of the premium offering. + pub name: String, + /// A system-generated URL slug based on the SKU. + pub slug: String, +} + +enum_number! { + /// Differentiates between SKU types. + /// + /// [Discord docs](https://discord.com/developers/docs/monetization/skus#sku-object-sku-types) + #[derive(Clone, Debug, Serialize, Deserialize)] + #[serde(from = "u8", into = "u8")] + #[non_exhaustive] + pub enum SkuKind { + /// Represents a recurring subscription. + Subscription = 5, + /// A system-generated group for each SKU created of type [`SkuKind::Subscription`]. + SubscriptionGroup = 6, + _ => Unknown(u8), + } +} + +/// Represents that a user or guild has access to a premium offering in the application. +/// +/// [Discord docs](https://discord.com/developers/docs/monetization/entitlements#entitlement-object-entitlement-structure) +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Entitlement { + /// The ID of the entitlement. + pub id: EntitlementId, + /// The ID of the corresponding SKU. + pub sku_id: SkuId, + /// The ID of the user that is granted access to the SKU. + pub user_id: Option, + /// The ID of the guild that is granted access to the SKU. + pub guild_id: Option, + /// The ID of the parent application. + pub application_id: ApplicationId, + /// The type of the entitlement. + #[serde(rename = "type")] + pub kind: EntitlementKind, + /// Whether the entitlement has been consumed or not. Will always be `false` for ongoing App + /// Subscriptions. + pub consumed: bool, + /// Start date after which the entitlement is valid. Not present when using test entitlements. + pub starts_at: Option, + /// End date after which the entitlement is no longer valid. Not present when using test + /// entitlements. + pub ends_at: Option, +} + +enum_number! { + #[derive(Clone, Debug, Serialize, Deserialize)] + #[serde(from = "u8", into = "u8")] + #[non_exhaustive] + pub enum EntitlementKind { + ApplicationSubscription = 8, + _ => Unknown(u8), + } +} + +pub enum EntitlementOwner { + Guild(GuildId), + User(UserId), +}