Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: fix various issues and bugs with dpp::emoji #775

Merged
merged 2 commits into from
Aug 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 50 additions & 22 deletions include/dpp/emoji.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,38 +55,66 @@ class DPP_EXPORT emoji : public managed, public json_interface<emoji> {
/**
* @brief Emoji name
*/
std::string name;
std::string name{};
/**
* @brief User id who uploaded the emoji
*/
snowflake user_id;
snowflake user_id{0};
/**
* @brief Flags for the emoji from dpp::emoji_flags
*/
uint8_t flags;
uint8_t flags{0};
/**
* @brief Image data for the emoji if uploading
*/
std::string* image_data;
std::string image_data{};
braindigitalis marked this conversation as resolved.
Show resolved Hide resolved

/**
* @brief Construct a new emoji object
*/
emoji();
emoji() = default;

/**
* @brief Construct a new emoji object with name, ID and flags
*
* @param n The emoji's name
* @param i ID, if it has one (unicode does not)
* @param f Emoji flags (emoji_flags)
*
* @param name The emoji's name
* @param id ID, if it has one (unicode does not)
* @param flags Emoji flags (emoji_flags)
*/
emoji(const std::string n, const snowflake i = 0, const uint8_t f = 0);
emoji(const std::string_view name, const snowflake id = 0, const uint8_t flags = 0);

/**
* @brief Copy constructor, copies another emoji's data
*
* @param rhs Emoji to copy
*/
emoji(const emoji &rhs) = default;

/**
* @brief Move constructor, moves another emoji's data to this
*
* @param rhs Emoji to move from
*/
emoji(emoji &&rhs) noexcept = default;

/**
* @brief Destroy the emoji object
*/
virtual ~emoji();
~emoji() override = default;

Mishura4 marked this conversation as resolved.
Show resolved Hide resolved
/**
* @brief Copy assignment operator, copies another emoji's data
*
* @param rhs Emoji to copy
*/
emoji &operator=(const emoji &rhs) = default;

/**
* @brief Move constructor, moves another emoji's data to this
*
* @param rhs Emoji to move from
*/
emoji &operator=(emoji &&rhs) noexcept = default;

/**
* @brief Create a mentionable emoji
Expand All @@ -95,35 +123,35 @@ class DPP_EXPORT emoji : public managed, public json_interface<emoji> {
* @param is_animated is emoji animated.
* @return std::string The formatted mention of the emoji.
*/
static std::string get_mention(const std::string& name, const snowflake& id, bool is_animated = false);
static std::string get_mention(std::string_view name, snowflake id, bool is_animated = false);

/**
* @brief Read class values from json object
*
*
* @param j A json object to read from
* @return A reference to self
*/
emoji& fill_from_json(nlohmann::json* j);

/**
* @brief Build the json for this object
*
*
* @param with_id include the id in the JSON
* @return std::string json data
*/
std::string build_json(bool with_id = false) const;
std::string build_json(bool with_id = false) const override;
Mishura4 marked this conversation as resolved.
Show resolved Hide resolved

/**
* @brief Emoji requires colons
*
*
* @return true Requires colons
* @return false Does not require colons
*/
bool requires_colons() const;

/**
* @brief Emoji is managed
*
*
* @return true Is managed
* @return false Is not managed
*/
Expand All @@ -139,25 +167,25 @@ class DPP_EXPORT emoji : public managed, public json_interface<emoji> {

/**
* @brief Is available
*
*
* @return true Is available
* @return false Is unavailable
*/
bool is_available() const;

/**
* @brief Load an image into the object as base64
*
*
* @param image_blob Image binary data
* @param type Type of image. It can be one of `i_gif`, `i_jpg` or `i_png`.
* @return emoji& Reference to self
* @throw dpp::length_exception Image content exceeds discord maximum of 256 kilobytes
*/
emoji& load_image(const std::string &image_blob, const image_type type);
emoji& load_image(std::string_view image_blob, const image_type type);

/**
* @brief Format to name if unicode, name:id if has id or a:name:id if animated
*
*
* @return Formatted name for reactions
*/
std::string format() const;
Expand Down
64 changes: 63 additions & 1 deletion include/dpp/utility.h
Original file line number Diff line number Diff line change
Expand Up @@ -500,7 +500,7 @@ namespace dpp {
* @param is_animated is emoji animated.
* @return std::string The formatted mention of the emoji.
*/
std::string DPP_EXPORT emoji_mention(const std::string& name, const snowflake& id, bool is_animated = false);
std::string DPP_EXPORT emoji_mention(std::string_view name, snowflake id, bool is_animated = false);

/**
* @brief Create a mentionable role.
Expand All @@ -509,6 +509,68 @@ namespace dpp {
*/
std::string DPP_EXPORT role_mention(const snowflake& id);

#ifdef _DOXYGEN_
/**
* @brief Get the mime type for an image type.
* @param type Image type
* @return std::string The mime type for this image type
*/
std::string DPP_EXPORT mime_type(image_type type);

/**
* @brief Get the mime type for a sticker format.
* @param format Sticker format
* @return std::string The mime type for this sticker format
*/
std::string DPP_EXPORT mime_type(sticker_format format);

/**
* @brief Get the file extension for an image type.
* @param type Image type
* @return std::string The file extension (e.g. ".png") for this image type
*/
std::string DPP_EXPORT file_extension(image_type type);

/**
* @brief Get the file extension for a sticker format.
* @param format Sticker format
* @return std::string The file extension (e.g. ".png") for this sticker format
*/
std::string DPP_EXPORT file_extension(sticker_format format);
#else
/**
* @brief Get the mime type for an image type.
* @param type Image type
* @return std::string The mime type for this image type
*/
template <typename T>
extern std::enable_if_t<std::is_same_v<T, image_type>, std::string> DPP_EXPORT mime_type(T type);

/**
* @brief Get the mime type for a sticker format.
* @param format Sticker format
* @return std::string The mime type for this sticker format
*/
template <typename T>
extern std::enable_if_t<std::is_same_v<T, sticker_format>, std::string> DPP_EXPORT mime_type(T format);

/**
* @brief Get the file extension for an image type.
* @param type Image type
* @return std::string The file extension (e.g. ".png") for this image type
*/
template <typename T>
extern std::enable_if_t<std::is_same_v<T, image_type>, std::string> DPP_EXPORT file_extension(T type);

/**
* @brief Get the file extension for a sticker format.
* @param format Sticker format
* @return std::string The file extension (e.g. ".png") for this sticker format
*/
template <typename T>
extern std::enable_if_t<std::is_same_v<T, sticker_format>, std::string> DPP_EXPORT file_extension(T format);
#endif

/**
* @brief Returns the library's version string
*
Expand Down
15 changes: 1 addition & 14 deletions src/dpp/cluster/sticker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,25 +23,12 @@

namespace dpp {

namespace {
std::string get_sticker_mimetype(const sticker &s) {
static const std::map<sticker_format, std::string> mime_types = {
{ sticker_format::sf_png, "image/png" },
{ sticker_format::sf_apng, "image/png" },
{ sticker_format::sf_lottie, "application/json" },
{ sticker_format::sf_gif, "image/gif" },
};

return mime_types.find(s.format_type)->second;
}
} // namespace

void cluster::guild_sticker_create(sticker &s, command_completion_event_t callback) {
this->post_rest(API_PATH "/guilds", std::to_string(s.guild_id), "stickers", m_post, s.build_json(false), [this, callback](json &j, const http_request_completion_t& http) {
if (callback) {
callback(confirmation_callback_t(this, sticker().fill_from_json(&j), http));
}
}, s.filename, s.filecontent, get_sticker_mimetype(s));
}, s.filename, s.filecontent, utility::mime_type(s.format_type));
}

void cluster::guild_sticker_delete(snowflake sticker_id, snowflake guild_id, command_completion_event_t callback) {
Expand Down
46 changes: 28 additions & 18 deletions src/dpp/discordevents.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -210,28 +210,38 @@ void set_bool_not_null(const json* j, const char *keyname, bool &v) {

std::string base64_encode(unsigned char const* buf, unsigned int buffer_length) {
/* Quick and dirty base64 encode */
static const char to_base64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
size_t ret_size = buffer_length + 2;

ret_size = 4 * ret_size / 3;

static constexpr std::string_view to_base64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static constexpr auto push = [](std::string &dst, unsigned char b0, unsigned char b1, unsigned char b2) {
dst.push_back(to_base64[ ((b0 & 0xfc) >> 2) ]);
dst.push_back(to_base64[ ((b0 & 0x03) << 4) + ((b1 & 0xf0) >> 4) ]);
dst.push_back(to_base64[ ((b1 & 0x0f) << 2) + ((b2 & 0xc0) >> 6) ]);
dst.push_back(to_base64[ ((b2 & 0x3f)) ]);
};
size_t ret_size = 4 * ((buffer_length + 2) / 3); // ceil(4*size/3)
size_t i = 0;
std::string ret;
ret.reserve(ret_size);

for (unsigned int i=0; i<ret_size/4; ++i)
{
size_t index = i*3;
unsigned char b3[3];
b3[0] = buf[index+0];
b3[1] = buf[index+1];
b3[2] = buf[index+2];
ret.reserve(ret_size);

ret.push_back(to_base64[ ((b3[0] & 0xfc) >> 2) ]);
ret.push_back(to_base64[ ((b3[0] & 0x03) << 4) + ((b3[1] & 0xf0) >> 4) ]);
ret.push_back(to_base64[ ((b3[1] & 0x0f) << 2) + ((b3[2] & 0xc0) >> 6) ]);
ret.push_back(to_base64[ ((b3[2] & 0x3f)) ]);
if (buffer_length > 2) { // vvvvv avoid unsigned overflow
while (i < buffer_length - 2) {
push(ret, buf[i], buf[i + 1], buf[i + 2]);
i += 3;
}
}
size_t left = buffer_length - i;
if (left >= 1) { // handle non-multiple of 3s, pad the end with =
ret.push_back(to_base64[ ((buf[i] & 0xfc) >> 2) ]);
if (left >= 2) {
ret.push_back(to_base64[ ((buf[i] & 0x03) << 4) + ((buf[i + 1] & 0xf0) >> 4) ]);
ret.push_back(to_base64[ ((buf[i + 1] & 0x0f) << 2) ]);
ret.push_back('=');
}
else {
ret.push_back(to_base64[ ((buf[i] & 0x03) << 4) ]);
ret += "==";
}
}

return ret;
}

Expand Down
31 changes: 6 additions & 25 deletions src/dpp/emoji.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,9 @@ namespace dpp {

using json = nlohmann::json;

emoji::emoji() : managed(), user_id(0), flags(0), image_data(nullptr)
{
}

emoji::emoji(const std::string n, const snowflake i, const uint8_t f) : managed(i), name(n), user_id(0), flags(f), image_data(nullptr)
{
}
emoji::emoji(const std::string_view n, const snowflake i, const uint8_t f) : managed(i), name(n), flags(f) {}

emoji::~emoji() {
delete image_data;
}

std::string emoji::get_mention(const std::string &name, const snowflake &id, bool is_animated) {
std::string emoji::get_mention(std::string_view name, snowflake id, bool is_animated) {
return utility::emoji_mention(name,id,is_animated);
}

Expand Down Expand Up @@ -68,8 +58,8 @@ std::string emoji::build_json(bool with_id) const {
j["id"] = std::to_string(id);
}
j["name"] = name;
if (image_data) {
j["image"] = *image_data;
if (!image_data.empty()) {
j["image"] = image_data;
}
return j.dump();
}
Expand All @@ -90,21 +80,12 @@ bool emoji::is_available() const {
return flags & e_available;
}

emoji& emoji::load_image(const std::string &image_blob, const image_type type) {
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" },
};
emoji& emoji::load_image(std::string_view image_blob, const image_type type) {
if (image_blob.size() > MAX_EMOJI_SIZE) {
throw dpp::length_exception("Emoji 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," + base64_encode((unsigned char const*)image_blob.data(), (unsigned int)image_blob.length()));
image_data = "data:" + utility::mime_type(type) + ";base64," + base64_encode(reinterpret_cast<unsigned char const*>(image_blob.data()), static_cast<unsigned int>(image_blob.length()));

return *this;
}
Expand Down
Loading
Loading