Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: added support for Premium App Subscriptions #969

Merged
merged 17 commits into from
Oct 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,9 @@
"cvtps",
"neww",
"STDCORO",
"NOMINMAX"
"NOMINMAX",
"sku",
"skus"
],
"flagWords": [
"hte"
Expand Down
165 changes: 138 additions & 27 deletions include/dpp/appcommand.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#include <dpp/channel.h>
#include <dpp/role.h>
#include <dpp/user.h>
#include <dpp/entitlement.h>
#include <variant>
#include <map>
#include <dpp/json_fwd.h>
Expand Down Expand Up @@ -297,13 +298,51 @@ void to_json(nlohmann::json& j, const command_option& opt);
* @brief Response types when responding to an interaction within on_interaction_create.
*/
enum interaction_response_type {
ir_pong = 1, //!< Acknowledge a Ping
ir_channel_message_with_source = 4, //!< respond to an interaction with a message
ir_deferred_channel_message_with_source = 5, //!< Acknowledge an interaction and edit a response later, the user sees a loading state
ir_deferred_update_message = 6, //!< for components, acknowledge an interaction and edit the original message later; the user does not see a loading state
ir_update_message = 7, //!< for components, edit the message the component was attached to
ir_autocomplete_reply = 8, //!< Reply to autocomplete interaction. Be sure to do this within 500ms of the interaction!
ir_modal_dialog = 9, //!< A modal dialog box
/**
* @brief Acknowledge a Ping
*/
ir_pong = 1,
/**
* @brief Respond to an interaction with a message.
*/
ir_channel_message_with_source = 4,
/**
* @brief Acknowledge an interaction and edit a response later, the user sees a loading state
*/
ir_deferred_channel_message_with_source = 5,

/**
* @brief For components, acknowledge an interaction and edit the original message later; the user does not see a loading state.
*/
ir_deferred_update_message = 6,

/**
* @brief For components, edit the message the component was attached to.
*/
ir_update_message = 7,

/**
* @brief Reply to autocomplete interaction.
*
* @note Be sure to do this within 500ms of the interaction!
*/
ir_autocomplete_reply = 8,

/**
* @brief A modal dialog box
*
* @note Not available for modal submit and ping interactions.
*/
ir_modal_dialog = 9,

/**
* @brief Acknowledge a interaction with an upgrade button, only available for apps with monetization enabled.
*
* @see https://discord.com/developers/docs/monetization/entitlements#premiumrequired-interaction-response
* @note Not available for autocomplete and ping interactions.
* @warning This response does not support using `content`, `embeds`, or `attachments`, so reply with no data when using this!
*/
ir_premium_required = 10,
};

/**
Expand Down Expand Up @@ -732,26 +771,98 @@ class DPP_EXPORT interaction : public managed, public json_interface<interaction
virtual json to_json_impl(bool with_id = false) const;

public:
snowflake application_id; //!< id of the application this interaction is for
uint8_t type; //!< the type of interaction (dpp::interaction_type)
std::variant<command_interaction, component_interaction, autocomplete_interaction> data; //!< Optional: the command data payload
snowflake guild_id; //!< Optional: the guild it was sent from
snowflake channel_id; //!< Optional: the channel it was sent from
dpp::channel channel; //!< Optional: The partial channel object where it was sent from
snowflake message_id; //!< Originating message id for context menu actions
permission app_permissions; //!< Permissions of the bot in the channel/guild where this command was issued
message msg; //!< Originating message for context menu actions
guild_member member; //!< Optional: guild member data for the invoking user, including permissions. Filled when the interaction is invoked in a guild
user usr; //!< User object for the invoking user
std::string token; //!< a continuation token for responding to the interaction
uint8_t version; //!< read-only property, always 1
command_resolved resolved; //!< Resolved data e.g. users, members, roles, channels, permissions, etc.
std::string locale; //!< User's [locale](https://discord.com/developers/docs/reference#locales) (language)
std::string guild_locale; //!< Guild's locale (language) - for guild interactions only
cache_policy_t cache_policy; //!< Cache policy from cluster

/**
* @brief Construct a new interaction object
/**
* @brief ID of the application this interaction is for.
*/
snowflake application_id;

/**
* @brief The type of interaction from dpp::interaction_type.
*/
uint8_t type;

/**
* @brief Optional: the command data payload.
*/
std::variant<command_interaction, component_interaction, autocomplete_interaction> data;

/**
* @brief Optional: the guild it was sent from.
*/
snowflake guild_id;

/**
* @brief Optional: the channel it was sent from
*/
snowflake channel_id;

/**
* @brief Optional: The partial channel object where it was sent from.
*/
dpp::channel channel;

/**
* @brief Originating message id for context menu actions.
*/
snowflake message_id;

/**
* @brief Permissions of the bot in the channel/guild where this command was issued.
*/
permission app_permissions;

/**
* @brief Originating message for context menu actions.
*/
message msg;

/**
* @brief Optional: guild member data for the invoking user, including permissions. Filled when the interaction is invoked in a guild
*/
guild_member member;

/**
* @brief User object for the invoking user.
*/
user usr;

/**
* @brief A continuation token for responding to the interaction.
*/
std::string token;

/**
* @brief Read-only property, always 1.
*/
uint8_t version;

/**
* @brief Resolved data e.g. users, members, roles, channels, permissions, etc.
*/
command_resolved resolved;

/**
* @brief User's [locale](https://discord.com/developers/docs/reference#locales) (language).
*/
std::string locale;

/**
* @brief Guild's locale (language) - for guild interactions only.
*/
std::string guild_locale;

/**
* @brief Cache policy from cluster.
*/
cache_policy_t cache_policy;

/**
* @brief For monetized apps, any entitlements for the invoking user, representing access to premium SKUs.
*/
std::vector<entitlement> entitlements;

/**
* @brief Construct a new interaction object.
*/
interaction();

Expand Down
81 changes: 81 additions & 0 deletions include/dpp/cluster.h
Original file line number Diff line number Diff line change
Expand Up @@ -1312,6 +1312,36 @@ class DPP_EXPORT cluster {
*/
event_router_t<stage_instance_delete_t> on_stage_instance_delete;

/**
Jaskowicz1 marked this conversation as resolved.
Show resolved Hide resolved
* @brief Called when a user subscribes to an SKU.
*
* @see https://discord.com/developers/docs/monetization/entitlements#new-entitlement
* @note Use operator() to attach a lambda to this event, and the detach method to detach the listener using the returned ID.
* The function signature for this event takes a single `const` reference of type channel_delete_t&, and returns void.
*/
event_router_t<entitlement_create_t> on_entitlement_create;


/**
* @brief Called when a user's subscription renews for the next billing period.
* The `ends_at` field will have an updated value with the new expiration date.
*
* @see https://discord.com/developers/docs/monetization/entitlements#updated-entitlement
* @note Use operator() to attach a lambda to this event, and the detach method to detach the listener using the returned ID.
* The function signature for this event takes a single `const` reference of type channel_update_t&, and returns void.
*/
event_router_t<entitlement_update_t> on_entitlement_update;

/**
* @brief Called when a user's entitlement is deleted.
* These events are infrequent and only occur if Discord issues a refund, or Discord removes an entitlement via "internal tooling".
* Entitlements **are not deleted** when they expire.
*
* @see https://discord.com/developers/docs/monetization/entitlements#deleted-entitlement
* @note Use operator() to attach a lambda to this event, and the detach method to detach the listener using the returned ID.
* The function signature for this event takes a single `const` reference of type channel_update_t&, and returns void.
*/
event_router_t<entitlement_delete_t> on_entitlement_delete;

/**
* @brief Post a REST request. Where possible use a helper method instead like message_create
Expand Down Expand Up @@ -3624,6 +3654,57 @@ class DPP_EXPORT cluster {
*/
void automod_rule_delete(snowflake guild_id, snowflake rule_id, command_completion_event_t callback = utility::log_error());

/**
* @brief Returns all entitlements for a given app, active and expired.
*
* @see https://discord.com/developers/docs/monetization/entitlements#list-entitlements
* @param user_id User ID to look up entitlements for.
* @param sku_ids List of SKU IDs to check entitlements for.
* @param before_id Retrieve entitlements before this entitlement ID.
* @param after_id Retrieve entitlements after this entitlement ID.
* @param limit Number of entitlements to return, 1-100 (default 100).
* @param guild_id Guild ID to look up entitlements for.
* @param exclude_ended Whether ended entitlements should be excluded from the search.
* @param callback Function to call when the API call completes.
* On success the callback will contain a dpp::emoji_map object in confirmation_callback_t::value. On failure, the value is undefined and confirmation_callback_t::is_error() method will return true. You can obtain full error details with confirmation_callback_t::get_error().
*/
void entitlements_get(snowflake user_id = 0, const std::vector<snowflake>& sku_ids = {}, snowflake before_id = 0, snowflake after_id = 0, uint8_t limit = 100, snowflake guild_id = 0, bool exclude_ended = false, command_completion_event_t callback = utility::log_error());

/**
* @brief Creates a test entitlement to a given SKU for a given guild or user.
* Discord will act as though that user or guild has entitlement to your premium offering.
*
* @see https://discord.com/developers/docs/monetization/entitlements#create-test-entitlement
* @param new_entitlement The entitlement to create.
* Make sure your dpp::entitlement_type (inside your dpp::entitlement object) matches the type of the owner_id
* (if type is guild, owner_id is a guild id), otherwise it won't work!
* @param callback Function to call when the API call completes.
* On success the callback will contain a dpp::entitlement object in confirmation_callback_t::value. On failure, the value is undefined and confirmation_callback_t::is_error() method will return true. You can obtain full error details with confirmation_callback_t::get_error().
*/
void entitlement_test_create(const class entitlement& new_entitlement, command_completion_event_t callback = utility::log_error());

/**
* @brief Deletes a currently-active test entitlement.
* Discord will act as though that user or guild no longer has entitlement to your premium offering.
*
* @see https://discord.com/developers/docs/monetization/entitlements#delete-test-entitlement
* @param entitlement_id The test entitlement to delete.
* @param callback Function to call when the API call completes.
* On success the callback will contain a dpp::confirmation object in confirmation_callback_t::value. On failure, the value is undefined and confirmation_callback_t::is_error() method will return true. You can obtain full error details with confirmation_callback_t::get_error().
*/
void entitlement_test_delete(snowflake entitlement_id, command_completion_event_t callback = utility::log_error());

/**
* @brief Returns all SKUs for a given application.
* @note Because of how Discord's SKU and subscription systems work, you will see two SKUs for your premium offering.
* For integration and testing entitlements, you should use the SKU with type: 5.
*
* @see https://discord.com/developers/docs/monetization/skus#list-skus
* @param callback Function to call when the API call completes.
* On success the callback will contain a dpp::confirmation object in confirmation_callback_t::value. On failure, the value is undefined and confirmation_callback_t::is_error() method will return true. You can obtain full error details with confirmation_callback_t::get_error().
*/
void skus_get(command_completion_event_t callback = utility::log_error());

#include <dpp/cluster_sync_calls.h>
#ifdef DPP_CORO
#include <dpp/cluster_coro_calls.h>
Expand Down
55 changes: 55 additions & 0 deletions include/dpp/cluster_coro_calls.h
Original file line number Diff line number Diff line change
Expand Up @@ -696,6 +696,49 @@
*/
[[nodiscard]] async<confirmation_callback_t> co_guild_emojis_get(snowflake guild_id);

/**
* @brief Returns all entitlements for a given app, active and expired.
*
* @see dpp::cluster::entitlements_get
* @see https://discord.com/developers/docs/monetization/entitlements#list-entitlements
* @param user_id User ID to look up entitlements for.
* @param sku_ids List of SKU IDs to check entitlements for.
* @param before_id Retrieve entitlements before this entitlement ID.
* @param after_id Retrieve entitlements after this entitlement ID.
* @param limit Number of entitlements to return, 1-100 (default 100).
* @param guild_id Guild ID to look up entitlements for.
* @param exclude_ended Whether ended entitlements should be excluded from the search.
* @return entitlement_map returned object on completion
* \memberof dpp::cluster
*/
[[nodiscard]] async<confirmation_callback_t> co_entitlements_get(snowflake user_id = 0, const std::vector<snowflake>& sku_ids = {}, snowflake before_id = 0, snowflake after_id = 0, uint8_t limit = 100, snowflake guild_id = 0, bool exclude_ended = false);

/**
* @brief Creates a test entitlement to a given SKU for a given guild or user.
* Discord will act as though that user or guild has entitlement to your premium offering.
*
* @see dpp::cluster::entitlement_test_create
* @see https://discord.com/developers/docs/monetization/entitlements#create-test-entitlement
* @param new_entitlement The entitlement to create.
* Make sure your dpp::entitlement_type (inside your dpp::entitlement object) matches the type of the owner_id
* (if type is guild, owner_id is a guild id), otherwise it won't work!
* @return entitlement returned object on completion
* \memberof dpp::cluster
*/
[[nodiscard]] async<confirmation_callback_t> co_entitlement_test_create(const class entitlement& new_entitlement);

/**
* @brief Deletes a currently-active test entitlement.
* Discord will act as though that user or guild no longer has entitlement to your premium offering.
*
* @see dpp::cluster::entitlement_test_delete
* @see https://discord.com/developers/docs/monetization/entitlements#delete-test-entitlement
* @param entitlement_id The test entitlement to delete.
* @return confirmation returned object on completion
* \memberof dpp::cluster
*/
[[nodiscard]] async<confirmation_callback_t> co_entitlement_test_delete(snowflake entitlement_id);

/**
* @brief Get the gateway information for the bot using the token
* @see dpp::cluster::get_gateway_bot
Expand Down Expand Up @@ -1727,6 +1770,18 @@
*/
[[nodiscard]] async<confirmation_callback_t> co_guild_event_get(snowflake guild_id, snowflake event_id);

/**
* @brief Returns all SKUs for a given application.
* @note Because of how Discord's SKU and subscription systems work, you will see two SKUs for your premium offering.
* For integration and testing entitlements, you should use the SKU with type: 5.
*
* @see dpp::cluster::skus_get
* @see https://discord.com/developers/docs/monetization/skus#list-skus
* @return sku_map returned object on completion
* \memberof dpp::cluster
*/
[[nodiscard]] async<confirmation_callback_t> co_skus_get();


[[nodiscard]] async<confirmation_callback_t> co_stage_instance_create(const stage_instance& si);

Expand Down
Loading