diff --git a/doxygen-awesome-css b/doxygen-awesome-css index 245c7c94c2..1141ff5fb6 160000 --- a/doxygen-awesome-css +++ b/doxygen-awesome-css @@ -1 +1 @@ -Subproject commit 245c7c94c20eac22730ef89035967f78b77bf405 +Subproject commit 1141ff5fb6888f8536088cfe6dc5a7834094bad7 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/include/dpp/channel.h b/include/dpp/channel.h index d39a0e0998..5a28fcc906 100644 --- a/include/dpp/channel.h +++ b/include/dpp/channel.h @@ -504,16 +504,39 @@ class DPP_EXPORT channel : public managed, public json_interface { channel& set_rate_limit_per_user(const uint16_t rate_limit_per_user); /** - * @brief Add a permission_overwrite to this channel object - * - * @param id ID of the role or the member you want to add overwrite for + * @brief Add permission overwrites for a user or role. + * If the channel already has permission overwrites for the passed target, the existing ones will be adjusted by the passed permissions + * + * @param target ID of the role or the member you want to adjust overwrites for * @param type type of overwrite - * @param allowed_permissions bitmask of allowed permissions (refer to enum dpp::permissions) for this user/role in this channel - * @param denied_permissions bitmask of denied permissions (refer to enum dpp::permissions) for this user/role in this channel + * @param allowed_permissions bitmask of dpp::permissions you want to allow for this user/role in this channel. Note: You can use the dpp::permission class + * @param denied_permissions bitmask of dpp::permissions you want to deny for this user/role in this channel. Note: You can use the dpp::permission class * - * @return Reference to self, so these method calls may be chained + * @return Reference to self, so these method calls may be chained + */ + channel& add_permission_overwrite(const snowflake target, const overwrite_type type, const uint64_t allowed_permissions, const uint64_t denied_permissions); + /** + * @brief Set permission overwrites for a user or role on this channel object. Old permission overwrites for the target will be overwritten + * + * @param target ID of the role or the member you want to set overwrites for + * @param type type of overwrite + * @param allowed_permissions bitmask of allowed dpp::permissions for this user/role in this channel. Note: You can use the dpp::permission class + * @param denied_permissions bitmask of denied dpp::permissions for this user/role in this channel. Note: You can use the dpp::permission class + * + * @return Reference to self, so these method calls may be chained + * + * @note If both `allowed_permissions` and `denied_permissions` parameters are 0, the permission overwrite for the target will be removed + */ + channel& set_permission_overwrite(const snowflake target, const overwrite_type type, const uint64_t allowed_permissions, const uint64_t denied_permissions); + /** + * @brief Remove channel specific permission overwrites of a user or role + * + * @param target ID of the role or the member you want to remove permission overwrites of + * @param type type of overwrite + * + * @return Reference to self, so these method calls may be chained */ - channel& add_permission_overwrite(const snowflake id, const overwrite_type type, const uint64_t allowed_permissions, const uint64_t denied_permissions); + channel& remove_permission_overwrite(const snowflake target, const overwrite_type type); /** * @brief Get the channel type diff --git a/include/dpp/invite.h b/include/dpp/invite.h index e23592fae0..b04dd7b733 100644 --- a/include/dpp/invite.h +++ b/include/dpp/invite.h @@ -25,9 +25,21 @@ #include #include #include +#include +#include +#include namespace dpp { +/** + * @brief Invite target types for dpp::invite + */ +enum invite_target_t : uint8_t { + itt_none = 0, //!< Undefined invite target type + itt_stream = 1, //!< Stream target type + itt_embedded_application = 2, //!< Embedded Application target type +}; + /** * @brief Represents an invite to a discord guild or channel */ @@ -43,18 +55,28 @@ class DPP_EXPORT invite : public json_interface { /** Guild ID this invite is for */ snowflake guild_id; + /** The partial guild this invite is for. Only filled in retrieved invites + */ + guild destination_guild; /** Channel ID this invite is for */ snowflake channel_id; + /** The partial channel this invite is for. Only filled in retrieved invites + */ + channel destination_channel; /** User ID who created this invite + * @deprecated Use the `inviter` field instead */ snowflake inviter_id; + /** User who created this invite + */ + user inviter; /** The user ID whose stream to display for this voice channel stream invite */ snowflake target_user_id; - /** Target type + /** Target type for this voice channel invite */ - uint8_t target_type; + invite_target_t target_type; /** Approximate number of online users * @note Only returned from cluster::invite_get */ @@ -68,7 +90,7 @@ class DPP_EXPORT invite : public json_interface { uint32_t max_age; /** Maximum number of uses, or 0 for unlimited. Must be between 0 and 100. Defaults to 0 */ - uint32_t max_uses; + uint8_t max_uses; /** Whether this invite only grants temporary membership */ bool temporary; 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/channel.cpp b/src/dpp/channel.cpp index 3017e713e2..e22db2d7e5 100644 --- a/src/dpp/channel.cpp +++ b/src/dpp/channel.cpp @@ -219,12 +219,42 @@ channel& channel::set_user_limit(const uint8_t user_limit) { return *this; } -channel& channel::add_permission_overwrite(const snowflake id, const overwrite_type type, const uint64_t allowed_permissions, const uint64_t denied_permissions) { - permission_overwrite po {id, allowed_permissions, denied_permissions, type}; +channel& channel::add_permission_overwrite(const snowflake target, const overwrite_type type, const uint64_t allowed_permissions, const uint64_t denied_permissions) { + for (auto &o : this->permission_overwrites) { + if (o.id == target && o.type == type) { + o.allow.remove(denied_permissions); + o.allow.add(allowed_permissions); + o.deny.remove(allowed_permissions); + o.deny.add(denied_permissions); + return *this; + } + } + permission_overwrite po {target, allowed_permissions, denied_permissions, type}; this->permission_overwrites.push_back(po); return *this; } +channel& channel::set_permission_overwrite(const snowflake target, const overwrite_type type, const uint64_t allowed_permissions, const uint64_t denied_permissions) { + this->remove_permission_overwrite(target, type); + if (allowed_permissions != 0 || denied_permissions != 0) { + permission_overwrite po{target, allowed_permissions, denied_permissions, type}; + this->permission_overwrites.push_back(po); + } + return *this; +} + +channel& channel::remove_permission_overwrite(const dpp::snowflake target, const dpp::overwrite_type type) { + auto it = this->permission_overwrites.begin(); + while (it != this->permission_overwrites.end()) { + if (it->id == target && it->type == type) { + it = this->permission_overwrites.erase(it); + } else { + it++; + } + } + return *this; +} + bool channel::is_nsfw() const { return flags & dpp::c_nsfw; } diff --git a/src/dpp/invite.cpp b/src/dpp/invite.cpp index 640bf42b8f..c3d26ba4f1 100644 --- a/src/dpp/invite.cpp +++ b/src/dpp/invite.cpp @@ -28,7 +28,7 @@ namespace dpp { using json = nlohmann::json; -invite::invite() : expires_at(0), guild_id(0), channel_id(0), inviter_id(0), target_user_id(0), target_type(0), approximate_presence_count(0), approximate_member_count(0), max_age(86400), max_uses(0), temporary(false), unique(false), uses(0), created_at(0) +invite::invite() : expires_at(0), guild_id(0), channel_id(0), inviter_id(0), target_user_id(0), target_type(itt_none), approximate_presence_count(0), approximate_member_count(0), max_age(86400), max_uses(0), temporary(false), unique(false), uses(0), created_at(0) { } @@ -37,22 +37,27 @@ invite& invite::fill_from_json(nlohmann::json* j) { expires_at = (j->contains("expires_at")) ? ts_not_null(j, "expires_at") : 0; created_at = (j->contains("created_at")) ? ts_not_null(j, "created_at") : 0; if (j->contains("guild") && !j->at("guild").is_null()) { - guild_id = snowflake_not_null(&((*j)["guild"]), "id"); - } else if (j->contains("guild_id")) { // check ID for the invite create event + destination_guild = dpp::guild().fill_from_json(&((*j)["guild"])); + guild_id = destination_guild.id; + } else if (j->contains("guild_id")) { // check ID for invite gateway events guild_id = snowflake_not_null(j, "guild_id"); } if (j->contains("channel") && !j->at("channel").is_null()) { - channel_id = snowflake_not_null(&((*j)["channel"]), "id"); - } else if (j->contains("channel_id")) { // check ID for the invite create event + destination_channel = dpp::channel().fill_from_json(&((*j)["channel"])); + channel_id = destination_channel.id; + } else if (j->contains("channel_id")) { // check ID for invite gateway events channel_id = snowflake_not_null(j, "channel_id"); } - inviter_id = (j->contains("inviter")) ? snowflake_not_null(&((*j)["inviter"]), "id") : 0; + if (j->contains("inviter") && !j->at("inviter").is_null()) { + inviter = dpp::user().fill_from_json(&((*j)["inviter"])); + inviter_id = inviter.id; + } target_user_id = (j->contains("target_user")) ? snowflake_not_null(&((*j)["target_user"]), "id") : 0; - target_type = int8_not_null(j, "target_type"); + target_type = static_cast(int8_not_null(j, "target_type")); approximate_presence_count = int32_not_null(j, "approximate_presence_count"); approximate_member_count = int32_not_null(j, "approximate_member_count"); max_age = int32_not_null(j, "max_age"); - max_uses = int32_not_null(j, "max_uses"); + max_uses = int8_not_null(j, "max_uses"); temporary = bool_not_null(j, "temporary"); unique = bool_not_null(j, "unique"); uses = (j->contains("uses")) ? int32_not_null(j, "uses") : 0; @@ -68,7 +73,7 @@ std::string invite::build_json(bool with_id) const { j["max_uses"] = max_uses; if (target_user_id > 0) j["target_user"] = target_user_id; - if (target_type > 0) + if (target_type != itt_none) j["target_type"] = target_type; if (temporary) j["temporary"] = temporary; diff --git a/src/dpp/message.cpp b/src/dpp/message.cpp index d328256116..eb776c8a65 100644 --- a/src/dpp/message.cpp +++ b/src/dpp/message.cpp @@ -47,35 +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) { - custom_id = string_not_null(j, "custom_id"); - type = (component_type)int8_not_null(j, "type"); + } 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")); + 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()) { @@ -85,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; } @@ -219,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; } @@ -409,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; } @@ -424,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 79b9c7b7d4..27a97904fd 100644 --- a/src/dpp/slashcommand.cpp +++ b/src/dpp/slashcommand.cpp @@ -764,13 +764,17 @@ 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)); + auto row = dpp::component().fill_from_json(&c); + if (!row.components.empty()) { + components.push_back(row.components); + } } } return *this; diff --git a/src/unittest/test.cpp b/src/unittest/test.cpp index 246cfbbace..1b93e059eb 100644 --- a/src/unittest/test.cpp +++ b/src/unittest/test.cpp @@ -991,7 +991,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b if (event.is_error()) return; auto created = event.get(); - if (!created.code.empty() && created.channel_id == TEST_TEXT_CHANNEL_ID && created.guild_id == TEST_GUILD_ID && created.inviter_id != 0) { + if (!created.code.empty() && created.channel_id == TEST_TEXT_CHANNEL_ID && created.guild_id == TEST_GUILD_ID && created.inviter.id == bot.me.id) { set_test("INVITE_CREATE", true); } @@ -999,7 +999,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b if (event.is_error()) return; auto retrieved = event.get(); - if (retrieved.code == created.code && retrieved.expires_at == 0 && retrieved.guild_id == created.guild_id && retrieved.channel_id == created.channel_id && retrieved.inviter_id == created.inviter_id) { + if (retrieved.code == created.code && retrieved.expires_at == 0 && retrieved.guild_id == created.guild_id && retrieved.channel_id == created.channel_id && retrieved.inviter.id == created.inviter.id) { set_test("INVITE_GET", true); }