Repository: serenity-rs/serenity (language: rust)

Issue:
Update to the monetization API
The [Discord API documentation](https://github.com/discord/discord-api-docs/commit/4853fbc9ba8df174fc8bf1abbe8a19bc8f14cd1c) updated with a few changes:
- Added a new sku_id field to the interaction buttons model.
- Added a new button style.
- Updated the conflicts between button fields.
- Deprecated the PREMIUM_REQUIRED interaction response type.
- Added 2 new links to store items (`Entitlement` and `Sku` could implement methods that could return these URLs):
  - `https://discord.com/application-directory/:appID/store`
  - `https://discord.com/application-directory/:appID/store/:skuID`


Discussion / hints:

Fix patch:
diff --git a/src/builder/create_components.rs b/src/builder/create_components.rs
index 43341217446..b4b2c3b0ed7 100644
--- a/src/builder/create_components.rs
+++ b/src/builder/create_components.rs
@@ -53,6 +53,21 @@ impl CreateButton {
         })
     }
 
+    /// Creates a new premium button associated with the given SKU.
+    ///
+    /// Clicking this button _will not_ trigger an interaction event in your bot.
+    pub fn new_premium(sku_id: impl Into<SkuId>) -> Self {
+        Self(Button {
+            kind: ComponentType::Button,
+            data: ButtonKind::Premium {
+                sku_id: sku_id.into(),
+            },
+            label: None,
+            emoji: None,
+            disabled: false,
+        })
+    }
+
     /// Creates a normal button with the given custom ID. You must also set [`Self::label`] and/or
     /// [`Self::emoji`] after this.
     pub fn new(custom_id: impl Into<String>) -> Self {
@@ -71,7 +86,7 @@ impl CreateButton {
     /// Sets the custom id of the button, a developer-defined identifier. Replaces the current
     /// value as set in [`Self::new`].
     ///
-    /// Has no effect on link buttons.
+    /// Has no effect on link buttons and premium buttons.
     pub fn custom_id(mut self, id: impl Into<String>) -> Self {
         if let ButtonKind::NonLink {
             custom_id, ..
@@ -84,7 +99,7 @@ impl CreateButton {
 
     /// Sets the style of this button.
     ///
-    /// Has no effect on link buttons.
+    /// Has no effect on link buttons and premium buttons.
     pub fn style(mut self, new_style: ButtonStyle) -> Self {
         if let ButtonKind::NonLink {
             style, ..
diff --git a/src/builder/create_interaction_response.rs b/src/builder/create_interaction_response.rs
index 3d29f98df04..2fc2a9daba1 100644
--- a/src/builder/create_interaction_response.rs
+++ b/src/builder/create_interaction_response.rs
@@ -63,6 +63,7 @@ pub enum CreateInteractionResponse {
     /// Responds to the interaction with an upgrade button.
     ///
     /// Corresponds to Discord's `PREMIUM_REQUIRED'.
+    #[deprecated = "use premium button components via `CreateButton::new_premium` instead"]
     PremiumRequired,
 }
 
@@ -70,6 +71,7 @@ impl serde::Serialize for CreateInteractionResponse {
     fn serialize<S: serde::Serializer>(&self, serializer: S) -> StdResult<S::Ok, S::Error> {
         use serde::ser::Error as _;
 
+        #[allow(deprecated)]
         #[allow(clippy::match_same_arms)] // hurts readability
         json!({
             "type": match self {
diff --git a/src/model/application/component.rs b/src/model/application/component.rs
index 3f45b8ecea3..997a5c5c0b6 100644
--- a/src/model/application/component.rs
+++ b/src/model/application/component.rs
@@ -105,6 +105,7 @@ impl From<SelectMenu> for ActionRowComponent {
 #[serde(untagged)]
 pub enum ButtonKind {
     Link { url: String },
+    Premium { sku_id: SkuId },
     NonLink { custom_id: String, style: ButtonStyle },
 }
 
@@ -120,6 +121,8 @@ impl Serialize for ButtonKind {
             url: Option<&'a str>,
             #[serde(skip_serializing_if = "Option::is_none")]
             custom_id: Option<&'a str>,
+            #[serde(skip_serializing_if = "Option::is_none")]
+            sku_id: Option<SkuId>,
         }
 
         let helper = match self {
@@ -129,6 +132,15 @@ impl Serialize for ButtonKind {
                 style: 5,
                 url: Some(url),
                 custom_id: None,
+                sku_id: None,
+            },
+            ButtonKind::Premium {
+                sku_id,
+            } => Helper {
+                style: 6,
+                url: None,
+                custom_id: None,
+                sku_id: Some(*sku_id),
             },
             ButtonKind::NonLink {
                 custom_id,
@@ -137,6 +149,7 @@ impl Serialize for ButtonKind {
                 style: (*style).into(),
                 url: None,
                 custom_id: Some(custom_id),
+                sku_id: None,
             },
         };
         helper.serialize(serializer)
@@ -323,5 +336,13 @@ mod tests {
             &button,
             json!({"type": 2, "style": 5, "url": "https://google.com", "label": "a", "disabled": false}),
         );
+
+        button.data = ButtonKind::Premium {
+            sku_id: 1234965026943668316.into(),
+        };
+        assert_json(
+            &button,
+            json!({"type": 2, "style": 6, "sku_id": "1234965026943668316", "label": "a", "disabled": false}),
+        );
     }
 }
diff --git a/src/model/application/mod.rs b/src/model/application/mod.rs
index 7750c478b66..6754212895e 100644
--- a/src/model/application/mod.rs
+++ b/src/model/application/mod.rs
@@ -82,6 +82,17 @@ pub struct CurrentApplicationInfo {
         std::collections::HashMap<InstallationContext, InstallationContextConfig>,
 }
 
+impl CurrentApplicationInfo {
+    /// Returns the store url for the application. If included in a message, will render as a rich
+    /// embed. See the [Discord docs] for details.
+    ///
+    /// [Discord docs]: https://discord.com/developers/docs/monetization/managing-your-store#linking-to-your-store
+    #[must_use]
+    pub fn store_url(&self) -> String {
+        format!("https://discord.com/application-directory/{}/store", self.id)
+    }
+}
+
 #[cfg(feature = "unstable_discord_api")]
 enum_number! {
     /// An enum representing the [installation contexts].
diff --git a/src/model/monetization.rs b/src/model/monetization.rs
index 8b6cc2332d3..7808cb0de0e 100644
--- a/src/model/monetization.rs
+++ b/src/model/monetization.rs
@@ -20,6 +20,20 @@ pub struct Sku {
     pub flags: SkuFlags,
 }
 
+impl Sku {
+    /// Returns the store url for this SKU. If included in a message, will render as a rich embed.
+    /// See the [Discord docs] for details.
+    ///
+    /// [Discord docs]: https://discord.com/developers/docs/monetization/skus#linking-to-your-skus
+    #[must_use]
+    pub fn url(&self) -> String {
+        format!(
+            "https://discord.com/application-directory/{}/store/{}",
+            self.application_id, self.id
+        )
+    }
+}
+
 enum_number! {
     /// Differentiates between SKU classes.
     ///
@@ -82,6 +96,17 @@ pub struct Entitlement {
     pub guild_id: Option<GuildId>,
 }
 
+impl Entitlement {
+    /// Returns a link to the SKU corresponding to this entitlement. See [`Sku::url`] for details.
+    #[must_use]
+    pub fn sku_url(&self) -> String {
+        format!(
+            "https://discord.com/application-directory/{}/store/{}",
+            self.application_id, self.sku_id
+        )
+    }
+}
+
 enum_number! {
     /// Differentiates between Entitlement types.
     ///


Test patch: