diff --git a/include/dpp/cluster.h b/include/dpp/cluster.h index d86e12443f..f025a67d1d 100644 --- a/include/dpp/cluster.h +++ b/include/dpp/cluster.h @@ -2506,7 +2506,7 @@ class DPP_EXPORT cluster { /** * @brief Edit guild widget * - * Requires the `MANAGE_GUILD` permission. + * Requires the `MANAGE_GUILD` permission. * * @param guild_id Guild ID to edit widget for * @param gw New guild widget information diff --git a/include/dpp/presence.h b/include/dpp/presence.h index d49b3ea7ce..441f15863d 100644 --- a/include/dpp/presence.h +++ b/include/dpp/presence.h @@ -21,6 +21,7 @@ #pragma once #include #include +#include #include #include @@ -114,17 +115,101 @@ enum activity_type : uint8_t { */ enum activity_flags { /// In an instance - af_instance = 0b00000001, + af_instance = 0b000000001, /// Joining - af_join = 0b00000010, + af_join = 0b000000010, /// Spectating - af_spectate = 0b00000100, + af_spectate = 0b000000100, /// Sending join request - af_join_request = 0b00001000, + af_join_request = 0b000001000, /// Synchronising - af_sync = 0b00010000, + af_sync = 0b000010000, /// Playing - af_play = 0b00100000 + af_play = 0b000100000, + /// Party privacy friends + af_party_privacy_friends = 0b001000000, + /// Party privacy voice channel + af_party_privacy_voice_channel = 0b010000000, + /// Embedded + af_embedded = 0b100000000 +}; + +/** + * @brief An activity button is a custom button shown in the rich presence. Can be to join a game or whatever + */ +struct DPP_EXPORT activity_button { +public: + /** The text shown on the button (1-32 characters) + */ + std::string label; + /** The url opened when clicking the button (1-512 characters). It's may be empty + * + * @note Bots cannot access the activity button URLs. + */ + std::string url; + + /** Constructor */ + activity_button() = default; +}; + +/** + * @brief An activity asset are the images and the hover text displayed in the rich presence + */ +struct DPP_EXPORT activity_assets { +public: + /** The large asset image which usually contain snowflake ID or prefixed image ID + */ + std::string large_image; + /** Text displayed when hovering over the large image of the activity + */ + std::string large_text; + /** The small asset image which usually contain snowflake ID or prefixed image ID + */ + std::string small_image; + /** Text displayed when hovering over the small image of the activity + */ + std::string small_text; + + /** Constructor */ + activity_assets() = default; +}; + +/** + * @brief Secrets for Rich Presence joining and spectating + */ +struct DPP_EXPORT activity_secrets { +public: + /** The secret for joining a party + */ + std::string join; + /** The secret for spectating a game + */ + std::string spectate; + /** The secret for a specific instanced match + */ + std::string match; + + /** Constructor */ + activity_secrets() = default; +}; + +/** + * @brief Information for the current party of the player + */ +struct DPP_EXPORT activity_party { +public: + /** The ID of the party + */ + snowflake id; + /** The party's current size. Used to show the party's current size + */ + int32_t current_size; + /** The party's maximum size. Used to show the party's maximum size + */ + int32_t maximum_size; + + /** Constructor */ + activity_party(); }; /** @@ -136,15 +221,33 @@ class DPP_EXPORT activity { * e.g. "Fortnite" */ std::string name; - /** State of activity. + /** State of activity or the custom user status. * e.g. "Waiting in lobby" */ std::string state; + /** What the player is currently doing + */ + std::string details; + /** Images for the presence and their hover texts + */ + activity_assets assets; /** URL. * Only applicable for certain sites such a YouTube * Alias: details */ std::string url; + /** The custom buttons shown in the Rich Presence (max 2) + */ + std::vector buttons; + /** The emoji used for the custom status + */ + dpp::emoji emoji; + /** Information of the current party if there is one + */ + activity_party party; + /** Secrets for rich presence joining and spectating + */ + activity_secrets secrets; /** Activity type */ activity_type type; @@ -160,11 +263,30 @@ class DPP_EXPORT activity { /** Creating application (e.g. a linked account on the user's client) */ snowflake application_id; - /** Flags bitmask from activity_flags + /** Flags bitmask from dpp::activity_flags */ uint8_t flags; + /** Whether or not the activity is an instanced game session + */ + bool is_instance; + + /** + * @brief Get the assets large image url if they have one, otherwise returns an empty string. In case of prefixed image IDs (mp:{image_id}) it returns an empty string. + * + * @param size The size of the image in pixels. It can be any power of two between 16 and 4096. if not specified, the default sized image is returned. + * @return image url or empty string + */ + std::string get_large_asset_url(uint16_t size = 0) const; + + /** + * @brief Get the assets small image url if they have one, otherwise returns an empty string. In case of prefixed image IDs (mp:{image_id}) it returns an empty string. + * + * @param size The size of the image in pixels. It can be any power of two between 16 and 4096. if not specified, the default sized image is returned. + * @return image url or empty string + */ + std::string get_small_asset_url(uint16_t size = 0) const; - activity() = default; + activity(); /** * @brief Construct a new activity @@ -188,7 +310,7 @@ class DPP_EXPORT presence { /** Guild ID. Apparently, Discord supports this internally but the client doesnt... */ snowflake guild_id; - /** Flags bitmask containing presence_flags */ + /** Flags bitmask containing dpp::presence_flags */ uint8_t flags; /** List of activities */ diff --git a/src/dpp/presence.cpp b/src/dpp/presence.cpp index 877defba2c..68b24e01c9 100644 --- a/src/dpp/presence.cpp +++ b/src/dpp/presence.cpp @@ -20,15 +20,56 @@ ************************************************************************************/ #include #include +#include +#include #include +#include using json = nlohmann::json; namespace dpp { +std::string activity::get_large_asset_url(uint16_t size) const { + // https://discord.com/developers/docs/topics/gateway#activity-object-activity-asset-image + if (!this->assets.large_image.empty() && this->application_id && + this->assets.large_image.find(':') == std::string::npos) { // make sure it's not a prefixed proxy image + return fmt::format("{}/app-assets/{}/{}.png{}", + utility::cdn_host, + this->application_id, + this->assets.large_image, + utility::avatar_size(size) + ); + } else { + return std::string(); + } +} + +std::string activity::get_small_asset_url(uint16_t size) const { + // https://discord.com/developers/docs/topics/gateway#activity-object-activity-asset-image + if (!this->assets.small_image.empty() && this->application_id && + this->assets.small_image.find(':') == std::string::npos) { // make sure it's not a prefixed proxy image + return fmt::format("{}/app-assets/{}/{}.png{}", + utility::cdn_host, + this->application_id, + this->assets.small_image, + utility::avatar_size(size) + ); + } else { + return std::string(); + } +} + +activity_party::activity_party() : id(0), current_size(0), maximum_size(0) +{ +} + activity::activity(const activity_type typ, const std::string& nam, const std::string& stat, const std::string& url_) : - name(nam), state(stat), url(url_), type(typ) -{ + name(nam), state(stat), url(url_), type(typ), created_at(0), start(0), end(0), application_id(0), flags(0), is_instance(false) +{ +} + +activity::activity(): created_at(0), start(0), end(0), application_id(0), flags(0), is_instance(false) +{ } presence::presence() : user_id(0), guild_id(0), flags(0) @@ -135,18 +176,57 @@ presence& presence::fill_from_json(nlohmann::json* j) { for (auto & act : (*j)["activities"]) { activity a; a.name = string_not_null(&act, "name"); - a.state = string_not_null(&act, "state"); // if user - if (a.state.empty()) a.state = string_not_null(&act, "details"); // if activity from bot, maybe? + a.details = string_not_null(&act, "details"); + if (act.find("assets") != act.end()) { + a.assets.large_image = string_not_null(&act["assets"], "large_image"); + a.assets.large_text = string_not_null(&act["assets"], "large_text"); + a.assets.small_image = string_not_null(&act["assets"], "small_image"); + a.assets.small_text = string_not_null(&act["assets"], "small_text"); + } + a.state = string_not_null(&act, "state"); a.type = (activity_type)int8_not_null(&act, "type"); a.url = string_not_null(&act, "url"); + if (act.find("buttons") != act.end()) { + for (auto &b : act["buttons"]) { + activity_button btn; + if (b.is_string()) { // its may be just a string (label) because normal bots cannot access the button URLs + btn.label = b; + } else { + btn.label = string_not_null(&b, "label"); + btn.url = string_not_null(&b, "url"); + } + a.buttons.push_back(btn); + } + } + if (act.find("emoji") != act.end()) { + a.emoji.name = string_not_null(&act["emoji"], "name"); + a.emoji.id = snowflake_not_null(&act["emoji"], "id"); + if (bool_not_null(&act["emoji"], "animated")) + a.emoji.flags |= e_animated; + } + if (act.find("party") != act.end()) { + a.party.id = snowflake_not_null(&act["party"], "id"); + if (act["party"].find("size") != act["party"].end()) { // "size" is an array of two integers + try { + a.party.current_size = act["party"]["size"][0].get(); + a.party.maximum_size = act["party"]["size"][1].get(); + } catch (std::exception &exception) {} + } + } + if (act.find("secrets") != act.end()) { + a.secrets.join = string_not_null(&act["secret"], "join"); + a.secrets.spectate = string_not_null(&act["secret"], "spectate"); + a.secrets.match = string_not_null(&act["secret"], "match"); + } a.created_at = int64_not_null(&act, "created_at"); if (act.find("timestamps") != act.end()) { - a.start = int64_not_null(&(act["timestamps"]), "start"); - a.end = int64_not_null(&(act["timestamps"]), "end"); + a.start = int64_not_null(&act["timestamps"], "start"); + a.end = int64_not_null(&act["timestamps"], "end"); } a.application_id = snowflake_not_null(&act, "application_id"); a.flags = int8_not_null(&act, "flags"); - + a.is_instance = bool_not_null(&act, "instance"); + activities.push_back(a); } }