Skip to content

Commit

Permalink
feat: added application and following support for webhooks. (#960)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jaskowicz1 authored Oct 21, 2023
1 parent f19db87 commit 82f9297
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 70 deletions.
90 changes: 75 additions & 15 deletions include/dpp/webhook.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
#include <dpp/misc-enum.h>
#include <dpp/managed.h>
#include <dpp/json_fwd.h>
#include <dpp/user.h>
#include <dpp/guild.h>
#include <dpp/channel.h>
#include <unordered_map>
#include <dpp/json_interface.h>

Expand All @@ -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.
};

/**
Expand All @@ -62,15 +66,76 @@ class DPP_EXPORT webhook : public managed, public json_interface<webhook> {
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
Expand All @@ -93,11 +158,6 @@ class DPP_EXPORT webhook : public managed, public json_interface<webhook> {
*/
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
*
Expand Down
21 changes: 5 additions & 16 deletions src/dpp/cluster/webhook.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,34 +23,29 @@

namespace dpp {

void cluster::create_webhook(const class webhook &w, command_completion_event_t callback) {
rest_request<webhook>(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<webhook>(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<confirmation>(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},
});
rest_request<confirmation>(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<confirmation>(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<webhook>(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},
Expand All @@ -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()) {
Expand All @@ -71,20 +65,19 @@ void cluster::edit_webhook_with_token(const class webhook& wh, command_completio
rest_request<webhook>(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;
Expand All @@ -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<webhook>(this, API_PATH "/channels", std::to_string(channel_id), "webhooks", m_get, "", callback);
}
Expand All @@ -108,20 +100,17 @@ void cluster::get_guild_webhooks(snowflake guild_id, command_completion_event_t
rest_request_list<webhook>(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<webhook>(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},
});
rest_request<message>(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<webhook>(this, API_PATH "/webhooks", std::to_string(webhook_id), utility::url_encode(token), m_get, "", callback);
}
Expand Down
59 changes: 20 additions & 39 deletions src/dpp/webhook.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
}

Expand All @@ -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<image_type, std::string> 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<unsigned char const*>(image_blob.data()), static_cast<unsigned int>(image_blob.length())));

return *this;
}
Expand Down

0 comments on commit 82f9297

Please sign in to comment.