From 82f9297965a2a1cc0789f3d2c8e48c33c283d0ba Mon Sep 17 00:00:00 2001 From: Archie Jaskowicz Date: Sat, 21 Oct 2023 18:05:51 +0100 Subject: [PATCH] feat: added application and following support for webhooks. (#960) --- include/dpp/webhook.h | 90 ++++++++++++++++++++++++++++++------- src/dpp/cluster/webhook.cpp | 21 +++------ src/dpp/webhook.cpp | 59 +++++++++--------------- 3 files changed, 100 insertions(+), 70 deletions(-) diff --git a/include/dpp/webhook.h b/include/dpp/webhook.h index 808eb45227..0593a9fcb4 100644 --- a/include/dpp/webhook.h +++ b/include/dpp/webhook.h @@ -25,6 +25,9 @@ #include #include #include +#include +#include +#include #include #include @@ -35,7 +38,8 @@ namespace dpp { */ enum webhook_type { w_incoming = 1, //!< Incoming webhook - w_channel_follower = 2 //!< Channel following webhook + w_channel_follower = 2, //!< Channel following webhook + w_application = 3 //!< Application webhooks for interactions. }; /** @@ -62,15 +66,76 @@ class DPP_EXPORT webhook : public managed, public json_interface { virtual json to_json_impl(bool with_id = false) const; public: - uint8_t type; //!< the type of the webhook - snowflake guild_id; //!< Optional: the guild id this webhook is for - snowflake channel_id; //!< the channel id this webhook is for - snowflake user_id; //!< Optional: the user this webhook was created by (not returned when getting a webhook with its token) - std::string name; //!< the default name of the webhook (may be empty) - std::string avatar; //!< the default avatar of the webhook (may be empty) - std::string token; //!< Optional: the secure token of the webhook (returned for Incoming Webhooks) - snowflake application_id; //!< the bot/OAuth2 application that created this webhook (may be empty) - std::string* image_data; //!< base64 encoded image data if uploading a new image + /** + * @brief Type of the webhook from dpp::webhook_type. + */ + uint8_t type; + + /** + * @brief The guild id this webhook is for. + * @note This field is optional, and may also be empty. + */ + snowflake guild_id; + + /** + * @brief The channel id this webhook is for. + * @note This may be empty. + */ + snowflake channel_id; + + /** + * @brief The user this webhook was created by. + * @note This field is optional. + * @warning This is not returned when getting a webhook with its token! + */ + user user_obj; + + /** + * @brief The default name of the webhook. + * @note This may be empty. + */ + std::string name; + + /** + * @brief The default avatar of the webhook + * @note This may be empty. + */ + utility::iconhash avatar; + + /** + * @brief The secure token of the webhook (returned for Incoming Webhooks). + * @note This field is optional. + */ + std::string token; + + /** + * @brief The bot/OAuth2 application that created this webhook. + * @note This may be empty. + */ + snowflake application_id; + + /** + * @brief The guild of the channel that this webhook is following (only for Channel Follower Webhooks). + * @warning This will be absent if the webhook creator has since lost access to the guild where the followed channel resides! + */ + guild source_guild; + + /** + * @brief The channel that this webhook is following (only for Channel Follower Webhooks). + * @warning This will be absent if the webhook creator has since lost access to the guild where the followed channel resides! + */ + channel source_channel; + + /** + * @brief The url used for executing the webhook (returned by the webhooks OAuth2 flow). + */ + std::string url; + + /** + * @brief base64 encoded image data if uploading a new image. + * @warning You should only ever read data from here. If you want to set the data, use dpp::webhook::load_image. + */ + std::string image_data; /** * @brief Construct a new webhook object @@ -93,11 +158,6 @@ class DPP_EXPORT webhook : public managed, public json_interface { */ webhook(const snowflake webhook_id, const std::string& webhook_token); - /** - * @brief Destroy the webhook object - */ - ~webhook(); - /** * @brief Base64 encode image data and allocate it to image_data * diff --git a/src/dpp/cluster/webhook.cpp b/src/dpp/cluster/webhook.cpp index f182012ec2..609db9ae1b 100644 --- a/src/dpp/cluster/webhook.cpp +++ b/src/dpp/cluster/webhook.cpp @@ -23,16 +23,14 @@ namespace dpp { -void cluster::create_webhook(const class webhook &w, command_completion_event_t callback) { - rest_request(this, API_PATH "/channels", std::to_string(w.channel_id), "webhooks", m_post, w.build_json(false), callback); +void cluster::create_webhook(const class webhook &wh, command_completion_event_t callback) { + rest_request(this, API_PATH "/channels", std::to_string(wh.channel_id), "webhooks", m_post, wh.build_json(false), callback); } - void cluster::delete_webhook(snowflake webhook_id, command_completion_event_t callback) { rest_request(this, API_PATH "/webhooks", std::to_string(webhook_id), "", m_delete, "", callback); } - void cluster::delete_webhook_message(const class webhook &wh, snowflake message_id, snowflake thread_id, command_completion_event_t callback) { std::string parameters = utility::make_url_parameters({ {"thread_id", thread_id}, @@ -40,17 +38,14 @@ void cluster::delete_webhook_message(const class webhook &wh, snowflake message_ rest_request(this, API_PATH "/webhooks", std::to_string(wh.id), utility::url_encode(!wh.token.empty() ? wh.token: token) + "/messages/" + std::to_string(message_id) + parameters, m_delete, "", callback); } - void cluster::delete_webhook_with_token(snowflake webhook_id, const std::string &token, command_completion_event_t callback) { rest_request(this, API_PATH "/webhooks", std::to_string(webhook_id), utility::url_encode(token), m_delete, "", callback); } - void cluster::edit_webhook(const class webhook& wh, command_completion_event_t callback) { rest_request(this, API_PATH "/webhooks", std::to_string(wh.id), "", m_patch, wh.build_json(false), callback); } - void cluster::edit_webhook_message(const class webhook &wh, const struct message& m, snowflake thread_id, command_completion_event_t callback) { std::string parameters = utility::make_url_parameters({ {"thread_id", thread_id}, @@ -62,7 +57,6 @@ void cluster::edit_webhook_message(const class webhook &wh, const struct message }, m.filename, m.filecontent, m.filemimetype); } - void cluster::edit_webhook_with_token(const class webhook& wh, command_completion_event_t callback) { json jwh = wh.to_json(true); if (jwh.find("channel_id") != jwh.end()) { @@ -71,20 +65,19 @@ void cluster::edit_webhook_with_token(const class webhook& wh, command_completio rest_request(this, API_PATH "/webhooks", std::to_string(wh.id), utility::url_encode(wh.token), m_patch, jwh.dump(), callback); } - void cluster::execute_webhook(const class webhook &wh, const struct message& m, bool wait, snowflake thread_id, const std::string& thread_name, command_completion_event_t callback) { std::string parameters = utility::make_url_parameters({ {"wait", wait}, {"thread_id", thread_id}, }); std::string body; - if (!thread_name.empty() || !wh.avatar.empty() || !wh.name.empty()) { // only use json::parse if thread_name is set + if (!thread_name.empty() || !wh.avatar.to_string().empty() || !wh.name.empty()) { // only use json::parse if thread_name is set json j = m.to_json(false); if (!thread_name.empty()) { j["thread_name"] = thread_name; } - if (!wh.avatar.empty()) { - j["avatar_url"] = wh.avatar; + if (!wh.avatar.to_string().empty()) { + j["avatar_url"] = wh.avatar.to_string(); } if (!wh.name.empty()) { j["username"] = wh.name; @@ -98,7 +91,6 @@ void cluster::execute_webhook(const class webhook &wh, const struct message& m, }, m.filename, m.filecontent, m.filemimetype); } - void cluster::get_channel_webhooks(snowflake channel_id, command_completion_event_t callback) { rest_request_list(this, API_PATH "/channels", std::to_string(channel_id), "webhooks", m_get, "", callback); } @@ -108,12 +100,10 @@ void cluster::get_guild_webhooks(snowflake guild_id, command_completion_event_t rest_request_list(this, API_PATH "/guilds", std::to_string(guild_id), "webhooks", m_get, "", callback); } - void cluster::get_webhook(snowflake webhook_id, command_completion_event_t callback) { rest_request(this, API_PATH "/webhooks", std::to_string(webhook_id), "", m_get, "", callback); } - void cluster::get_webhook_message(const class webhook &wh, snowflake message_id, snowflake thread_id, command_completion_event_t callback) { std::string parameters = utility::make_url_parameters({ {"thread_id", thread_id}, @@ -121,7 +111,6 @@ void cluster::get_webhook_message(const class webhook &wh, snowflake message_id, rest_request(this, API_PATH "/webhooks", std::to_string(wh.id), utility::url_encode(!wh.token.empty() ? wh.token: token) + "/messages/" + std::to_string(message_id) + parameters, m_get, "", callback); } - void cluster::get_webhook_with_token(snowflake webhook_id, const std::string &token, command_completion_event_t callback) { rest_request(this, API_PATH "/webhooks", std::to_string(webhook_id), utility::url_encode(token), m_get, "", callback); } diff --git a/src/dpp/webhook.cpp b/src/dpp/webhook.cpp index 9ef46d2df6..4b3c23a620 100644 --- a/src/dpp/webhook.cpp +++ b/src/dpp/webhook.cpp @@ -30,7 +30,7 @@ using json = nlohmann::json; const size_t MAX_ICON_SIZE = 256 * 1024; -webhook::webhook() : managed(), type(w_incoming), guild_id(0), channel_id(0), user_id(0), application_id(0), image_data(nullptr) +webhook::webhook() : managed(), type(w_incoming), guild_id(0), channel_id(0), application_id(0) { } @@ -56,66 +56,47 @@ webhook::webhook(const snowflake webhook_id, const std::string& webhook_token) : id = webhook_id; } -webhook::~webhook() { - delete image_data; -} - webhook& webhook::fill_from_json_impl(nlohmann::json* j) { - id = snowflake_not_null(j, "id"); - type = int8_not_null(j, "type"); - channel_id = snowflake_not_null(j, "channel_id"); - guild_id = snowflake_not_null(j, "guild_id"); + set_snowflake_not_null(j, "id", id); + set_int8_not_null(j, "type", type); + set_snowflake_not_null(j, "guild_id", guild_id); + set_snowflake_not_null(j, "channel_id", channel_id); if (j->contains("user")) { - json & user = (*j)["user"]; - user_id = snowflake_not_null(&user, "id"); + user_obj = user().fill_from_json(&((*j)["user"])); + } + set_string_not_null(j, "name", name); + set_iconhash_not_null(j, "avatar", avatar); + set_string_not_null(j, "token", token); + set_snowflake_not_null(j, "application_id", application_id); + if (j->contains("source_guild")) { + source_guild = guild().fill_from_json(&((*j)["source_guild"])); + } + if (j->contains("source_channel")) { + source_channel = channel().fill_from_json(&((*j)["source_channel"])); } - name = string_not_null(j, "name"); - avatar = string_not_null(j, "avatar"); - token = string_not_null(j, "token"); - application_id = snowflake_not_null(j, "application_id"); + set_string_not_null(j, "url", url); return *this; } json webhook::to_json_impl(bool with_id) const { json j; - if (with_id) { - j["id"] = std::to_string(id); - } j["name"] = name; - j["type"] = type; if (channel_id) { j["channel_id"] = channel_id; } - if (guild_id) { - j["guild_id"] = guild_id; - } - if (!name.empty()) { - j["name"] = name; - } - if (image_data) { - j["avatar"] = *image_data; - } - if (application_id) { - j["application_id"] = application_id; + if (!image_data.empty()) { + j["avatar"] = image_data; } return j; } webhook& webhook::load_image(const std::string &image_blob, const image_type type, bool is_base64_encoded) { - static const std::map mimetypes = { - { i_gif, "image/gif" }, - { i_jpg, "image/jpeg" }, - { i_png, "image/png" }, - { i_webp, "image/webp" }, - }; if (image_blob.size() > MAX_ICON_SIZE) { throw dpp::length_exception("Webhook icon file exceeds discord limit of 256 kilobytes"); } - /* If there's already image data defined, free the old data, to prevent a memory leak */ - delete image_data; - image_data = new std::string("data:" + mimetypes.find(type)->second + ";base64," + (is_base64_encoded ? image_blob : base64_encode((unsigned char const*)image_blob.data(), (unsigned int)image_blob.length()))); + image_data = "data:" + utility::mime_type(type) + ";base64," + (is_base64_encoded ? image_blob : base64_encode(reinterpret_cast(image_blob.data()), static_cast(image_blob.length()))); return *this; }