From 19a4aa95130ee0c7e6f105369bba64f4e74cf85b Mon Sep 17 00:00:00 2001 From: Phil B Date: Mon, 29 May 2023 17:27:44 +0200 Subject: [PATCH 1/6] fix: interaction_modal_response::fill_from_json --- include/dpp/appcommand.h | 2 +- src/dpp/message.cpp | 2 ++ src/dpp/slashcommand.cpp | 9 +++++---- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/include/dpp/appcommand.h b/include/dpp/appcommand.h index 22ecf2d257..1177edf1be 100644 --- a/include/dpp/appcommand.h +++ b/include/dpp/appcommand.h @@ -395,7 +395,7 @@ struct DPP_EXPORT interaction_modal_response : public interaction_response, publ std::string custom_id; /** - * @brief Title of the modal form box + * @brief Title of the modal form box (max 25 characters) */ std::string title; diff --git a/src/dpp/message.cpp b/src/dpp/message.cpp index d328256116..51efabf33c 100644 --- a/src/dpp/message.cpp +++ b/src/dpp/message.cpp @@ -74,6 +74,8 @@ component& component::fill_from_json(nlohmann::json* j) { } } } else if (type == cot_text) { + label = string_not_null(j, "label"); + text_style = static_cast(int8_not_null(j, "style")); custom_id = string_not_null(j, "custom_id"); type = (component_type)int8_not_null(j, "type"); required = bool_not_null(j, "required"); diff --git a/src/dpp/slashcommand.cpp b/src/dpp/slashcommand.cpp index 79b9c7b7d4..1d7c14e005 100644 --- a/src/dpp/slashcommand.cpp +++ b/src/dpp/slashcommand.cpp @@ -764,13 +764,14 @@ interaction_response& interaction_response::fill_from_json(nlohmann::json* j) { } interaction_modal_response& interaction_modal_response::fill_from_json(nlohmann::json* j) { - json& d = (*j)["data"]; type = (interaction_response_type)int8_not_null(j, "type"); + json& d = (*j)["data"]; custom_id = string_not_null(&d, "custom_id"); - title = string_not_null(&d, "custom_id"); - if (d.find("components") != d.end()) { + title = string_not_null(&d, "title"); + if (d.contains("components")) { + components.clear(); for (auto& c : d["components"]) { - components[current_row].push_back(dpp::component().fill_from_json(&c)); + components.push_back(dpp::component().fill_from_json(&c).components); } } return *this; From 5f76c8019cc5464d8f5d0c50b9524cfaa465114f Mon Sep 17 00:00:00 2001 From: Phil B Date: Mon, 29 May 2023 21:37:46 +0200 Subject: [PATCH 2/6] fixed component::fill_from_json which didn't parse some fields --- include/dpp/message.h | 26 ++++++++-------- src/dpp/message.cpp | 64 +++++++++++++++++++++------------------- src/dpp/slashcommand.cpp | 5 +++- 3 files changed, 51 insertions(+), 44 deletions(-) diff --git a/include/dpp/message.h b/include/dpp/message.h index e95f96c30a..a12cb52a27 100644 --- a/include/dpp/message.h +++ b/include/dpp/message.h @@ -85,15 +85,15 @@ enum component_style : uint8_t { */ struct DPP_EXPORT select_option : public json_interface { /** - * @brief Label for option + * @brief User-facing name of the option */ std::string label; /** - * @brief Value for option + * @brief Dev-defined value of the option */ std::string value; /** - * @brief Description of option + * @brief Additional description of the option */ std::string description; /** @@ -256,12 +256,12 @@ class DPP_EXPORT component : public json_interface { */ std::string placeholder; - /** Minimum number of items that must be chosen for a select menu. + /** Minimum number of items that must be chosen for a select menu (0-25). * Default is -1 to not set this */ int32_t min_values; - /** Maximum number of items that can be chosen for a select menu. + /** Maximum number of items that can be chosen for a select menu (0-25). * Default is -1 to not set this */ int32_t max_values; @@ -441,33 +441,33 @@ class DPP_EXPORT component : public json_interface { component& set_placeholder(const std::string &placeholder); /** - * @brief Set the min value + * @brief Set the minimum number of items that must be chosen for a select menu * - * @param min_values min value to set + * @param min_values min value to set (0-25) * @return component& Reference to self */ component& set_min_values(uint32_t min_values); /** - * @brief Set the max value + * @brief Set the maximum number of items that can be chosen for a select menu * - * @param max_values max value to set (0 - 25) + * @param max_values max value to set (0-25) * @return component& Reference to self */ component& set_max_values(uint32_t max_values); /** - * @brief Set the min length of text input + * @brief Set the minimum input length for a text input * - * @param min_l min value to set (0 - 25) + * @param min_l min length to set (0-4000) * @return component& Reference to self */ component& set_min_length(uint32_t min_l); /** - * @brief Set the max length of text input + * @brief Set the maximum input length for a text input * - * @param max_l max value to set + * @param max_l max length to set (1-4000) * @return component& Reference to self */ component& set_max_length(uint32_t max_l); diff --git a/src/dpp/message.cpp b/src/dpp/message.cpp index 51efabf33c..eb776c8a65 100644 --- a/src/dpp/message.cpp +++ b/src/dpp/message.cpp @@ -47,37 +47,53 @@ component::component() : component& component::fill_from_json(nlohmann::json* j) { type = static_cast(int8_not_null(j, "type")); + label = string_not_null(j, "label"); + custom_id = string_not_null(j, "custom_id"); + disabled = bool_not_null(j, "disabled"); + placeholder = string_not_null(j, "placeholder"); + if (j->contains("min_values") && j->at("min_values").is_number_integer()) { + min_values = j->at("min_values").get(); + } + if (j->contains("max_values") && j->at("max_values").is_number_integer()) { + max_values = j->at("max_values").get(); + } if (type == cot_action_row) { for (json sub_component : (*j)["components"]) { dpp::component new_component; new_component.fill_from_json(&sub_component); components.emplace_back(new_component); } - } else if (type == cot_button) { - label = string_not_null(j, "label"); + } else if (type == cot_button) { // button specific fields style = static_cast(int8_not_null(j, "style")); - custom_id = string_not_null(j, "custom_id"); - disabled = bool_not_null(j, "disabled"); + url = string_not_null(j, "url"); if (j->contains("emoji")) { json emo = (*j)["emoji"]; emoji.id = snowflake_not_null(&emo, "id"); emoji.name = string_not_null(&emo, "name"); emoji.animated = bool_not_null(&emo, "animated"); } - } else if (type == cot_selectmenu) { - label = ""; - custom_id = string_not_null(j, "custom_id"); - disabled = bool_not_null(j, "disabled"); + } else if (type == cot_selectmenu) { // string select menu specific fields if (j->contains("options")) { - for(json opt : (*j)["options"]) { + for (json opt : (*j)["options"]) { options.push_back(dpp::select_option().fill_from_json(&opt)); } } - } else if (type == cot_text) { - label = string_not_null(j, "label"); + } else if (type == cot_channel_selectmenu) { // channel select menu specific fields + if (j->contains("channel_types")) { + for (json &ct : (*j)["channel_types"]) { + if (ct.is_number_integer()) { + channel_types.push_back(ct.get()); + } + } + } + } else if (type == cot_text) { // text inputs (modal) specific fields text_style = static_cast(int8_not_null(j, "style")); - custom_id = string_not_null(j, "custom_id"); - type = (component_type)int8_not_null(j, "type"); + if (j->contains("min_length") && j->at("min_length").is_number_integer()) { + min_length = j->at("min_length").get(); + } + if (j->contains("max_length") && j->at("max_length").is_number_integer()) { + max_length = j->at("max_length").get(); + } required = bool_not_null(j, "required"); json v = (*j)["value"]; if (!v.is_null() && v.is_number_integer()) { @@ -87,19 +103,6 @@ component& component::fill_from_json(nlohmann::json* j) { } else if (!v.is_null() && v.is_string()) { value = v.get(); } - } else if (type == cot_user_selectmenu || type == cot_role_selectmenu || type == cot_mentionable_selectmenu) { - custom_id = string_not_null(j, "custom_id"); - disabled = bool_not_null(j, "disabled"); - } else if (type == cot_channel_selectmenu) { - custom_id = string_not_null(j, "custom_id"); - disabled = bool_not_null(j, "disabled"); - if (j->contains("channel_types")) { - for (json &ct : (*j)["channel_types"]) { - if (ct.is_number_integer()) { - channel_types.push_back(ct.get()); - } - } - } } return *this; } @@ -221,13 +224,13 @@ component& component::set_emoji(const std::string& name, dpp::snowflake id, bool component& component::set_min_length(uint32_t min_l) { - min_length = min_l; + min_length = static_cast(min_l); return *this; } component& component::set_max_length(uint32_t max_l) { - max_length = max_l; + max_length = static_cast(max_l); return *this; } @@ -411,6 +414,7 @@ select_option& select_option::fill_from_json(nlohmann::json* j) { emoji.name = string_not_null(&emoj, "name"); emoji.id = snowflake_not_null(&emoj, "id"); } + is_default = bool_not_null(j, "default"); return *this; } @@ -426,12 +430,12 @@ component& component::set_placeholder(const std::string &_placeholder) { } component& component::set_min_values(uint32_t _min_values) { - min_values = _min_values; + min_values = static_cast(_min_values); return *this; } component& component::set_max_values(uint32_t _max_values) { - max_values = _max_values; + max_values = static_cast(_max_values); return *this; } diff --git a/src/dpp/slashcommand.cpp b/src/dpp/slashcommand.cpp index 1d7c14e005..27a97904fd 100644 --- a/src/dpp/slashcommand.cpp +++ b/src/dpp/slashcommand.cpp @@ -771,7 +771,10 @@ interaction_modal_response& interaction_modal_response::fill_from_json(nlohmann: if (d.contains("components")) { components.clear(); for (auto& c : d["components"]) { - components.push_back(dpp::component().fill_from_json(&c).components); + auto row = dpp::component().fill_from_json(&c); + if (!row.components.empty()) { + components.push_back(row.components); + } } } return *this; From 613922955a6b654396ad02347549ac4e86f3e5b3 Mon Sep 17 00:00:00 2001 From: Phil B Date: Fri, 2 Jun 2023 12:56:52 +0200 Subject: [PATCH 3/6] feat: added setter methods to dpp::invite class --- include/dpp/invite.h | 48 ++++++++++++++++++++++++++++++++++++++++++++ src/dpp/invite.cpp | 30 +++++++++++++++++++++++++++ 2 files changed, 78 insertions(+) diff --git a/include/dpp/invite.h b/include/dpp/invite.h index b04dd7b733..fea11a7eab 100644 --- a/include/dpp/invite.h +++ b/include/dpp/invite.h @@ -116,6 +116,54 @@ class DPP_EXPORT invite : public json_interface { */ virtual ~invite() = default; + /** + * @brief Set the max age after which the invite expires + * + * @param max_age_ The duration in seconds, or 0 for no expiration. Must be between 0 and 604800 (7 days) + * @return invite& reference to self for chaining of calls + */ + invite& set_max_age(const uint32_t max_age_); + + /** + * @brief Set the maximum number of uses for this invite + * + * @param max_uses_ Maximum number of uses, or 0 for unlimited. Must be between 0 and 100 + * @return invite& reference to self for chaining of calls + */ + invite& set_max_uses(const uint8_t max_uses_); + + /** + * @brief Set the target user id + * + * @param user_id The user ID whose stream to display for this voice channel stream invite + * @return invite& reference to self for chaining of calls + */ + invite& set_target_user_id(const snowflake user_id); + + /** + * @brief Set the target type for this voice channel invite + * + * @param type invite_target_t Target type + * @return invite& reference to self for chaining of calls + */ + invite& set_target_type(const invite_target_t type); + + /** + * @brief Set temporary property of this invite object + * + * @param is_temporary Whether this invite only grants temporary membership + * @return invite& reference to self for chaining of calls + */ + invite& set_temporary(const bool is_temporary); + + /** + * @brief Set unique property of this invite object + * + * @param is_unique True if this invite should not replace or "attach to" similar invites + * @return invite& reference to self for chaining of calls + */ + invite& set_unique(const bool is_unique); + /** Read class values from json object * @param j A json object to read from * @return A reference to self diff --git a/src/dpp/invite.cpp b/src/dpp/invite.cpp index c3d26ba4f1..bfd23bb04f 100644 --- a/src/dpp/invite.cpp +++ b/src/dpp/invite.cpp @@ -82,4 +82,34 @@ std::string invite::build_json(bool with_id) const { return j.dump(); } +invite &invite::set_max_age(const uint32_t max_age_) { + this->max_age = max_age_; + return *this; +} + +invite &invite::set_max_uses(const uint8_t max_uses_) { + this->max_uses = max_uses_; + return *this; +} + +invite &invite::set_target_user_id(const snowflake user_id) { + this->target_user_id = user_id; + return *this; +} + +invite &invite::set_target_type(const invite_target_t type) { + this->target_type = type; + return *this; +} + +invite &invite::set_temporary(const bool is_temporary) { + this->temporary = is_temporary; + return *this; +} + +invite &invite::set_unique(const bool is_unique) { + this->unique = is_unique; + return *this; +} + }; From 4815cc2c742d90db99e9d3056acc795484d10ab0 Mon Sep 17 00:00:00 2001 From: Phil B Date: Sat, 15 Jul 2023 13:27:35 +0200 Subject: [PATCH 4/6] feat: added role flag `IN_PROMPT` --- include/dpp/cluster.h | 4 ++-- include/dpp/role.h | 6 ++++++ src/dpp/role.cpp | 17 +++++++++++++++++ 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/include/dpp/cluster.h b/include/dpp/cluster.h index 1e74b500c0..3748f51963 100644 --- a/include/dpp/cluster.h +++ b/include/dpp/cluster.h @@ -3122,7 +3122,7 @@ class DPP_EXPORT cluster { * @brief Get public archived threads in a channel (Sorted by archive_timestamp in descending order) * @see https://discord.com/developers/docs/resources/channel#list-public-archived-threads * @param channel_id Channel to get public archived threads for - * @param before_timestamp Get threads before this timestamp + * @param before_timestamp Get threads archived before this timestamp * @param limit Number of threads to get * @param callback Function to call when the API call completes * On success the callback will contain a dpp::thread_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(). @@ -3133,7 +3133,7 @@ class DPP_EXPORT cluster { * @brief Get private archived threads in a channel (Sorted by archive_timestamp in descending order) * @see https://discord.com/developers/docs/resources/channel#list-private-archived-threads * @param channel_id Channel to get public archived threads for - * @param before_timestamp Get threads before this timestamp + * @param before_timestamp Get threads archived before this timestamp * @param limit Number of threads to get * @param callback Function to call when the API call completes * On success the callback will contain a dpp::thread_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(). diff --git a/include/dpp/role.h b/include/dpp/role.h index fd16b39122..3a313ccc62 100644 --- a/include/dpp/role.h +++ b/include/dpp/role.h @@ -37,6 +37,7 @@ enum role_flags : uint8_t { r_premium_subscriber = 0b00001000, //!< Whether this is the guild's booster role r_available_for_purchase = 0b00010000, //!< Whether the role is available for purchase r_guild_connections = 0b00100000, //!< Whether the role is a guild's linked role + r_in_prompt = 0b01000000, //!< Whether the role can be selected by members in an onboarding prompt }; /** @@ -287,6 +288,11 @@ class DPP_EXPORT role : public managed, public json_interface { * @return bool True if the role is a linked role */ bool is_linked() const; + /** + * @brief True if the role can be selected by members in an onboarding prompt + * @return bool True if the role can be selected by members in an onboarding prompt + */ + bool is_selectable_in_prompt() const; /** * @brief True if has create instant invite permission * @note Having the administrator permission causes this method to always return true diff --git a/src/dpp/role.cpp b/src/dpp/role.cpp index dc11b7772c..7739a3ef19 100644 --- a/src/dpp/role.cpp +++ b/src/dpp/role.cpp @@ -32,6 +32,11 @@ namespace dpp { using json = nlohmann::json; +/* A mapping of discord's flag values to our bitmap (they're different bit positions to fit other stuff in) */ +std::map rolemap = { + { 1 << 0, dpp::r_in_prompt }, +}; + role::role() : managed(), guild_id(0), @@ -70,6 +75,14 @@ role& role::fill_from_json(snowflake _guild_id, nlohmann::json* j) this->colour = int32_not_null(j, "color"); this->position = int8_not_null(j, "position"); this->permissions = snowflake_not_null(j, "permissions"); + + uint8_t f = int8_not_null(j, "flags"); + for (auto & flag : rolemap) { + if (f & flag.first) { + this->flags |= flag.second; + } + } + this->flags |= bool_not_null(j, "hoist") ? dpp::r_hoist : 0; this->flags |= bool_not_null(j, "managed") ? dpp::r_managed : 0; this->flags |= bool_not_null(j, "mentionable") ? dpp::r_mentionable : 0; @@ -167,6 +180,10 @@ bool role::is_linked() const { return this->flags & dpp::r_guild_connections; } +bool role::is_selectable_in_prompt() const { + return this->flags & dpp::r_in_prompt; +} + bool role::has_create_instant_invite() const { return has_administrator() || permissions.has(p_create_instant_invite); } From 16b4c208a190eeb3d5178f0c5698d3b6d6134cca Mon Sep 17 00:00:00 2001 From: Phil B Date: Wed, 16 Aug 2023 22:52:41 +0200 Subject: [PATCH 5/6] feat: added can and can_any methods to dpp::permission class --- include/dpp/permissions.h | 44 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/include/dpp/permissions.h b/include/dpp/permissions.h index b57af3d9f3..fccb333712 100644 --- a/include/dpp/permissions.h +++ b/include/dpp/permissions.h @@ -127,6 +127,44 @@ class DPP_EXPORT permission { */ operator nlohmann::json() const; + /** + * @brief Check for certain permissions, taking into account administrator privileges. It uses the Bitwise AND operator + * @tparam T one or more uint64_t permission bits + * @param values The permissions (from dpp::permissions) to check for + * + * **Example:** + * + * ```cpp + * bool is_mod = permission.can(dpp::p_kick_members, dpp::p_ban_members); + * // Returns true if it has permission to p_kick_members and p_ban_members + * ``` + * + * @return bool True if it has **all** the given permissions or dpp::p_administrator + */ + template + constexpr bool can(T... values) const noexcept { + return has(values...) || (value & p_administrator); + } + + /** + * @brief Check for certain permissions, taking into account administrator privileges. It uses the Bitwise AND operator + * @tparam T one or more uint64_t permission bits + * @param values The permissions (from dpp::permissions) to check for + * + * **Example:** + * + * ```cpp + * bool is_mod = permission.can_any(dpp::p_kick_members, dpp::p_ban_members); + * // Returns true if it has permission to p_kick_members or p_ban_members + * ``` + * + * @return bool True if it has **any** of the given permissions or dpp::p_administrator + */ + template + constexpr bool can_any(T... values) const noexcept { + return has_any(values...) || (value & p_administrator); + } + /** * @brief Check for permission flags set. It uses the Bitwise AND operator * @tparam T one or more uint64_t permission bits @@ -139,7 +177,7 @@ class DPP_EXPORT permission { * // Returns true if the permission bitmask contains p_kick_members and p_ban_members * ``` * - * @return bool True if it has all the given permissions + * @return bool True if it has **all** the given permissions */ template constexpr bool has(T... values) const noexcept { @@ -158,7 +196,7 @@ class DPP_EXPORT permission { * // Returns true if the permission bitmask contains p_administrator or p_ban_members * ``` * - * @return bool True if it has any the given permissions + * @return bool True if it has **any** of the given permissions */ template constexpr bool has_any(T... values) const noexcept { @@ -187,7 +225,7 @@ class DPP_EXPORT permission { } /** - * @brief Assign a permission. This will reset the bitmask to the new value. + * @brief Assign permissions. This will reset the bitmask to the new value. * @tparam T one or more uint64_t permission bits * @param values The permissions (from dpp::permissions) to set * From e9af273021c20cec8b6b0e32411e777ea9a1adf1 Mon Sep 17 00:00:00 2001 From: Phil B Date: Wed, 16 Aug 2023 22:53:25 +0200 Subject: [PATCH 6/6] test: added unit tests for the new can and can_any method on the dpp::permission class --- src/unittest/test.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/unittest/test.cpp b/src/unittest/test.cpp index 3ad4b56288..844ff1d3c0 100644 --- a/src/unittest/test.cpp +++ b/src/unittest/test.cpp @@ -305,6 +305,8 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b p.set(0).add(~uint64_t{0}).remove(dpp::p_speak).set(dpp::p_administrator); success = !p.has(dpp::p_administrator, dpp::p_ban_members) && success; // must return false because they're not both set success = !p.has(dpp::p_administrator | dpp::p_ban_members) && success; + success = p.can(dpp::p_ban_members) && success; + success = p.can(dpp::p_speak) && success; constexpr auto permission_test = [](dpp::permission p) constexpr noexcept { bool success{true}; @@ -317,6 +319,10 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b success = p.has(dpp::p_administrator | dpp::p_ban_members) && success; success = p.add(dpp::p_speak).has(dpp::p_administrator, dpp::p_speak) && success; success = !p.remove(dpp::p_speak).has(dpp::p_administrator, dpp::p_speak) && success; + p.remove(dpp::p_administrator); + success = p.can(dpp::p_ban_members) && success; + success = !p.can(dpp::p_speak, dpp::p_ban_members) && success; + success = p.can_any(dpp::p_speak, dpp::p_ban_members) && success; return success; }; constexpr auto constexpr_success = permission_test({~uint64_t{0}}); // test in constant evaluated