diff --git a/.cspell.json b/.cspell.json index 459a14315c..8b263fcb2a 100644 --- a/.cspell.json +++ b/.cspell.json @@ -133,7 +133,9 @@ "cvtps", "neww", "STDCORO", - "NOMINMAX" + "NOMINMAX", + "sku", + "skus" ], "flagWords": [ "hte" diff --git a/include/dpp/appcommand.h b/include/dpp/appcommand.h index 10d49456dc..5ffc850525 100644 --- a/include/dpp/appcommand.h +++ b/include/dpp/appcommand.h @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -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, }; /** @@ -732,26 +771,98 @@ class DPP_EXPORT interaction : public managed, public json_interface 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 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 entitlements; + + /** + * @brief Construct a new interaction object. */ interaction(); diff --git a/include/dpp/cluster.h b/include/dpp/cluster.h index 219e60800b..d74a69472a 100644 --- a/include/dpp/cluster.h +++ b/include/dpp/cluster.h @@ -1312,6 +1312,36 @@ class DPP_EXPORT cluster { */ event_router_t on_stage_instance_delete; + /** + * @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 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 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 on_entitlement_delete; /** * @brief Post a REST request. Where possible use a helper method instead like message_create @@ -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& 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 #ifdef DPP_CORO #include diff --git a/include/dpp/cluster_coro_calls.h b/include/dpp/cluster_coro_calls.h index ddbb9a2395..22cdcef0c4 100644 --- a/include/dpp/cluster_coro_calls.h +++ b/include/dpp/cluster_coro_calls.h @@ -696,6 +696,49 @@ */ [[nodiscard]] async 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 co_entitlements_get(snowflake user_id = 0, const std::vector& 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 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 co_entitlement_test_delete(snowflake entitlement_id); + /** * @brief Get the gateway information for the bot using the token * @see dpp::cluster::get_gateway_bot @@ -1727,6 +1770,18 @@ */ [[nodiscard]] async 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 co_skus_get(); + [[nodiscard]] async co_stage_instance_create(const stage_instance& si); diff --git a/include/dpp/cluster_sync_calls.h b/include/dpp/cluster_sync_calls.h index 1594d8e0a4..26e81f111a 100644 --- a/include/dpp/cluster_sync_calls.h +++ b/include/dpp/cluster_sync_calls.h @@ -864,6 +864,58 @@ emoji guild_emoji_get_sync(snowflake guild_id, snowflake emoji_id); */ emoji_map guild_emojis_get_sync(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 + * @throw dpp::rest_exception upon failure to execute REST function + * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. + * Avoid direct use of this function inside an event handler. + */ +entitlement_map entitlements_get_sync(snowflake user_id = 0, const std::vector& 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 + * @throw dpp::rest_exception upon failure to execute REST function + * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. + * Avoid direct use of this function inside an event handler. + */ +entitlement entitlement_test_create_sync(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 + * @throw dpp::rest_exception upon failure to execute REST function + * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. + * Avoid direct use of this function inside an event handler. + */ +confirmation entitlement_test_delete_sync(snowflake entitlement_id); + /** * @brief Get the gateway information for the bot using the token * @see dpp::cluster::get_gateway_bot @@ -2117,6 +2169,21 @@ scheduled_event guild_event_edit_sync(const scheduled_event& event); */ scheduled_event guild_event_get_sync(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 + * @throw dpp::rest_exception upon failure to execute REST function + * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. + * Avoid direct use of this function inside an event handler. + */ +sku_map skus_get_sync(); + stage_instance stage_instance_create_sync(const stage_instance& si); diff --git a/include/dpp/dispatcher.h b/include/dpp/dispatcher.h index 5fc92ef4fd..0df6996f42 100644 --- a/include/dpp/dispatcher.h +++ b/include/dpp/dispatcher.h @@ -42,6 +42,7 @@ #include #include #include +#include #include #include #include @@ -1918,5 +1919,38 @@ struct DPP_EXPORT voice_client_disconnect_t : public event_dispatch_t { snowflake user_id = {}; }; +/** @brief Delete stage instance */ +struct DPP_EXPORT entitlement_create_t : public event_dispatch_t { + using event_dispatch_t::event_dispatch_t; + using event_dispatch_t::operator=; + + /** + * @brief The created entitlement. + */ + entitlement created = {}; +}; + +/** @brief Delete stage instance */ +struct DPP_EXPORT entitlement_update_t : public event_dispatch_t { + using event_dispatch_t::event_dispatch_t; + using event_dispatch_t::operator=; + + /** + * @brief The entitlement that was updated. + */ + entitlement updating_entitlement = {}; +}; + +/** @brief Delete stage instance */ +struct DPP_EXPORT entitlement_delete_t : public event_dispatch_t { + using event_dispatch_t::event_dispatch_t; + using event_dispatch_t::operator=; + + /** + * @brief The deleted entitlement. + */ + entitlement deleted = {}; +}; + } // namespace dpp diff --git a/include/dpp/entitlement.h b/include/dpp/entitlement.h new file mode 100644 index 0000000000..e1cfab8818 --- /dev/null +++ b/include/dpp/entitlement.h @@ -0,0 +1,160 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2021 Craig Edwards and D++ contributors + * (https://github.com/brainboxdotcc/DPP/graphs/contributors) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ************************************************************************************/ + +#pragma once +#include +#include +#include +#include +#include +#include + +namespace dpp { + +/** + * @brief The type of entitlement. + * */ +enum entitlement_type : uint8_t { + /** + * @brief A subscription for a guild. + * @warning This can only be used when creating a test entitlement. + */ + GUILD_SUBSCRIPTION = 1, + /** + * @brief A subscription for a user. + * @warning This can only be used when creating a test entitlement. + */ + USER_SUBSCRIPTION = 2, + /** + * @brief Entitlement was purchased as an app subscription. + */ + APPLICATION_SUBSCRIPTION = 8 +}; + +/** + * @brief Entitlement flags. + */ +enum entitlement_flags : uint16_t { + /** + * @brief Entitlement was deleted + */ + ent_deleted = 0b000000000000001, +}; + +/** + * @brief A definition of a discord entitlement. + */ +class DPP_EXPORT entitlement : public managed, public json_interface { +protected: + friend struct json_interface; + + /** Read class values from json object + * @param j A json object to read from + * @return A reference to self + */ + entitlement& fill_from_json_impl(nlohmann::json* j); + + /** + * @brief Build json for this entitlement object + * + * @param with_id include the ID in the json + * @return json JSON object + */ + json to_json_impl(bool with_id = false) const; + +public: + /** + * @brief ID of the SKU + */ + snowflake sku_id{0}; + + /** + * @brief ID of the parent application + */ + snowflake application_id{0}; + + /** + * @brief Optional: ID of the user/guild that is granted access to the entitlement's SKU + */ + snowflake owner_id{0}; + + /** + * @brief The type of entitlement. + */ + entitlement_type type = entitlement_type::APPLICATION_SUBSCRIPTION; + + /** + * @brief Optional: Start date at which the entitlement is valid. + * + * @note Not present when using test entitlements. + */ + time_t starts_at{0}; + + /** + * @brief Optional: Date at which the entitlement is no longer valid. + * + * @note Not present when using test entitlements. + */ + time_t ends_at{0}; + + /** + * @brief Flags bitmap from dpp::entitlement_flags + */ + uint16_t flags{0}; + + /** + * @brief Construct a new entitlement object + */ + entitlement() = default; + + /** + * @brief Construct a new entitlement object with sku_id, ID, application_id, type, and flags. + * + * @param sku_id The ID of the SKU. + * @param id The ID of the entitlement. + * @param application_id The ID of the parent application. + * @param type The type of entitlement (Should only ever be APPLICATION_SUBSCRIPTION unless you going to use this object as a parameter for dpp::cluster::entitlement_test_create). + * @param flags The flags for the SKU from dpp::entitlement_flags. + */ + entitlement(const snowflake sku_id, const snowflake id = 0, const snowflake application_id = 0, const entitlement_type type = dpp::entitlement_type::APPLICATION_SUBSCRIPTION, const uint8_t flags = 0); + + /** + * @brief Get the type of entitlement. + * + * @return entitlement_type Entitlement type + */ + entitlement_type get_type() const; + + /** + * @brief Was the entitlement deleted? + * + * @return true if the entitlement was deleted. + */ + bool is_deleted() const; +}; + +/** + * @brief Group of entitlements. + */ +typedef std::unordered_map entitlement_map; + +} // namespace dpp diff --git a/include/dpp/event.h b/include/dpp/event.h index b0cb96ea08..b9b3f003f7 100644 --- a/include/dpp/event.h +++ b/include/dpp/event.h @@ -148,4 +148,9 @@ event_decl(automod_rule_execute, AUTO_MODERATION_ACTION_EXECUTION); /* Audit log */ event_decl(guild_audit_log_entry_create, GUILD_AUDIT_LOG_ENTRY_CREATE); +/* Entitlements */ +event_decl(entitlement_create, ENTITLEMENT_CREATE); +event_decl(entitlement_update, ENTITLEMENT_UPDATE); +event_decl(entitlement_delete, ENTITLEMENT_DELETE); + } // namespace dpp::events diff --git a/include/dpp/restresults.h b/include/dpp/restresults.h index 9033241c5d..a340b50ebc 100644 --- a/include/dpp/restresults.h +++ b/include/dpp/restresults.h @@ -43,6 +43,8 @@ #include #include #include +#include +#include namespace dpp { @@ -202,7 +204,11 @@ typedef std::variant< automod_rule, automod_rule_map, onboarding, - welcome_screen + welcome_screen, + entitlement, + entitlement_map, + sku, + sku_map > confirmable_t; /** diff --git a/include/dpp/sku.h b/include/dpp/sku.h new file mode 100644 index 0000000000..88b07d0728 --- /dev/null +++ b/include/dpp/sku.h @@ -0,0 +1,159 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2021 Craig Edwards and D++ contributors + * (https://github.com/brainboxdotcc/DPP/graphs/contributors) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ************************************************************************************/ + +#pragma once +#include +#include +#include +#include +#include +#include + +namespace dpp { + +/** + * @brief The type of SKU. + * */ +enum sku_type : uint8_t { + /** + * @brief Represents a recurring subscription + */ + SUBSCRIPTION = 5, + /** + * @brief System-generated group for each SUBSCRIPTION SKU created + * @warning These are automatically created for each subscription SKU and are not used at this time. Please refrain from using these. + */ + SUBSCRIPTION_GROUP = 6, +}; + +/** + * @brief SKU flags. + */ +enum sku_flags : uint16_t { + /** + * @brief SKU is available for purchase + */ + sku_available = 0b000000000000100, + /** + * @brief Recurring SKU that can be purchased by a user and applied to a single server. Grants access to every user in that server. + */ + sku_guild_subscription = 0b000000010000000, + /** + * @brief Recurring SKU purchased by a user for themselves. Grants access to the purchasing user in every server. + */ + sku_user_subscription = 0b000000100000000, +}; + +/** + * @brief A definition of a discord SKU. + */ +class DPP_EXPORT sku : public managed, public json_interface { +protected: + friend struct json_interface; + + /** Read class values from json object + * @param j A json object to read from + * @return A reference to self + */ + sku& fill_from_json_impl(nlohmann::json* j); + + /** + * @brief Build json for this SKU object + * + * @param with_id include the ID in the json + * @return json JSON object + */ + json to_json_impl(bool with_id = false) const; + +public: + /** + * @brief The type of SKU. + */ + sku_type type = sku_type::SUBSCRIPTION; + + /** + * @brief ID of the parent application + */ + snowflake application_id{0}; + + /** + * @brief Customer-facing name of your premium offering + */ + std::string name{}; + + /** + * @brief System-generated URL slug based on the SKU's name + */ + std::string slug{}; + + /** + * @brief Flags bitmap from dpp::sku_flags + */ + uint16_t flags{0}; + + /** + * @brief Construct a new SKU object + */ + sku() = default; + + /** + * @brief Construct a new SKU object with all data required. + * + * @param id SKU id. + */ + sku(const snowflake id, const sku_type type, const snowflake application_id, const std::string name, const std::string slug, const uint16_t flags); + + /** + * @brief Get the type of SKU. + * + * @return sku_type SKU type + */ + sku_type get_type() const; + + /** + * @brief Is the SKU available for purchase? + * + * @return true if the SKU can be purchased. + */ + bool is_available() const; + + /** + * @brief Is the SKU a guild subscription? + * + * @return true if the SKU is a guild subscription. + */ + bool is_guild_subscription() const; + + /** + * @brief Is the SKU a user subscription? + * + * @return true if the SKU is a user subscription + */ + bool is_user_subscription() const; +}; + +/** + * @brief Group of SKUs. + */ +typedef std::unordered_map sku_map; + +} // namespace dpp diff --git a/src/dpp/cluster/entitlement.cpp b/src/dpp/cluster/entitlement.cpp new file mode 100644 index 0000000000..1783e79296 --- /dev/null +++ b/src/dpp/cluster/entitlement.cpp @@ -0,0 +1,80 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * Copyright 2021 Craig Edwards and D++ contributors + * (https://github.com/brainboxdotcc/DPP/graphs/contributors) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ************************************************************************************/ +#include +#include + +namespace dpp { + +void cluster::entitlements_get(snowflake user_id, const std::vector& sku_ids, snowflake before_id, snowflake after_id, uint8_t limit, snowflake guild_id, bool exclude_ended, command_completion_event_t callback) { + json j; + + if(user_id) { + j["user_id"] = user_id.str(); + } + + if(!sku_ids.empty()) { + /* Why can't Discord just be consistent and accept an array of ids???? + * Why just out of nowhere introduce a "comma-delimited set of snowflakes", like what. + * Just allow an array like you normally do!!!!!!!!!!!! + */ + std::string ids = ""; + for(size_t i = 0; i(this, API_PATH "/applications", me.id.str(), "entitlements", m_get, j, callback); +} + +void cluster::entitlement_test_create(const class entitlement& new_entitlement, command_completion_event_t callback) { + json j; + j["sku_id"] = new_entitlement.sku_id.str(); + j["owner_id"] = new_entitlement.owner_id.str(); + j["owner_type"] = new_entitlement.type; + rest_request(this, API_PATH "/applications", me.id.str(), "entitlements", m_post, j, callback); +} + +void cluster::entitlement_test_delete(const class snowflake entitlement_id, command_completion_event_t callback) { + rest_request(this, API_PATH "/applications", me.id.str(), "entitlements/" + entitlement_id.str(), m_delete, "", callback); +} + +} // namespace dpp diff --git a/src/dpp/cluster/sku.cpp b/src/dpp/cluster/sku.cpp new file mode 100644 index 0000000000..20f23f3e77 --- /dev/null +++ b/src/dpp/cluster/sku.cpp @@ -0,0 +1,30 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * Copyright 2021 Craig Edwards and D++ contributors + * (https://github.com/brainboxdotcc/DPP/graphs/contributors) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ************************************************************************************/ +#include +#include + +namespace dpp { + +void cluster::skus_get(command_completion_event_t callback) { + rest_request_list(this, API_PATH "/applications", me.id.str(), "entitlements", m_get, "", callback); +} + +} // namespace dpp diff --git a/src/dpp/cluster_coro_calls.cpp b/src/dpp/cluster_coro_calls.cpp index 528df234ab..d03183cdeb 100644 --- a/src/dpp/cluster_coro_calls.cpp +++ b/src/dpp/cluster_coro_calls.cpp @@ -259,6 +259,18 @@ async cluster::co_guild_emojis_get(snowflake guild_id) return async{ this, static_cast(&cluster::guild_emojis_get), guild_id }; } +async cluster::co_entitlements_get(snowflake user_id, const std::vector& sku_ids, snowflake before_id, snowflake after_id, uint8_t limit, snowflake guild_id, bool exclude_ended) { + return async{ this, static_cast&, snowflake, snowflake, uint8_t, snowflake, bool, command_completion_event_t)>(&cluster::entitlements_get), user_id, sku_ids, before_id, after_id, limit, guild_id, exclude_ended }; +} + +async cluster::co_entitlement_test_create(const class entitlement& new_entitlement) { + return async{ this, static_cast(&cluster::entitlement_test_create), new_entitlement }; +} + +async cluster::co_entitlement_test_delete(const class snowflake entitlement_id) { + return async{ this, static_cast(&cluster::entitlement_test_delete), entitlement_id }; +} + async cluster::co_get_gateway_bot() { return async{ this, static_cast(&cluster::get_gateway_bot) }; } @@ -567,6 +579,10 @@ async cluster::co_guild_event_get(snowflake guild_id, s return async{ this, static_cast(&cluster::guild_event_get), guild_id, event_id }; } +async cluster::co_skus_get() { + return async{ this, static_cast(&cluster::skus_get) }; +} + async cluster::co_stage_instance_create(const stage_instance& si) { return async{ this, static_cast(&cluster::stage_instance_create), si }; } diff --git a/src/dpp/cluster_sync_calls.cpp b/src/dpp/cluster_sync_calls.cpp index 0167edddb2..66e057c1ee 100644 --- a/src/dpp/cluster_sync_calls.cpp +++ b/src/dpp/cluster_sync_calls.cpp @@ -257,6 +257,18 @@ emoji_map cluster::guild_emojis_get_sync(snowflake guild_id) { return dpp::sync(this, static_cast(&cluster::guild_emojis_get), guild_id); } +entitlement_map cluster::entitlements_get_sync(snowflake user_id, const std::vector& sku_ids, snowflake before_id, snowflake after_id, uint8_t limit, snowflake guild_id, bool exclude_ended) { + return dpp::sync(this, static_cast&, snowflake, snowflake, uint8_t, snowflake, bool, command_completion_event_t)>(&cluster::entitlements_get), user_id, sku_ids, before_id, after_id, limit, guild_id, exclude_ended); +} + +entitlement cluster::entitlement_test_create_sync(const class entitlement& new_entitlement) { + return dpp::sync(this, static_cast(&cluster::entitlement_test_create), new_entitlement); +} + +confirmation cluster::entitlement_test_delete_sync(const class snowflake entitlement_id) { + return dpp::sync(this, static_cast(&cluster::entitlement_test_delete), entitlement_id); +} + gateway cluster::get_gateway_bot_sync() { return dpp::sync(this, static_cast(&cluster::get_gateway_bot)); } @@ -565,6 +577,10 @@ scheduled_event cluster::guild_event_get_sync(snowflake guild_id, snowflake even return dpp::sync(this, static_cast(&cluster::guild_event_get), guild_id, event_id); } +sku_map cluster::skus_get_sync() { + return dpp::sync(this, static_cast(&cluster::skus_get)); +} + stage_instance cluster::stage_instance_create_sync(const stage_instance& si) { return dpp::sync(this, static_cast(&cluster::stage_instance_create), si); } diff --git a/src/dpp/discordevents.cpp b/src/dpp/discordevents.cpp index 7ca54a0d04..18396ed225 100644 --- a/src/dpp/discordevents.cpp +++ b/src/dpp/discordevents.cpp @@ -390,6 +390,9 @@ const std::map eventmap = { { "AUTO_MODERATION_RULE_DELETE", new dpp::events::automod_rule_delete() }, { "AUTO_MODERATION_ACTION_EXECUTION", new dpp::events::automod_rule_execute() }, { "GUILD_AUDIT_LOG_ENTRY_CREATE", new dpp::events::guild_audit_log_entry_create() }, + { "ENTITLEMENT_CREATE", new dpp::events::entitlement_create() }, + { "ENTITLEMENT_UPDATE", new dpp::events::entitlement_update() }, + { "ENTITLEMENT_DELETE", new dpp::events::entitlement_delete() }, }; void discord_client::handle_event(const std::string &event, json &j, const std::string &raw) diff --git a/src/dpp/entitlement.cpp b/src/dpp/entitlement.cpp new file mode 100644 index 0000000000..245eb3b383 --- /dev/null +++ b/src/dpp/entitlement.cpp @@ -0,0 +1,78 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2021 Craig Edwards and D++ contributors + * (https://github.com/brainboxdotcc/DPP/graphs/contributors) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ************************************************************************************/ +#include +#include +#include + +namespace dpp { + +using json = nlohmann::json; + +entitlement::entitlement(const snowflake _sku_id, const snowflake _id, const snowflake _application_id, const entitlement_type _type, const uint8_t _flags) + : managed(_id), sku_id(_sku_id), application_id(_application_id), type(_type), flags(_flags) {} + +entitlement& entitlement::fill_from_json_impl(nlohmann::json* j) { + set_snowflake_not_null(j, "id", id); + set_snowflake_not_null(j, "sku_id", sku_id); + set_snowflake_not_null(j, "application_id", application_id); + + /* Discord does separate these values, but asks for them as "owner_id" in the create event, just makes sense to make them as one as only one is ever set. */ + if(snowflake_not_null(j, "user_id")) { + set_snowflake_not_null(j, "user_id", owner_id); + } else if(snowflake_not_null(j, "guild_id")) { + set_snowflake_not_null(j, "guild_id", owner_id); + } + + type = static_cast(int8_not_null(j, "type")); + + if (bool_not_null(j, "deleted")) { + flags |= ent_deleted; + } + + set_ts_not_null(j, "starts_at", starts_at); + set_ts_not_null(j, "ends_at", ends_at); + + /* + * TODO: Look at the entitlement example on docs and see what we're missing, add it here after. Discord seems to be missing information in their structure as their example shows more data. + */ + + return *this; +} + +json entitlement::to_json_impl(bool with_id) const { + json j; + if (with_id) { + j["id"] = id.str(); + } + j["sku_id"] = sku_id.str(); + return j; +} + +entitlement_type entitlement::get_type() const { + return type; +} + +bool entitlement::is_deleted() const { + return flags & entitlement_flags::ent_deleted; +} + +} // namespace dpp diff --git a/src/dpp/events/entitlement_create.cpp b/src/dpp/events/entitlement_create.cpp new file mode 100644 index 0000000000..5356f88b98 --- /dev/null +++ b/src/dpp/events/entitlement_create.cpp @@ -0,0 +1,47 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2021 Craig Edwards and D++ contributors + * (https://github.com/brainboxdotcc/DPP/graphs/contributors) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ************************************************************************************/ +#include +#include +#include + +namespace dpp::events { + +/** + * @brief Handle event + * + * @param client Websocket client (current shard) + * @param j JSON data for the event + * @param raw Raw JSON string + */ +void entitlement_create::handle(discord_client* client, json &j, const std::string &raw) { + if (!client->creator->on_entitlement_create.empty()) { + dpp::entitlement ent; + ent.fill_from_json(&j); + + dpp::entitlement_create_t entitlement_event(client, raw); + entitlement_event.created = ent; + + client->creator->on_entitlement_create.call(entitlement_event); + } +} + +}; diff --git a/src/dpp/events/entitlement_delete.cpp b/src/dpp/events/entitlement_delete.cpp new file mode 100644 index 0000000000..d1c645faee --- /dev/null +++ b/src/dpp/events/entitlement_delete.cpp @@ -0,0 +1,47 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2021 Craig Edwards and D++ contributors + * (https://github.com/brainboxdotcc/DPP/graphs/contributors) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ************************************************************************************/ +#include +#include +#include + +namespace dpp::events { + +/** + * @brief Handle event + * + * @param client Websocket client (current shard) + * @param j JSON data for the event + * @param raw Raw JSON string + */ +void entitlement_delete::handle(discord_client* client, json &j, const std::string &raw) { + if (!client->creator->on_entitlement_delete.empty()) { + dpp::entitlement ent; + ent.fill_from_json(&j); + + dpp::entitlement_delete_t entitlement_event(client, raw); + entitlement_event.deleted = ent; + + client->creator->on_entitlement_delete.call(entitlement_event); + } +} + +}; diff --git a/src/dpp/events/entitlement_update.cpp b/src/dpp/events/entitlement_update.cpp new file mode 100644 index 0000000000..b4921eb4dc --- /dev/null +++ b/src/dpp/events/entitlement_update.cpp @@ -0,0 +1,47 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2021 Craig Edwards and D++ contributors + * (https://github.com/brainboxdotcc/DPP/graphs/contributors) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ************************************************************************************/ +#include +#include +#include + +namespace dpp::events { + +/** + * @brief Handle event + * + * @param client Websocket client (current shard) + * @param j JSON data for the event + * @param raw Raw JSON string + */ +void entitlement_update::handle(discord_client* client, json &j, const std::string &raw) { + if (!client->creator->on_entitlement_update.empty()) { + dpp::entitlement ent; + ent.fill_from_json(&j); + + dpp::entitlement_update_t entitlement_event(client, raw); + entitlement_event.updating_entitlement = ent; + + client->creator->on_entitlement_update.call(entitlement_event); + } +} + +}; diff --git a/src/dpp/sku.cpp b/src/dpp/sku.cpp new file mode 100644 index 0000000000..170a158159 --- /dev/null +++ b/src/dpp/sku.cpp @@ -0,0 +1,89 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2021 Craig Edwards and D++ contributors + * (https://github.com/brainboxdotcc/DPP/graphs/contributors) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ************************************************************************************/ +#include +#include +#include + +namespace dpp { + +using json = nlohmann::json; + +sku::sku(const snowflake _id, const sku_type _type, const snowflake _application_id, const std::string _name, const std::string _slug, const uint16_t _flags) + : managed(_id), type(_type), application_id(_application_id), name(_name), slug(_slug), flags(_flags) {} + +sku& sku::fill_from_json_impl(nlohmann::json* j) { + set_snowflake_not_null(j, "id", id); + + type = static_cast(int8_not_null(j, "type")); + + set_snowflake_not_null(j, "application_id", application_id); + + set_string_not_null(j, "name", name); + set_string_not_null(j, "slug", slug); + + uint8_t sku_flags = int8_not_null(j, "flags"); + if (sku_flags & (1 << 2)) { + flags |= sku_flags::sku_available; + } + if (sku_flags & (1 << 7)) { + flags |= sku_flags::sku_guild_subscription; + } + if (sku_flags & (1 << 8)) { + flags |= sku_flags::sku_user_subscription; + } + + /* + * TODO: Look at the SKU example on docs and see what we're missing, add it here after. Discord seems to be missing information in their structure as their example shows more data. + * yes this is copied from dpp/entitlement.cpp, Discord's fault for their docs being inconsistent. + */ + + return *this; +} + +json sku::to_json_impl(bool with_id) const { + json j; + if (with_id) { + j["id"] = id.str(); + } + + /* There's no reason to ever use this as you can only get SKUs, so no point putting any data here */ + + return j; +} + +sku_type sku::get_type() const { + return type; +} + +bool sku::is_available() const { + return flags & sku_flags::sku_available; +} + +bool sku::is_guild_subscription() const { + return flags & sku_flags::sku_guild_subscription; +} + +bool sku::is_user_subscription() const { + return flags & sku_flags::sku_user_subscription; +} + +} // namespace dpp diff --git a/src/dpp/slashcommand.cpp b/src/dpp/slashcommand.cpp index 77dde316fb..f3a8118145 100644 --- a/src/dpp/slashcommand.cpp +++ b/src/dpp/slashcommand.cpp @@ -593,12 +593,12 @@ void from_json(const nlohmann::json& j, interaction& i) { if (j.contains("channel") && !j.at("channel").is_null()) { const json& c = j["channel"]; - i.channel = channel().fill_from_json((json*)&c); + i.channel = channel().fill_from_json(const_cast(&c)); } if (j.contains("message") && !j.at("message").is_null()) { const json& m = j["message"]; - i.msg = message().fill_from_json((json*)&m, i.cache_policy); + i.msg = message().fill_from_json(const_cast(&m), i.cache_policy); set_snowflake_not_null(&m, "id", i.message_id); } @@ -719,6 +719,12 @@ void from_json(const nlohmann::json& j, interaction& i) { i.data = ai; } } + + if(j.contains("entitlements")) { + for (auto& entitle : j["entitlements"]) { + i.entitlements.emplace_back(entitlement().fill_from_json(const_cast(&entitle))); + } + } } interaction_response& interaction_response::add_autocomplete_choice(const command_option_choice& achoice) {