/************************************************************************************ * * D++, A Lightweight C++ library for Discord * * Copyright 2021 Craig Edwards and D++ contributors * (https://github.com/brainboxdotcc/DPP/graphs/contributors) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ************************************************************************************/ #define _XOPEN_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #ifdef _WIN32 #include #include #include char* strptime(const char* s, const char* f, struct tm* tm) { std::istringstream input(s); input.imbue(std::locale(setlocale(LC_ALL, nullptr))); input >> std::get_time(tm, f); if (input.fail()) { return const_cast< char* >(""); } return (char*)(s + input.tellg()); } #endif namespace dpp { uint64_t SnowflakeNotNull(const json* j, const char *keyname) { /* Snowflakes are a special case. Pun intended. * Because discord drinks the javascript kool-aid, they have to send 64 bit integers as strings as js can't deal with them * even though we can. So, all snowflakes are sent and received wrapped as string values and must be read by nlohmann::json * as string types, then converted from string to uint64_t. Checks for existence of the value, and that it is a string containing * a number. If not, then this function returns 0. */ auto k = j->find(keyname); if (k != j->end()) { return !k->is_null() && k->is_string() ? strtoull(k->get().c_str(), nullptr, 10) : 0; } else { return 0; } } void SetSnowflakeNotNull(const json* j, const char *keyname, uint64_t &v) { auto k = j->find(keyname); if (k != j->end()) { v = !k->is_null() && k->is_string() ? strtoull(k->get().c_str(), nullptr, 10) : 0; } } std::string StringNotNull(const json* j, const char *keyname) { /* Returns empty string if the value is not a string, or is null or not defined */ auto k = j->find(keyname); if (k != j->end()) { return !k->is_null() && k->is_string() ? k->get() : ""; } else { return ""; } } void SetStringNotNull(const json* j, const char *keyname, std::string &v) { /* Returns empty string if the value is not a string, or is null or not defined */ auto k = j->find(keyname); if (k != j->end()) { v = !k->is_null() && k->is_string() ? k->get() : ""; } } uint64_t Int64NotNull(const json* j, const char *keyname) { auto k = j->find(keyname); if (k != j->end()) { return !k->is_null() && !k->is_string() ? k->get() : 0; } else { return 0; } } void SetInt64NotNull(const json* j, const char *keyname, uint64_t &v) { auto k = j->find(keyname); if (k != j->end()) { v = !k->is_null() && !k->is_string() ? k->get() : 0; } } uint32_t Int32NotNull(const json* j, const char *keyname) { auto k = j->find(keyname); if (k != j->end()) { return !k->is_null() && !k->is_string() ? k->get() : 0; } else { return 0; } } void SetInt32NotNull(const json* j, const char *keyname, uint32_t &v) { auto k = j->find(keyname); if (k != j->end()) { v = !k->is_null() && !k->is_string() ? k->get() : 0; } } uint16_t Int16NotNull(const json* j, const char *keyname) { auto k = j->find(keyname); if (k != j->end()) { return !k->is_null() && !k->is_string() ? k->get() : 0; } else { return 0; } } void SetInt16NotNull(const json* j, const char *keyname, uint16_t &v) { auto k = j->find(keyname); if (k != j->end()) { v = !k->is_null() && !k->is_string() ? k->get() : 0; } } uint8_t Int8NotNull(const json* j, const char *keyname) { auto k = j->find(keyname); if (k != j->end()) { return !k->is_null() && !k->is_string() ? k->get() : 0; } else { return 0; } } void SetInt8NotNull(const json* j, const char *keyname, uint8_t &v) { auto k = j->find(keyname); if (k != j->end()) { v = !k->is_null() && !k->is_string() ? k->get() : 0; } } bool BoolNotNull(const json* j, const char *keyname) { auto k = j->find(keyname); if (k != j->end()) { return !k->is_null() ? (k->get() == true) : false; } else { return false; } } void SetBoolNotNull(const json* j, const char *keyname, bool &v) { auto k = j->find(keyname); if (k != j->end()) { v = !k->is_null() ? (k->get() == true) : false; } } 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; std::string ret; ret.reserve(ret_size); for (unsigned int i=0; i> 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)) ]); } return ret; } time_t TimestampNotNull(const json* j, const char* keyname) { /* Parses discord ISO 8061 timestamps to time_t, accounting for local time adjustment. * Note that discord timestamps contain a decimal seconds part, which time_t and struct tm * can't handle. We strip these out. */ time_t retval = 0; if (j->find(keyname) != j->end() && !(*j)[keyname].is_null() && (*j)[keyname].is_string()) { tm timestamp = {}; std::string timedate = (*j)[keyname].get(); if (timedate.find('+') != std::string::npos && timedate.find('.') != std::string::npos) { std::string tzpart = timedate.substr(timedate.find('+'), timedate.length()); timedate = timedate.substr(0, timedate.find('.')) + tzpart ; strptime(timedate.substr(0, 19).c_str(), "%FT%TZ%z", ×tamp); timestamp.tm_isdst = 0; retval = mktime(×tamp); } else { strptime(timedate.substr(0, 19).c_str(), "%F %T", ×tamp); retval = mktime(×tamp); } } return retval; } void SetTimestampNotNull(const json* j, const char* keyname, time_t &v) { /* Parses discord ISO 8061 timestamps to time_t, accounting for local time adjustment. * Note that discord timestamps contain a decimal seconds part, which time_t and struct tm * can't handle. We strip these out. */ time_t retval = 0; if (j->find(keyname) != j->end() && !(*j)[keyname].is_null() && (*j)[keyname].is_string()) { tm timestamp = {}; std::string timedate = (*j)[keyname].get(); if (timedate.find('+') != std::string::npos && timedate.find('.') != std::string::npos) { std::string tzpart = timedate.substr(timedate.find('+'), timedate.length()); timedate = timedate.substr(0, timedate.find('.')) + tzpart ; strptime(timedate.substr(0, 19).c_str(), "%FT%TZ%z", ×tamp); timestamp.tm_isdst = 0; retval = mktime(×tamp); } else { strptime(timedate.substr(0, 19).c_str(), "%F %T", ×tamp); retval = mktime(×tamp); } v = retval; } } std::map eventmap = { { "__LOG__", new dpp::events::logger() }, { "GUILD_CREATE", new dpp::events::guild_create() }, { "GUILD_UPDATE", new dpp::events::guild_update() }, { "GUILD_DELETE", new dpp::events::guild_delete() }, { "GUILD_MEMBER_UPDATE", new dpp::events::guild_member_update() }, { "RESUMED", new dpp::events::resumed() }, { "READY", new dpp::events::ready() }, { "CHANNEL_CREATE", new dpp::events::channel_create() }, { "CHANNEL_UPDATE", new dpp::events::channel_update() }, { "CHANNEL_DELETE", new dpp::events::channel_delete() }, { "PRESENCE_UPDATE", new dpp::events::presence_update() }, { "TYPING_START", new dpp::events::typing_start() }, { "MESSAGE_CREATE", new dpp::events::message_create() }, { "MESSAGE_UPDATE", new dpp::events::message_update() }, { "MESSAGE_DELETE", new dpp::events::message_delete() }, { "MESSAGE_DELETE_BULK", new dpp::events::message_delete_bulk() }, { "MESSAGE_REACTION_ADD", new dpp::events::message_reaction_add() }, { "MESSAGE_REACTION_REMOVE", new dpp::events::message_reaction_remove() }, { "MESSAGE_REACTION_REMOVE_ALL", new dpp::events::message_reaction_remove_all() }, { "MESSAGE_REACTION_REMOVE_EMOJI", new dpp::events::message_reaction_remove_emoji() }, { "CHANNEL_PINS_UPDATE", new dpp::events::channel_pins_update() }, { "GUILD_BAN_ADD", new dpp::events::guild_ban_add() }, { "GUILD_BAN_REMOVE", new dpp::events::guild_ban_remove() }, { "GUILD_EMOJIS_UPDATE", new dpp::events::guild_emojis_update() }, { "GUILD_INTEGRATIONS_UPDATE", new dpp::events::guild_integrations_update() }, { "INTEGRATION_CREATE", new dpp::events::integration_create() }, { "INTEGRATION_UPDATE", new dpp::events::integration_update() }, { "INTEGRATION_DELETE", new dpp::events::integration_delete() }, { "GUILD_MEMBER_ADD", new dpp::events::guild_member_add() }, { "GUILD_MEMBER_REMOVE", new dpp::events::guild_member_remove() }, { "GUILD_MEMBERS_CHUNK", new dpp::events::guild_members_chunk() }, { "GUILD_ROLE_CREATE", new dpp::events::guild_role_create() }, { "GUILD_ROLE_UPDATE", new dpp::events::guild_role_update() }, { "GUILD_ROLE_DELETE", new dpp::events::guild_role_delete() }, { "VOICE_STATE_UPDATE", new dpp::events::voice_state_update() }, { "VOICE_SERVER_UPDATE", new dpp::events::voice_server_update() }, { "WEBHOOKS_UPDATE", new dpp::events::webhooks_update() }, { "INVITE_CREATE", new dpp::events::invite_create() }, { "INVITE_DELETE", new dpp::events::invite_delete() }, { "APPLICATION_COMMAND_CREATE", new dpp::events::application_command_create() }, { "APPLICATION_COMMAND_UPDATE", new dpp::events::application_command_update() }, { "APPLICATION_COMMAND_DELETE", new dpp::events::application_command_delete() }, { "INTERACTION_CREATE", new dpp::events::interaction_create() }, { "USER_UPDATE", new dpp::events::user_update() }, { "GUILD_JOIN_REQUEST_DELETE", new dpp::events::guild_join_request_delete() }, { "STAGE_INSTANCE_CREATE", new dpp::events::stage_instance_create() }, { "STAGE_INSTANCE_DELETE", new dpp::events::stage_instance_delete() }, { "THREAD_CREATE", new dpp::events::thread_create() }, { "THREAD_UPDATE", new dpp::events::thread_update() }, { "THREAD_DELETE", new dpp::events::thread_delete() }, { "THREAD_LIST_SYNC", new dpp::events::thread_list_sync() }, { "THREAD_MEMBER_UPDATE", new dpp::events::thread_member_update() }, { "THREAD_MEMBERS_UPDATE", new dpp::events::thread_members_update() }, { "GUILD_APPLICATION_COMMAND_COUNTS_UPDATE", nullptr }, { "GUILD_STICKERS_UPDATE", new dpp::events::guild_stickers_update() }, { "APPLICATION_COMMAND_PERMISSIONS_UPDATE", nullptr }, }; void discord_client::HandleEvent(const std::string &event, json &j, const std::string &raw) { auto ev_iter = eventmap.find(event); if (ev_iter != eventmap.end()) { /* A handler with nullptr is silently ignored. We don't plan to make a handler for it * so this usually some user-only thing thats crept into the API and shown to bots * that we dont care about. */ if (ev_iter->second != nullptr) { ev_iter->second->handle(this, j, raw); } } else { log(dpp::ll_debug, fmt::format("Unhandled event: {}, {}", event, j.dump())); } } };