diff --git a/.clang-tidy b/.clang-tidy index b1312fbca0..33014a7878 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -1,2 +1,2 @@ # TODO: Discuss about -readability-identifier-length, -readability-avoid-const-params-in-decls -Checks: "-*,bugprone-*,cert-*,clang-analyzer-*,concurrency-*,cppcoreguidelines-*,llvm-namespace-comment,modernize-*,performance-*,portability-*,readability-*,-bugprone-implicit-widening-of-multiplication-result, -bugprone-easily-swappable-parameters,-readability-identifier-length,-portability-restrict-system-includes,-modernize-use-trailing-return-type,-cppcoreguidelines-non-private-member-variables-in-classes,-readability-avoid-const-params-in-decls" +Checks: "-*,bugprone-*,cert-*,clang-analyzer-*,concurrency-*,cppcoreguidelines-*,llvm-namespace-comment,modernize-*,performance-*,portability-*,readability-*,-bugprone-implicit-widening-of-multiplication-result,-bugprone-easily-swappable-parameters,-readability-identifier-length,-portability-restrict-system-includes,-modernize-use-trailing-return-type,-cppcoreguidelines-non-private-member-variables-in-classes,-readability-avoid-const-params-in-decls,-cppcoreguidelines-owning-memory,-readability-function-cognitive-complexity,-cppcoreguidelines-avoid-do-while" diff --git a/.cspell.json b/.cspell.json index ebf8da3dab..82c89a6eb2 100644 --- a/.cspell.json +++ b/.cspell.json @@ -2,6 +2,9 @@ "version": "0.2", "language": "en-GB", "words": [ + "EVFILT", + "fflags", + "udata", "blurple", "featurable", "libdpp", diff --git a/buildtools/classes/Generator/CoroGenerator.php b/buildtools/classes/Generator/CoroGenerator.php index 7749054bc9..1a68809a70 100644 --- a/buildtools/classes/Generator/CoroGenerator.php +++ b/buildtools/classes/Generator/CoroGenerator.php @@ -116,7 +116,7 @@ public function getCommentArray(): array */ public function saveHeader(string $content): void { - $content .= "[[nodiscard]] async co_request(const std::string &url, http_method method, const std::string &postdata = \"\", const std::string &mimetype = \"text/plain\", const std::multimap &headers = {}, const std::string &protocol = \"1.1\", time_t request_timeout = 5);\n\n"; + $content .= "[[nodiscard]] async co_request(const std::string &url, http_method method, const std::string &postdata = \"\", const std::string &mimetype = \"text/plain\", const std::multimap &headers = {}, const std::string &protocol = \"1.1\");\n\n"; file_put_contents('include/dpp/cluster_coro_calls.h', $content); } @@ -125,7 +125,7 @@ public function saveHeader(string $content): void */ public function saveCpp(string $cppcontent): void { - $cppcontent .= "dpp::async dpp::cluster::co_request(const std::string &url, http_method method, const std::string &postdata, const std::string &mimetype, const std::multimap &headers, const std::string &protocol, time_t request_timeout) {\n\treturn async{ [&, this] (C &&cc) { return this->request(url, method, std::forward(cc), postdata, mimetype, headers, protocol, request_timeout); }};\n} + $cppcontent .= "dpp::async dpp::cluster::co_request(const std::string &url, http_method method, const std::string &postdata, const std::string &mimetype, const std::multimap &headers, const std::string &protocol) {\n\treturn async{ [&, this] (C &&cc) { return this->request(url, method, std::forward(cc), postdata, mimetype, headers, protocol); }};\n} #endif "; diff --git a/buildtools/classes/Generator/SyncGenerator.php b/buildtools/classes/Generator/SyncGenerator.php deleted file mode 100644 index a5c40319a9..0000000000 --- a/buildtools/classes/Generator/SyncGenerator.php +++ /dev/null @@ -1,131 +0,0 @@ -generateHeaderStart() . << -#include -#include - -namespace dpp { - - -EOT; - } - - /** - * @inheritDoc - */ - public function checkForChanges(): bool - { - /* Check if we need to re-generate by comparing modification times */ - $us = file_exists('include/dpp/cluster_sync_calls.h') ? filemtime('include/dpp/cluster_sync_calls.h') : 0; - $them = filemtime('include/dpp/cluster.h'); - if ($them <= $us) { - echo "-- No change required.\n"; - return false; - } - - echo "-- Autogenerating include/dpp/cluster_sync_calls.h\n"; - echo "-- Autogenerating src/dpp/cluster_sync_calls.cpp\n"; - return true; - } - - /** - * @inheritDoc - */ - public function generateHeaderDef(string $returnType, string $currentFunction, string $parameters, string $noDefaults, string $parameterTypes, string $parameterNames): string - { - return "DPP_DEPRECATED(\"Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html\") $returnType {$currentFunction}_sync($parameters);\n\n"; - } - - /** - * @inheritDoc - */ - public function generateCppDef(string $returnType, string $currentFunction, string $parameters, string $noDefaults, string $parameterTypes, string $parameterNames): string - { - return "$returnType cluster::{$currentFunction}_sync($noDefaults) {\n\treturn dpp::sync<$returnType>(this, static_cast(&cluster::$currentFunction)$parameterNames);\n}\n\n"; - } - - /** - * @inheritDoc - */ - public function getCommentArray(): array - { - return [ - " * \memberof dpp::cluster", - " * @throw dpp::rest_exception upon failure to execute REST function", - " * @deprecated This function is deprecated, please use coroutines instead.", - " * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread.", - " * Avoid direct use of this function inside an event handler.", - ]; - } - - /** - * @inheritDoc - */ - public function saveHeader(string $content): void - { - file_put_contents('include/dpp/cluster_sync_calls.h', $content); - } - - /** - * @inheritDoc - */ - public function saveCpp(string $cppcontent): void - { - file_put_contents('src/dpp/cluster_sync_calls.cpp', $cppcontent); - } -} \ No newline at end of file diff --git a/cmake/epoll.cmake b/cmake/epoll.cmake new file mode 100644 index 0000000000..63070933d7 --- /dev/null +++ b/cmake/epoll.cmake @@ -0,0 +1,17 @@ +macro(CHECK_EPOLL VARIABLE) + if(UNIX) + if("${VARIABLE}" MATCHES "^${VARIABLE}$") + message(STATUS "Check if the system supports epoll") + include(CheckSymbolExists) + check_symbol_exists(epoll_create "sys/epoll.h" EPOLL_PROTOTYPE_EXISTS) + + if(EPOLL_PROTOTYPE_EXISTS) + message(STATUS "Check if the system supports epoll - yes") + set(${VARIABLE} 1 CACHE INTERNAL "Result of CHECK_EPOLL" FORCE) + else(EPOLL_PROTOTYPE_EXISTS) + message(STATUS "Check if the system supports epoll - no") + set(${VARIABLE} "" CACHE INTERNAL "Result of CHECK_EPOLL" FORCE) + endif(EPOLL_PROTOTYPE_EXISTS) + endif("${VARIABLE}" MATCHES "^${VARIABLE}$") + endif(UNIX) +endmacro(CHECK_EPOLL) \ No newline at end of file diff --git a/cmake/kqueue.cmake b/cmake/kqueue.cmake new file mode 100644 index 0000000000..955e356d71 --- /dev/null +++ b/cmake/kqueue.cmake @@ -0,0 +1,17 @@ +macro(CHECK_KQUEUE VARIABLE) + if(UNIX) + if("${VARIABLE}" MATCHES "^${VARIABLE}$") + message(STATUS "Check if the system supports kqueue") + include(CheckSymbolExists) + check_symbol_exists(kqueue "sys/event.h" KQUEUE_PROTOTYPE_EXISTS) + + if(KQUEUE_PROTOTYPE_EXISTS) + message(STATUS "Check if the system supports kqueue - yes") + set(${VARIABLE} 1 CACHE INTERNAL "Result of CHECK_KQUEUE" FORCE) + else(KQUEUE_PROTOTYPE_EXISTS) + message(STATUS "Check if the system supports kqueue - no") + set(${VARIABLE} "" CACHE INTERNAL "Result of CHECK_KQUEUE" FORCE) + endif(KQUEUE_PROTOTYPE_EXISTS) + endif("${VARIABLE}" MATCHES "^${VARIABLE}$") + endif(UNIX) +endmacro(CHECK_KQUEUE) \ No newline at end of file diff --git a/docpages/advanced_reference/thread_model.md b/docpages/advanced_reference/thread_model.md index d34dc100a8..70a42cf047 100644 --- a/docpages/advanced_reference/thread_model.md +++ b/docpages/advanced_reference/thread_model.md @@ -4,8 +4,6 @@ digraph "Thread Model" { graph [ranksep=1]; node [colorscheme="blues9",fontname="helvetica"]; - "Discord Events" -> "Your Program" - "Your Program" [style=filled, color=1, shape=rect] "Cluster" [style=filled, color=1, shape=rect] @@ -14,68 +12,48 @@ digraph "Thread Model" { color=lightgrey; node [style=filled,color=2] "Your Program" - "Cluster" - label = "User Code"; + "Your Program" -> "Cluster" + label = "(1)"; } - subgraph cluster_0 { + subgraph cluster_3 { style=filled; color=lightgrey; - node [style=filled,color=4] - "Shard 1" [style=filled, color=4] - "Shard 2" - "Shard 3..." - label = "Shards (Each is a thread, one per 2500 Discord guilds)"; + node [style=filled,color=2] + "Cluster" -> "Event Loop" + "Event Loop" -> "HTTP(S) requests" + "Event Loop" -> "Voice Sessions" + "Event Loop" -> "Shards" + "Shards" -> "Websocket Events" + label = "(2)"; } - subgraph cluster_1 { - style=filled - color=lightgrey; - node [style=filled,color=4] - "REST Requests" - "Request In Queue 1" - "Request In Queue 2" - "Request In Queue 3..." - "Request Out Queue" - label = "REST Requests (Each in queue, and the out queue, are threads)" - } - subgraph cluster_3 { - style=filled + subgraph cluster_0 { + style=filled; color=lightgrey; node [style=filled,color=4] - "Discord Events" [style=filled,color=4] - "User Callback Functions" - label = "Events and Callbacks" + "Voice Sessions" -> "Websocket Events" + "HTTP(S) requests" -> "Thread Pool (4..n threads)" + "Websocket Events" -> "Thread Pool (4..n threads)" + "Thread Pool (4..n threads)" + label = "(3)"; } "Cluster" [shape=rect] - "REST Requests" [shape=rect] - "Request In Queue 1" [shape=rect] - "Request In Queue 2" [shape=rect] - "Request In Queue 3..." [shape=rect] - "Shard 1" [shape=rect] - "Shard 2" [shape=rect] - "Shard 3..." [shape=rect] - "Request Out Queue" [shape=rect] - "Discord Events" [shape=rect] - "User Callback Functions" [shape=rect] + "Thread Pool (4..n threads)" [shape=rect] + "HTTP(S) requests" [shape=rect] + "Shards" [shape=rect] + "Websocket Events" [shape=rect] + "Event Loop" [shape=rect] + "Voice Sessions" [shape=rect] - "Cluster" -> "REST Requests" - "Shard 1" -> "Discord Events" - "Shard 2" -> "Discord Events" - "Shard 3..." -> "Discord Events" - "Your Program" -> "Cluster" - "Cluster" -> "Shard 1" - "Cluster" -> "Shard 2" - "Cluster" -> "Shard 3..." - "REST Requests" -> "Request In Queue 1" - "REST Requests" -> "Request In Queue 2" - "REST Requests" -> "Request In Queue 3..." - "Request In Queue 1" -> "Request Out Queue" - "Request In Queue 2" -> "Request Out Queue" - "Request In Queue 3..." -> "Request Out Queue" - "Request Out Queue" -> "User Callback Functions" - "User Callback Functions" -> "Your Program" } \enddot + +## Details + +1. User Code - No assumptions are made about how your program threads, if at all. +2. The event loop manages all socket IO for the cluster. If you start the cluster with dpp::st_return this will run under its own thread, otherwise if you use dpp::st_wait it will run in the same thread as the caller of the dpp::cluster::start method. +The event loop will be either poll, epoll or kqueue based depending on your system capabilities. You should always start a cluster after forking, if your program forks, as various types of IO loop cannot be inherited by a forked process. +3. Set thread pool size via cluster constructor. Thread pool uses a priority queue and defaults in size to half the system concurrency value. Every callback or completed coroutine ends up executing here. The minimum concurrency of this pool is 4. diff --git a/docpages/example_code/coro_awaiting_events.cpp b/docpages/example_code/coro_awaiting_events.cpp index f5be22b681..469d1bcf94 100644 --- a/docpages/example_code/coro_awaiting_events.cpp +++ b/docpages/example_code/coro_awaiting_events.cpp @@ -20,7 +20,7 @@ int main() { ); co_await event.co_reply(m); - dpp::button_click_t click_event = co_await event.from->creator->on_button_click.when( + dpp::button_click_t click_event = co_await event.owner->on_button_click.when( // Note!! Due to a bug in g++11 and g++12, id must be captured as a reference here or the compiler will destroy it twice. This is fixed in g++13 [&id] (dpp::button_click_t const &b) { return b.custom_id == id; diff --git a/docpages/example_code/coro_expiring_buttons.cpp b/docpages/example_code/coro_expiring_buttons.cpp index ed58bdadad..4b5f585d59 100644 --- a/docpages/example_code/coro_expiring_buttons.cpp +++ b/docpages/example_code/coro_expiring_buttons.cpp @@ -21,10 +21,10 @@ int main() { co_await event.co_reply(m); auto result = co_await dpp::when_any{ // Whichever completes first... - event.from->creator->on_button_click.when([&id](const dpp::button_click_t &b) { // Button clicked + event.owner->on_button_click.when([&id](const dpp::button_click_t &b) { // Button clicked return b.custom_id == id; }), - event.from->creator->co_sleep(5) // Or sleep 5 seconds + event.owner->co_sleep(5) // Or sleep 5 seconds }; // Note!! Due to a bug in g++11 and g++12, id must be captured as a reference above or the compiler will destroy it twice. This is fixed in g++13 if (result.index() == 0) { // Awaitable #0 completed first, that is the button click event diff --git a/docpages/example_code/coro_intro.cpp b/docpages/example_code/coro_intro.cpp index c48128547e..0018943b67 100644 --- a/docpages/example_code/coro_intro.cpp +++ b/docpages/example_code/coro_intro.cpp @@ -9,7 +9,7 @@ int main() { bot.on_slashcommand([](const dpp::slashcommand_t& event) -> dpp::task { if (event.command.get_command_name() == "file") { /* Request the image from the URL specified and co_await the response */ - dpp::http_request_completion_t result = co_await event.from->creator->co_request("https://dpp.dev/DPP-Logo.png", dpp::m_get); + dpp::http_request_completion_t result = co_await event.owner->co_request("https://dpp.dev/DPP-Logo.png", dpp::m_get); /* Create a message and attach the image on success */ dpp::message msg(event.command.channel_id, "This is my new attachment:"); diff --git a/docpages/example_code/coro_simple_commands1.cpp b/docpages/example_code/coro_simple_commands1.cpp index 09891ea125..0891684131 100644 --- a/docpages/example_code/coro_simple_commands1.cpp +++ b/docpages/example_code/coro_simple_commands1.cpp @@ -7,7 +7,7 @@ int main() { bot.on_slashcommand([](const dpp::slashcommand_t& event) -> dpp::task { if (event.command.get_command_name() == "addemoji") { - dpp::cluster *cluster = event.from->creator; + dpp::cluster *cluster = event.owner; // Retrieve parameter values dpp::snowflake file_id = std::get(event.get_parameter("file")); std::string emoji_name = std::get(event.get_parameter("name")); diff --git a/docpages/example_code/coro_simple_commands2.cpp b/docpages/example_code/coro_simple_commands2.cpp index ac4683eb45..40a46e4e5f 100644 --- a/docpages/example_code/coro_simple_commands2.cpp +++ b/docpages/example_code/coro_simple_commands2.cpp @@ -32,7 +32,7 @@ int main() { } } // Finally if everything else failed, request API - dpp::confirmation_callback_t confirmation = co_await event.from->creator->co_guild_get_member(event.command.guild_id, user_id); + dpp::confirmation_callback_t confirmation = co_await event.owner->co_guild_get_member(event.command.guild_id, user_id); if (confirmation.is_error()) { co_return std::nullopt; // Member not found, return empty } else { @@ -53,7 +53,7 @@ int main() { std::string avatar_url = member->get_avatar_url(512); if (avatar_url.empty()) { // Member does not have a custom avatar for this server, get their user avatar - dpp::confirmation_callback_t confirmation = co_await event.from->creator->co_user_get_cached(member->user_id); + dpp::confirmation_callback_t confirmation = co_await event.owner->co_user_get_cached(member->user_id); if (confirmation.is_error()) { // Wait for the thinking response to arrive to make sure we can edit co_await thinking; diff --git a/docpages/example_code/join_voice.cpp b/docpages/example_code/join_voice.cpp index 6dc771f9ae..18dd5926c1 100644 --- a/docpages/example_code/join_voice.cpp +++ b/docpages/example_code/join_voice.cpp @@ -18,7 +18,7 @@ int main() { dpp::guild* g = dpp::find_guild(event.command.guild_id); /* Get the voice channel that the bot is currently in from this server (will return nullptr if we're not in a voice channel!) */ - auto current_vc = event.from->get_voice(event.command.guild_id); + auto current_vc = event.from()->get_voice(event.command.guild_id); bool join_vc = true; @@ -38,7 +38,7 @@ int main() { /* We are on a different voice channel. We should leave it, then join the new one * by falling through to the join_vc branch below. */ - event.from->disconnect_voice(event.command.guild_id); + event.from()->disconnect_voice(event.command.guild_id); join_vc = true; } diff --git a/docpages/example_code/mp3.cpp b/docpages/example_code/mp3.cpp index 83cc555116..1daf9e5d8c 100644 --- a/docpages/example_code/mp3.cpp +++ b/docpages/example_code/mp3.cpp @@ -75,7 +75,7 @@ int main() { event.reply("Joined your channel!"); } else if (event.command.get_command_name() == "mp3") { /* Get the voice channel the bot is in, in this current guild. */ - dpp::voiceconn* v = event.from->get_voice(event.command.guild_id); + dpp::voiceconn* v = event.from()->get_voice(event.command.guild_id); /* If the voice channel was invalid, or there is an issue with it, then tell the user. */ if (!v || !v->voiceclient || !v->voiceclient->is_ready()) { diff --git a/docpages/example_code/oggopus.cpp b/docpages/example_code/oggopus.cpp index 5c5d3e8952..7cfa65ae41 100644 --- a/docpages/example_code/oggopus.cpp +++ b/docpages/example_code/oggopus.cpp @@ -39,7 +39,7 @@ int main(int argc, char const *argv[]) { } else if (event.command.get_command_name() == "play") { /* Get the voice channel the bot is in, in this current guild. */ - dpp::voiceconn* v = event.from->get_voice(event.command.guild_id); + dpp::voiceconn* v = event.from()->get_voice(event.command.guild_id); /* If the voice channel was invalid, or there is an issue with it, then tell the user. */ if (!v || !v->voiceclient || !v->voiceclient->is_ready()) { diff --git a/docpages/example_code/oggopus2.cpp b/docpages/example_code/oggopus2.cpp index 46956a553c..ea969c9bf9 100644 --- a/docpages/example_code/oggopus2.cpp +++ b/docpages/example_code/oggopus2.cpp @@ -37,7 +37,7 @@ int main() { event.reply("Joined your channel!"); } else if (event.command.get_command_name() == "play") { /* Get the voice channel the bot is in, in this current guild. */ - dpp::voiceconn* v = event.from->get_voice(event.command.guild_id); + dpp::voiceconn* v = event.from()->get_voice(event.command.guild_id); /* If the voice channel was invalid, or there is an issue with it, then tell the user. */ if (!v || !v->voiceclient || !v->voiceclient->is_ready()) { diff --git a/docpages/example_code/record_user.cpp b/docpages/example_code/record_user.cpp index 9b98af56c7..7de36710f3 100644 --- a/docpages/example_code/record_user.cpp +++ b/docpages/example_code/record_user.cpp @@ -36,7 +36,7 @@ int main() { /* Tell the user we joined their channel. */ event.reply("Joined your channel, now recording!"); } else if (event.command.get_command_name() == "stop") { - event.from->disconnect_voice(event.command.guild_id); + event.from()->disconnect_voice(event.command.guild_id); fclose(fd); event.reply("Stopped recording."); diff --git a/docpages/example_code/soundboard.cpp b/docpages/example_code/soundboard.cpp index 77602afd53..457a44a9f7 100644 --- a/docpages/example_code/soundboard.cpp +++ b/docpages/example_code/soundboard.cpp @@ -47,7 +47,7 @@ int main() { event.reply("Joined your channel!"); } else if (event.command.get_command_name() == "robot") { /* Get the voice channel the bot is in, in this current guild. */ - dpp::voiceconn* v = event.from->get_voice(event.command.guild_id); + dpp::voiceconn* v = event.from()->get_voice(event.command.guild_id); /* If the voice channel was invalid, or there is an issue with it, then tell the user. */ if (!v || !v->voiceclient || !v->voiceclient->is_ready()) { diff --git a/docpages/example_code/webhooks.cpp b/docpages/example_code/webhooks.cpp index ed9cbb840e..acecd7d5ae 100644 --- a/docpages/example_code/webhooks.cpp +++ b/docpages/example_code/webhooks.cpp @@ -1,16 +1,26 @@ #include +#include +#include int main() { - dpp::cluster bot(""); /* Normally, you put your bot token in here, but its not required for webhooks. */ + /* If you just want to fire webhooks, you can instantiate a cluster with no token */ + dpp::cluster bot; - bot.on_log(dpp::utility::cout_logger()); + /* Start the cluster in its own thread */ + bot.start(dpp::st_return); /* Construct a webhook object using the URL you got from Discord */ dpp::webhook wh("https://discord.com/api/webhooks/833047646548133537/ntCHEYYIoHSLy_GOxPx6pmM0sUoLbP101ct-WI6F-S4beAV2vaIcl_Id5loAMyQwxqhE"); - /* Send a message with this webhook */ - bot.execute_webhook_sync(wh, dpp::message("Have a great time here :smile:")); + /* Send a message with this webhook asynchronously */ + bot.execute_webhook(wh, dpp::message("Have a great time here :smile:")); + /* Wait here for the webhook to complete, but we could do anything we need here */ + while (bot.active_requests() > 0) { + std::this_thread::sleep_for(std::chrono::seconds(1)); + } + + /* When we return here, the cluster will be terminated */ return 0; } diff --git a/docpages/example_programs/the_basics/webhooks.md b/docpages/example_programs/the_basics/webhooks.md index e121c2169e..abee1a63b1 100644 --- a/docpages/example_programs/the_basics/webhooks.md +++ b/docpages/example_programs/the_basics/webhooks.md @@ -1,9 +1,12 @@ \page webhooks Webhooks -Webhooks are a simple way to post messages from other apps and websites into Discord. They allow getting automated messages and data updates sent to a text channel in your server. [Read more](https://support.discord.com/hc/en-us/articles/228383668) in this article about Webhooks. +Webhooks are a simple way to post messages from other apps and websites into Discord. +They allow getting automated messages and data updates sent to a text channel in your server. [Read more](https://support.discord.com/hc/en-us/articles/228383668) in this article about Webhooks. The following code shows how to send messages in a channel using a webhook. \include{cpp} webhooks.cpp -The above is just a very simple example. You can also send embed messages. All you have to do is to add an embed to the message you want to send. If you want to, you can also send it into a thread. +\note For just sending a webhook, the example above is overkill. If you are here because you searched for 'how to send a Discord webhook in C++', you'll quickly learn that D++ can do much more than just send webhooks! The +above is just a very simple example. You can also send embed messages. All you have to do is to add an embed to the message you want to send. If you want to, you can also send it into a thread. For further examples, check +the rest of the site. diff --git a/include/dpp/cluster.h b/include/dpp/cluster.h index 179be8f434..144b64b0c5 100644 --- a/include/dpp/cluster.h +++ b/include/dpp/cluster.h @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -40,7 +41,6 @@ #include #include #include -#include #include #include #include @@ -48,9 +48,18 @@ #include #include #include +#include namespace dpp { +/** + * @brief Pass this value into the constructor of dpp::cluster for the shard count to create a cluster with no shards. + * A cluster with no shards does not connect to a websocket, but can still use the event loop to dispatch HTTPS API + * requests to Discord. This is useful for bots that do not need to receive websocket events as it will save a lot of + * resources. + */ +constexpr uint32_t NO_SHARDS = ~0U; + /** * @brief Types of startup for cluster::start() */ @@ -124,12 +133,17 @@ class DPP_EXPORT cluster { shard_list shards; /** - * @brief List of all active registered timers + * @brief List of shards waiting for reconnection + */ + reconnect_list reconnections; + + /** + * @brief Ephemeral list of deleted timer ids */ - timer_reg_t timer_list; + timers_deleted_t deleted_timers; /** - * @brief List of timers by time + * @brief Priority queue of of timers by time */ timer_next_t next_timer; @@ -164,18 +178,30 @@ class DPP_EXPORT cluster { */ std::map named_commands; #endif + /** + * @brief Thread pool + */ + std::unique_ptr pool{nullptr}; /** - * @brief Tick active timers + * @brief Used to spawn the socket engine into its own thread if + * the cluster is started with dpp::st_return. It is unused otherwise. */ - void tick_timers(); + std::thread engine_thread; /** - * @brief Reschedule a timer for its next tick - * - * @param t Timer to reschedule + * @brief Protection mutex for timers + */ + std::mutex timer_guard; + + /** + * @brief Mark a shard as requiring reconnection. + * Destructs the old shard in 5 seconds and creates a new one attempting to resume. + * + * @param shard_id Shard ID */ - void timer_reschedule(timer_t* t); + void add_reconnect(uint32_t shard_id); + public: /** * @brief Current bot token for all shards on this cluster and all commands sent via HTTP @@ -230,14 +256,33 @@ class DPP_EXPORT cluster { websocket_protocol_t ws_mode; /** - * @brief Condition variable notified when the cluster is terminating. + * @brief Atomic bool to set to true when the cluster is terminating. + * + * D++ itself does not set this value, it is for library users to set if they want + * the cluster to terminate outside of a flow where they may have simple access to + * destruct the cluster object. */ - std::condition_variable terminating; + std::atomic_bool terminating{false}; /** * @brief The time (in seconds) that a request is allowed to take. */ - uint16_t request_timeout = 20; + uint16_t request_timeout = 60; + + /** + * @brief Socket engine instance + */ + std::unique_ptr socketengine; + + /** + * @brief Constructor for creating a cluster without a token. + * A cluster created without a token has no shards, and just runs the event loop. You can use this to make asynchronous + * HTTP requests via e.g. dpp::cluster::request without having to connect to a websocket to receive shard events. + * @param pool_threads The number of threads to allocate for the thread pool. This defaults to half your system concurrency and if set to a number less than 4, will default to 4. + * All callbacks and events are placed into the thread pool. The bigger you make this pool (but generally no bigger than your number of cores), the more your bot will scale. + * @throw dpp::exception Thrown on windows, if WinSock fails to initialise, or on any other system if a dpp::request_queue fails to construct + */ + explicit cluster(uint32_t pool_threads = std::thread::hardware_concurrency() / 2); /** * @brief Constructor for creating a cluster. All but the token are optional. @@ -249,11 +294,23 @@ class DPP_EXPORT cluster { * @param maxclusters The total number of clusters that are active, which may be on separate processes or even separate machines. * @param compressed Whether or not to use compression for shards on this cluster. Saves a ton of bandwidth at the cost of some CPU * @param policy Set the caching policy for the cluster, either lazy (only cache users/members when they message the bot) or aggressive (request whole member lists on seeing new guilds too) - * @param request_threads The number of threads to allocate for making HTTP requests to Discord. This defaults to 12. You can increase this at runtime via the object returned from get_rest(). - * @param request_threads_raw The number of threads to allocate for making HTTP requests to sites outside of Discord. This defaults to 1. You can increase this at runtime via the object returned from get_raw_rest(). + * @param pool_threads The number of threads to allocate for the thread pool. This defaults to half your system concurrency and if set to a number less than 4, will default to 4. + * All callbacks and events are placed into the thread pool. The bigger you make this pool (but generally no bigger than your number of cores), the more your bot will scale. * @throw dpp::exception Thrown on windows, if WinSock fails to initialise, or on any other system if a dpp::request_queue fails to construct */ - cluster(const std::string& token, uint32_t intents = i_default_intents, uint32_t shards = 0, uint32_t cluster_id = 0, uint32_t maxclusters = 1, bool compressed = true, cache_policy_t policy = cache_policy::cpol_default, uint32_t request_threads = 12, uint32_t request_threads_raw = 1); + cluster(const std::string& token, uint32_t intents = i_default_intents, uint32_t shards = 0, uint32_t cluster_id = 0, uint32_t maxclusters = 1, bool compressed = true, cache_policy_t policy = cache_policy::cpol_default, uint32_t pool_threads = std::thread::hardware_concurrency() / 2); + + /** + * @brief Place some arbitrary work into the thread pool for execution when time permits. + * + * Work units are fetched into threads on the thread pool from the queue in order of priority, + * lowest numeric values first. Low numeric values should be reserved for API replies from Discord, + * guild creation events, etc. + * + * @param priority Priority of the work unit + * @param task Task to queue + */ + void queue_work(int priority, work_unit task); /** * @brief dpp::cluster is non-copyable @@ -310,6 +367,11 @@ class DPP_EXPORT cluster { */ cluster& set_websocket_protocol(websocket_protocol_t mode); + /** + * @brief Tick active timers + */ + void tick_timers(); + /** * @brief Set the audit log reason for the next REST call to be made. * This is set per-thread, so you must ensure that if you call this method, your request that @@ -433,7 +495,7 @@ class DPP_EXPORT cluster { * * @param return_after If true the bot will return to your program after starting shards, if false this function will never return. */ - void start(bool return_after = true); + void start(start_type return_after = st_wait); /** * @brief Set the presence for all shards on the cluster @@ -479,6 +541,14 @@ class DPP_EXPORT cluster { */ bool register_command(const std::string& name, const slashcommand_handler_t handler); + /** + * @brief Get the number of currently active HTTP(S) requests active in the cluster. + * This total includes all in-flight API requests and calls to dpp::cluster::request(). + * Note that once a request is passed to the thread pool it is no longer counted here. + * @return Total active request count + */ + size_t active_requests(); + #ifdef DPP_CORO /** * @brief Register a coroutine-based slash command handler. @@ -1487,9 +1557,8 @@ class DPP_EXPORT cluster { * @param mimetype MIME type of POST data * @param headers Headers to send with the request * @param protocol HTTP protocol to use (1.1 and 1.0 are supported) - * @param request_timeout How many seconds before the connection is considered failed if not finished */ - void request(const std::string &url, http_method method, http_completion_event callback, const std::string &postdata = "", const std::string &mimetype = "text/plain", const std::multimap &headers = {}, const std::string &protocol = "1.1", time_t request_timeout = 5); + void request(const std::string &url, http_method method, http_completion_event callback, const std::string &postdata = "", const std::string &mimetype = "text/plain", const std::multimap &headers = {}, const std::string &protocol = "1.1"); /** * @brief Respond to a slash command @@ -3975,9 +4044,8 @@ class DPP_EXPORT cluster { */ void channel_set_voice_status(snowflake channel_id, const std::string& status, command_completion_event_t callback = utility::log_error()); -#include #ifdef DPP_CORO -#include + #include #endif }; diff --git a/include/dpp/cluster_coro_calls.h b/include/dpp/cluster_coro_calls.h index 9077e9efa6..f7d44dcf32 100644 --- a/include/dpp/cluster_coro_calls.h +++ b/include/dpp/cluster_coro_calls.h @@ -450,8 +450,8 @@ * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. * @param c Channel to set permissions for * @param overwrite_id Overwrite to change (a user or role ID) - * @param allow allow permissions bitmask - * @param deny deny permissions bitmask + * @param allow Bitmask of allowed permissions (refer to enum dpp::permissions) + * @param deny Bitmask of denied permissions (refer to enum dpp::permissions) * @param member true if the overwrite_id is a user id, false if it is a channel id * @return confirmation returned object on completion * \memberof dpp::cluster @@ -466,8 +466,8 @@ * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. * @param channel_id ID of the channel to set permissions for * @param overwrite_id Overwrite to change (a user or role ID) - * @param allow allow permissions bitmask - * @param deny deny permissions bitmask + * @param allow Bitmask of allowed permissions (refer to enum dpp::permissions) + * @param deny Bitmask of denied permissions (refer to enum dpp::permissions) * @param member true if the overwrite_id is a user id, false if it is a channel id * @return confirmation returned object on completion * \memberof dpp::cluster @@ -2648,5 +2648,5 @@ /* End of auto-generated definitions */ -[[nodiscard]] async co_request(const std::string &url, http_method method, const std::string &postdata = "", const std::string &mimetype = "text/plain", const std::multimap &headers = {}, const std::string &protocol = "1.1", time_t request_timeout = 5); +[[nodiscard]] async co_request(const std::string &url, http_method method, const std::string &postdata = "", const std::string &mimetype = "text/plain", const std::multimap &headers = {}, const std::string &protocol = "1.1"); diff --git a/include/dpp/cluster_sync_calls.h b/include/dpp/cluster_sync_calls.h deleted file mode 100644 index 461736b215..0000000000 --- a/include/dpp/cluster_sync_calls.h +++ /dev/null @@ -1,3466 +0,0 @@ -/************************************************************************************ - * - * D++, A Lightweight C++ library for Discord - * - * Copyright 2022 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. - * - ************************************************************************************/ - - -/* Auto @generated by buildtools/make_sync_struct.php. - * - * DO NOT EDIT BY HAND! - * - * To re-generate this header file re-run the script! - */ -/** - * @brief Create/overwrite global slash commands. - * Any existing global slash commands will be deleted and replaced with these. - * - * @see dpp::cluster::global_bulk_command_create - * @see https://discord.com/developers/docs/interactions/application-commands#bulk-overwrite-global-application-commands - * @param commands Vector of slash commands to create/update. - * overwriting existing commands that are registered globally for this application. - * Commands that do not already exist will count toward daily application command create limits. - * @return slashcommand_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") slashcommand_map global_bulk_command_create_sync(const std::vector &commands); - -/** - * @brief Delete all existing global slash commands. - * - * @see dpp::cluster::global_bulk_command_delete - * @see https://discord.com/developers/docs/interactions/application-commands#bulk-overwrite-global-application-commands - * @return slashcommand_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") slashcommand_map global_bulk_command_delete_sync(); - -/** - * @brief Create a global slash command (a bot can have a maximum of 100 of these). - * - * @see dpp::cluster::global_command_create - * @see https://discord.com/developers/docs/interactions/application-commands#create-global-application-command - * @param s Slash command to create - * @return slashcommand returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") slashcommand global_command_create_sync(const slashcommand &s); - -/** - * @brief Get a global slash command - * - * @see dpp::cluster::global_command_get - * @see https://discord.com/developers/docs/interactions/application-commands#get-global-application-command - * @param id The ID of the slash command - * @return slashcommand returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") slashcommand global_command_get_sync(snowflake id); - -/** - * @brief Delete a global slash command (a bot can have a maximum of 100 of these) - * - * @see dpp::cluster::global_command_delete - * @see https://discord.com/developers/docs/interactions/application-commands#delete-global-application-command - * @param id Slash command to delete - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation global_command_delete_sync(snowflake id); - -/** - * @brief Edit a global slash command (a bot can have a maximum of 100 of these) - * - * @see dpp::cluster::global_command_edit - * @see https://discord.com/developers/docs/interactions/application-commands#edit-global-application-command - * @param s Slash command to change - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation global_command_edit_sync(const slashcommand &s); - -/** - * @brief Get the application's global slash commands - * - * @see dpp::cluster::global_commands_get - * @see https://discord.com/developers/docs/interactions/application-commands#get-global-application-commands - * @return slashcommand_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") slashcommand_map global_commands_get_sync(); - -/** - * @brief Create/overwrite guild slash commands. - * Any existing guild slash commands on this guild will be deleted and replaced with these. - * - * @see dpp::cluster::guild_bulk_command_create - * @see https://discord.com/developers/docs/interactions/application-commands#bulk-overwrite-guild-application-commands - * @param commands Vector of slash commands to create/update. - * New guild commands will be available in the guild immediately. If the command did not already exist, it will count toward daily application command create limits. - * @param guild_id Guild ID to create/update the slash commands in - * @return slashcommand_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") slashcommand_map guild_bulk_command_create_sync(const std::vector &commands, snowflake guild_id); - -/** - * @brief Delete all existing guild slash commands. - * - * @see dpp::cluster::guild_bulk_command_delete - * @see https://discord.com/developers/docs/interactions/application-commands#bulk-overwrite-global-application-commands - * @param guild_id Guild ID to delete the slash commands in. - * @return slashcommand_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") slashcommand_map guild_bulk_command_delete_sync(snowflake guild_id); - -/** - * @brief Get all slash command permissions of a guild - * - * @see dpp::cluster::guild_commands_get_permissions - * @see https://discord.com/developers/docs/interactions/application-commands#get-application-command-permissions - * @param guild_id Guild ID to get the slash commands permissions for - * @return guild_command_permissions_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") guild_command_permissions_map guild_commands_get_permissions_sync(snowflake guild_id); - -/** - * @brief Edit/Overwrite the permissions of all existing slash commands in a guild - * - * @note You can only add up to 10 permission overwrites for a command - * - * @see dpp::cluster::guild_bulk_command_edit_permissions - * @see https://discord.com/developers/docs/interactions/application-commands#batch-edit-application-command-permissions - * @warning The endpoint will overwrite all existing permissions for all commands of the application in a guild, including slash commands, user commands, and message commands. Meaning that if you forgot to pass a slash command, the permissions of it might be removed. - * @param commands A vector of slash commands to edit/overwrite the permissions for - * @param guild_id Guild ID to edit permissions of the slash commands in - * @return guild_command_permissions_map returned object on completion - * @deprecated This has been disabled with updates to Permissions v2. You can use guild_command_edit_permissions instead - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") guild_command_permissions_map guild_bulk_command_edit_permissions_sync(const std::vector &commands, snowflake guild_id); - -/** - * @brief Create a slash command local to a guild - * - * @see dpp::cluster::guild_command_create - * @see https://discord.com/developers/docs/interactions/application-commands#create-guild-application-command - * @note Creating a command with the same name as an existing command for your application will overwrite the old command. - * @param s Slash command to create - * @param guild_id Guild ID to create the slash command in - * @return slashcommand returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") slashcommand guild_command_create_sync(const slashcommand &s, snowflake guild_id); - -/** - * @brief Delete a slash command local to a guild - * - * @see dpp::cluster::guild_command_delete - * @see https://discord.com/developers/docs/interactions/application-commands#delete-guild-application-command - * @param id Slash command to delete - * @param guild_id Guild ID to delete the slash command in - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation guild_command_delete_sync(snowflake id, snowflake guild_id); - -/** - * @brief Edit slash command permissions of a guild - * - * @see dpp::cluster::guild_command_edit_permissions - * @see https://discord.com/developers/docs/interactions/application-commands#edit-application-command-permissions - * @note You can only add up to 10 permission overwrites for a command - * @param s Slash command to edit the permissions for - * @param guild_id Guild ID to edit the slash command in - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation guild_command_edit_permissions_sync(const slashcommand &s, snowflake guild_id); - -/** - * @brief Get a slash command of a guild - * - * @see dpp::cluster::guild_command_get - * @see https://discord.com/developers/docs/interactions/application-commands#get-guild-application-command - * @note The returned slash commands will not have permissions set, you need to use a permissions getter e.g. dpp::guild_commands_get_permissions to get the guild command permissions - * @param id The ID of the slash command - * @param guild_id Guild ID to get the slash command from - * @return slashcommand returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") slashcommand guild_command_get_sync(snowflake id, snowflake guild_id); - -/** - * @brief Get the permissions for a slash command of a guild - * - * @see dpp::cluster::guild_command_get_permissions - * @see https://discord.com/developers/docs/interactions/application-commands#get-application-command-permissions - * @param id The ID of the slash command to get the permissions for - * @param guild_id Guild ID to get the permissions of - * @return guild_command_permissions returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") guild_command_permissions guild_command_get_permissions_sync(snowflake id, snowflake guild_id); - -/** - * @brief Edit a slash command local to a guild - * - * @see dpp::cluster::guild_command_edit - * @see https://discord.com/developers/docs/interactions/application-commands#edit-guild-application-command - * @param s Slash command to edit - * @param guild_id Guild ID to edit the slash command in - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation guild_command_edit_sync(const slashcommand &s, snowflake guild_id); - -/** - * @brief Get the application's slash commands for a guild - * - * @see dpp::cluster::guild_commands_get - * @see https://discord.com/developers/docs/interactions/application-commands#get-guild-application-commands - * @note The returned slash commands will not have permissions set, you need to use a permissions getter e.g. dpp::guild_commands_get_permissions to get the guild command permissions - * @param guild_id Guild ID to get the slash commands for - * @return slashcommand_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") slashcommand_map guild_commands_get_sync(snowflake guild_id); - -/** - * @brief Respond to a slash command - * - * @see dpp::cluster::interaction_response_create - * @see https://discord.com/developers/docs/interactions/receiving-and-responding#create-interaction-response - * @param interaction_id Interaction id to respond to - * @param token Token for the interaction webhook - * @param r Response to send - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation interaction_response_create_sync(snowflake interaction_id, const std::string &token, const interaction_response &r); - -/** - * @brief Edit response to a slash command - * - * @see dpp::cluster::interaction_response_edit - * @see https://discord.com/developers/docs/interactions/receiving-and-responding#edit-original-interaction-response - * @param token Token for the interaction webhook - * @param m Message to send - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation interaction_response_edit_sync(const std::string &token, const message &m); - -/** - * @brief Get the original response to a slash command - * - * @see dpp::cluster::interaction_response_get_original - * @see https://discord.com/developers/docs/interactions/receiving-and-responding#get-original-interaction-response - * @param token Token for the interaction webhook - * @return message returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") message interaction_response_get_original_sync(const std::string &token); - -/** - * @brief Create a followup message to a slash command - * - * @see dpp::cluster::interaction_followup_create - * @see https://discord.com/developers/docs/interactions/receiving-and-responding#create-interaction-response - * @param token Token for the interaction webhook - * @param m followup message to create - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation interaction_followup_create_sync(const std::string &token, const message &m); - -/** - * @brief Edit original followup message to a slash command - * This is an alias for cluster::interaction_response_edit - * @see dpp::cluster::interaction_followup_edit_original - * @see cluster::interaction_response_edit - * - * @param token Token for the interaction webhook - * @param m message to edit, the ID should be set - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation interaction_followup_edit_original_sync(const std::string &token, const message &m); - -/** - * @brief Delete the initial interaction response - * - * @see dpp::cluster::interaction_followup_delete - * @see https://discord.com/developers/docs/interactions/receiving-and-responding#delete-original-interaction-response - * @param token Token for the interaction webhook - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation interaction_followup_delete_sync(const std::string &token); - -/** - * @brief Edit followup message to a slash command - * The message ID in the message you pass should be correctly set to that of a followup message you previously sent - * - * @see dpp::cluster::interaction_followup_edit - * @see https://discord.com/developers/docs/interactions/receiving-and-responding#edit-followup-message - * @param token Token for the interaction webhook - * @param m message to edit, the ID should be set - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation interaction_followup_edit_sync(const std::string &token, const message &m); - -/** - * @brief Get the followup message to a slash command - * - * @see dpp::cluster::interaction_followup_get - * @see https://discord.com/developers/docs/interactions/receiving-and-responding#get-followup-message - * @param token Token for the interaction webhook - * @param message_id message to retrieve - * @return message returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") message interaction_followup_get_sync(const std::string &token, snowflake message_id); - -/** - * @brief Get the original followup message to a slash command - * This is an alias for cluster::interaction_response_get_original - * @see dpp::cluster::interaction_followup_get_original - * @see cluster::interaction_response_get_original - * - * @param token Token for the interaction webhook - * @return message returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") message interaction_followup_get_original_sync(const std::string &token); - -/** - * @brief Get all auto moderation rules for a guild - * - * @param guild_id Guild id of the auto moderation rule - * @return automod_rule_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") automod_rule_map automod_rules_get_sync(snowflake guild_id); - -/** - * @brief Get a single auto moderation rule - * - * @param guild_id Guild id of the auto moderation rule - * @param rule_id Rule id to retrieve - * @return automod_rule returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") automod_rule automod_rule_get_sync(snowflake guild_id, snowflake rule_id); - -/** - * @brief Create an auto moderation rule - * - * @param guild_id Guild id of the auto moderation rule - * @param r Auto moderation rule to create - * @return automod_rule returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") automod_rule automod_rule_create_sync(snowflake guild_id, const automod_rule& r); - -/** - * @brief Edit an auto moderation rule - * - * @param guild_id Guild id of the auto moderation rule - * @param r Auto moderation rule to edit. The rule's id must be set. - * @return automod_rule returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") automod_rule automod_rule_edit_sync(snowflake guild_id, const automod_rule& r); - -/** - * @brief Delete an auto moderation rule - * - * @param guild_id Guild id of the auto moderation rule - * @param rule_id Auto moderation rule id to delete - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation automod_rule_delete_sync(snowflake guild_id, snowflake rule_id); - -/** - * @brief Create a channel - * - * Create a new channel object for the guild. Requires the `MANAGE_CHANNELS` permission. If setting permission overwrites, - * only permissions your bot has in the guild can be allowed/denied. Setting `MANAGE_ROLES` permission in channels is only possible - * for guild administrators. Returns the new channel object on success. Fires a `Channel Create Gateway` event. - * - * All parameters to this endpoint are optional excluding `name` - * - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * @see dpp::cluster::channel_create - * @see https://discord.com/developers/docs/resources/channel#create-channel - * @param c Channel to create - * @return channel returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") channel channel_create_sync(const class channel &c); - -/** - * @brief Remove a permission from a channel - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * @see dpp::cluster::channel_delete_permission - * @see https://discord.com/developers/docs/resources/channel#delete-channel-permission - * @param c Channel to remove permission from - * @param overwrite_id Overwrite to remove, user or channel ID - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation channel_delete_permission_sync(const class channel &c, snowflake overwrite_id); - -/** - * @brief Delete a channel - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * @see dpp::cluster::channel_delete - * @see https://discord.com/developers/docs/resources/channel#deleteclose-channel - * @param channel_id Channel id to delete - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation channel_delete_sync(snowflake channel_id); - -/** - * @brief Edit a channel's permissions - * - * @see dpp::cluster::channel_edit_permissions - * @see https://discord.com/developers/docs/resources/channel#edit-channel-permissions - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * @param c Channel to set permissions for - * @param overwrite_id Overwrite to change (a user or role ID) - * @param allow Bitmask of allowed permissions (refer to enum dpp::permissions) - * @param deny Bitmask of denied permissions (refer to enum dpp::permissions) - * @param member true if the overwrite_id is a user id, false if it is a channel id - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation channel_edit_permissions_sync(const class channel &c, const snowflake overwrite_id, const uint64_t allow, const uint64_t deny, const bool member); - -/** - * @brief Edit a channel's permissions - * - * @see dpp::cluster::channel_edit_permissions - * @see https://discord.com/developers/docs/resources/channel#edit-channel-permissions - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * @param channel_id ID of the channel to set permissions for - * @param overwrite_id Overwrite to change (a user or role ID) - * @param allow Bitmask of allowed permissions (refer to enum dpp::permissions) - * @param deny Bitmask of denied permissions (refer to enum dpp::permissions) - * @param member true if the overwrite_id is a user id, false if it is a channel id - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation channel_edit_permissions_sync(const snowflake channel_id, const snowflake overwrite_id, const uint64_t allow, const uint64_t deny, const bool member); - -/** - * @brief Edit multiple channels positions - * - * Modify the positions of a set of channel objects for the guild. - * Requires `MANAGE_CHANNELS` permission. Fires multiple `Channel Update Gateway` events. - * Only channels to be modified are required. - * - * @see dpp::cluster::channel_edit_positions - * @see https://discord.com/developers/docs/resources/guild#modify-guild-channel-positions - * @param c Channel to change the position for - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation channel_edit_positions_sync(const std::vector &c); - -/** - * @brief Edit a channel - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * @see dpp::cluster::channel_edit - * @see https://discord.com/developers/docs/resources/channel#modify-channel - * @param c Channel to edit/update - * @return channel returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") channel channel_edit_sync(const class channel &c); - -/** - * @brief Follow an announcement (news) channel - * @see dpp::cluster::channel_follow_news - * @see https://discord.com/developers/docs/resources/channel#follow-news-channel - * @param c Channel id to follow - * @param target_channel_id Channel to subscribe the channel to - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation channel_follow_news_sync(const class channel &c, snowflake target_channel_id); - -/** - * @brief Get a channel - * - * @see dpp::cluster::channel_get - * @see https://discord.com/developers/docs/resources/channel#get-channel - * @param c Channel ID to retrieve - * @return channel returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") channel channel_get_sync(snowflake c); - -/** - * @brief Create invite for a channel - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * @see dpp::cluster::channel_invite_create - * @see https://discord.com/developers/docs/resources/channel#create-channel-invite - * @param c Channel to create an invite on - * @param i Invite to create - * @return invite returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") invite channel_invite_create_sync(const class channel &c, const class invite &i); - -/** - * @brief Get invites for a channel - * - * @see dpp::cluster::channel_invites_get - * @see https://discord.com/developers/docs/resources/invite#get-invites - * @param c Channel to get invites for - * @return invite_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") invite_map channel_invites_get_sync(const class channel &c); - -/** - * @brief Trigger channel typing indicator - * @see dpp::cluster::channel_typing - * @see https://discord.com/developers/docs/resources/channel#trigger-typing-indicator - * @param c Channel to set as typing on - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation channel_typing_sync(const class channel &c); - -/** - * @brief Trigger channel typing indicator - * @see dpp::cluster::channel_typing - * @see https://discord.com/developers/docs/resources/channel#trigger-typing-indicator - * @param cid Channel ID to set as typing on - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation channel_typing_sync(snowflake cid); - -/** - * @brief Get all channels for a guild - * - * @see dpp::cluster::channels_get - * @see https://discord.com/developers/docs/resources/channel#get-channels - * @param guild_id Guild ID to retrieve channels for - * @return channel_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") channel_map channels_get_sync(snowflake guild_id); - -/** - * @brief Set the status of a voice channel. - * - * @see dpp::cluster::channel_set_voice_status - * @see https://github.com/discord/discord-api-docs/pull/6400 (please replace soon). - * @param channel_id The channel to update. - * @param status The new status for the channel. - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation channel_set_voice_status_sync(snowflake channel_id, const std::string& status); - -/** - * @brief Create a dm channel - * @see dpp::cluster::create_dm_channel - * @see https://discord.com/developers/docs/resources/user#create-dm - * @param user_id User ID to create DM channel with - * @return channel returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") channel create_dm_channel_sync(snowflake user_id); - -/** - * @brief Get current user DM channels - * - * @return channel_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") channel_map current_user_get_dms_sync(); - -/** - * @brief Create a direct message, also create the channel for the direct message if needed - * - * @see dpp::cluster::direct_message_create - * @see https://discord.com/developers/docs/resources/user#create-dm - * @see dpp::cluster::direct_message_create - * @see https://discord.com/developers/docs/resources/channel#create-message - * @param user_id User ID of user to send message to - * @param m Message object - * @return message returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") message direct_message_create_sync(snowflake user_id, const message &m); - -/** - * @brief Adds a recipient to a Group DM using their access token - * @see dpp::cluster::gdm_add - * @see https://discord.com/developers/docs/resources/channel#group-dm-add-recipient - * @param channel_id Channel id to add group DM recipients to - * @param user_id User ID to add - * @param access_token Access token from OAuth2 - * @param nick Nickname of user to apply to the chat - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation gdm_add_sync(snowflake channel_id, snowflake user_id, const std::string &access_token, const std::string &nick); - -/** - * @brief Removes a recipient from a Group DM - * @see dpp::cluster::gdm_remove - * @see https://discord.com/developers/docs/resources/channel#group-dm-remove-recipient - * @param channel_id Channel ID of group DM - * @param user_id User ID to remove from group DM - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation gdm_remove_sync(snowflake channel_id, snowflake user_id); - -/** - * @brief Create single emoji. - * You must ensure that the emoji passed contained image data using the emoji::load_image() method. - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * - * @see dpp::cluster::guild_emoji_create - * @see https://discord.com/developers/docs/resources/emoji#create-guild-emoji - * @param guild_id Guild ID to create emoji om - * @param newemoji Emoji to create - * @return emoji returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") emoji guild_emoji_create_sync(snowflake guild_id, const class emoji& newemoji); - -/** - * @brief Delete a guild emoji - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * - * @see dpp::cluster::guild_emoji_delete - * @see https://discord.com/developers/docs/resources/emoji#delete-guild-emoji - * @param guild_id Guild ID to delete emoji on - * @param emoji_id Emoji ID to delete - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation guild_emoji_delete_sync(snowflake guild_id, snowflake emoji_id); - -/** - * @brief Edit a single emoji. - * - * You must ensure that the emoji passed contained image data using the emoji::load_image() method. - * @see dpp::cluster::guild_emoji_edit - * @see https://discord.com/developers/docs/resources/emoji#modify-guild-emoji - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * @param guild_id Guild ID to edit emoji on - * @param newemoji Emoji to edit - * @return emoji returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") emoji guild_emoji_edit_sync(snowflake guild_id, const class emoji& newemoji); - -/** - * @brief Get a single emoji - * - * @see dpp::cluster::guild_emoji_get - * @see https://discord.com/developers/docs/resources/emoji#get-guild-emoji - * @param guild_id Guild ID to get emoji for - * @param emoji_id Emoji ID to get - * @return emoji returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") emoji guild_emoji_get_sync(snowflake guild_id, snowflake emoji_id); - -/** - * @brief Get all emojis for a guild - * - * @see dpp::cluster::guild_emojis_get - * @see https://discord.com/developers/docs/resources/emoji#list-guild-emojis - * @param guild_id Guild ID to get emojis for - * @return emoji_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") emoji_map guild_emojis_get_sync(snowflake guild_id); - -/** - * @brief List all Application Emojis - * - * @see dpp::cluster::application_emojis_get - * @see https://discord.com/developers/docs/resources/emoji#list-application-emojis - * @return emoji_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") emoji_map application_emojis_get_sync(); - -/** - * @brief Get an Application Emoji - * - * @see dpp::cluster::application_emoji_get - * @see https://discord.com/developers/docs/resources/emoji#get-application-emoji - * @param emoji_id The ID of the Emoji to get. - * @return emoji returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") emoji application_emoji_get_sync(snowflake emoji_id); - -/** - * @brief Create an Application Emoji - * - * @see dpp::cluster::application_emoji_create - * @see https://discord.com/developers/docs/resources/emoji#create-application-emoji - * @param newemoji The emoji to create - * @return emoji returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") emoji application_emoji_create_sync(const class emoji& newemoji); - -/** - * @brief Edit an Application Emoji - * - * @see dpp::cluster::application_emoji_edit - * @see https://discord.com/developers/docs/resources/emoji#modify-application-emoji - * @param newemoji The emoji to edit - * @return emoji returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") emoji application_emoji_edit_sync(const class emoji& newemoji); - -/** - * @brief Delete an Application Emoji - * - * @see dpp::cluster::application_emoji_delete - * @see https://discord.com/developers/docs/resources/emoji#delete-application-emoji - * @param emoji_id The emoji's ID to delete. - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation application_emoji_delete_sync(snowflake emoji_id); - -/** - * @brief Returns all entitlements for a given app, active and expired. - * - * @see dpp::cluster::entitlements_get - * @see https://discord.com/developers/docs/monetization/entitlements#list-entitlements - * @param user_id User ID to look up entitlements for. - * @param sku_ids List of SKU IDs to check entitlements for. - * @param before_id Retrieve entitlements before this entitlement ID. - * @param after_id Retrieve entitlements after this entitlement ID. - * @param limit Number of entitlements to return, 1-100 (default 100). - * @param guild_id Guild ID to look up entitlements for. - * @param exclude_ended Whether ended entitlements should be excluded from the search. - * @return entitlement_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") entitlement_map entitlements_get_sync(snowflake user_id = 0, const std::vector& sku_ids = {}, snowflake before_id = 0, snowflake after_id = 0, uint8_t limit = 100, snowflake guild_id = 0, bool exclude_ended = false); - -/** - * @brief Creates a test entitlement to a given SKU for a given guild or user. - * Discord will act as though that user or guild has entitlement to your premium offering. - * - * @see dpp::cluster::entitlement_test_create - * @see https://discord.com/developers/docs/monetization/entitlements#create-test-entitlement - * @param new_entitlement The entitlement to create. - * Make sure your dpp::entitlement_type (inside your dpp::entitlement object) matches the type of the owner_id - * (if type is guild, owner_id is a guild id), otherwise it won't work! - * @return entitlement returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") entitlement entitlement_test_create_sync(const class entitlement& new_entitlement); - -/** - * @brief Deletes a currently-active test entitlement. - * Discord will act as though that user or guild no longer has entitlement to your premium offering. - * - * @see dpp::cluster::entitlement_test_delete - * @see https://discord.com/developers/docs/monetization/entitlements#delete-test-entitlement - * @param entitlement_id The test entitlement to delete. - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation entitlement_test_delete_sync(snowflake entitlement_id); - -/** - * @brief For One-Time Purchase consumable SKUs, marks a given entitlement for the user as consumed. - * - * @see dpp::cluster::entitlement_consume - * @see https://discord.com/developers/docs/monetization/entitlements#consume-an-entitlement - * @param entitlement_id The entitlement to mark as consumed. - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation entitlement_consume_sync(snowflake entitlement_id); - -/** - * @brief Get the gateway information for the bot using the token - * @see dpp::cluster::get_gateway_bot - * @see https://discord.com/developers/docs/topics/gateway#get-gateway-bot - * @return gateway returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") gateway get_gateway_bot_sync(); - -/** - * @brief Modify current member - * - * Modifies the current member in a guild. - * Fires a `Guild Member Update` Gateway event. - * - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * @see dpp::cluster::guild_current_member_edit - * @see https://discord.com/developers/docs/resources/guild#modify-current-member - * @param guild_id Guild ID to change on - * @param nickname New nickname, or empty string to clear nickname - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation guild_current_member_edit_sync(snowflake guild_id, const std::string &nickname); - -/** - * @brief Get the audit log for a guild - * - * @see dpp::cluster::guild_auditlog_get - * @see https://discord.com/developers/docs/resources/audit-log#get-guild-audit-log - * @param guild_id Guild to get the audit log of - * @param user_id Entries from a specific user ID. Set this to `0` will fetch any user - * @param action_type Entries for a specific dpp::audit_type. Set this to `0` will fetch any type - * @param before Entries with ID less than a specific audit log entry ID. Used for paginating - * @param after Entries with ID greater than a specific audit log entry ID. Used for paginating - * @param limit Maximum number of entries (between 1-100) to return - * @return auditlog returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") auditlog guild_auditlog_get_sync(snowflake guild_id, snowflake user_id, uint32_t action_type, snowflake before, snowflake after, uint32_t limit); - -/** - * @brief Add guild ban - * - * Create a guild ban, and optionally delete previous messages sent by the banned user. - * Requires the `BAN_MEMBERS` permission. Fires a `Guild Ban Add` Gateway event. - * @see dpp::cluster::guild_ban_add - * @see https://discord.com/developers/docs/resources/guild#create-guild-ban - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * @param guild_id Guild ID to add ban to - * @param user_id User ID to ban - * @param delete_message_seconds How many seconds to delete messages for, between 0 and 604800 (7 days). Defaults to 0 - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation guild_ban_add_sync(snowflake guild_id, snowflake user_id, uint32_t delete_message_seconds = 0); - -/** - * @brief Delete guild ban - * - * Remove the ban for a user. Requires the `BAN_MEMBERS` permissions. - * Fires a Guild Ban Remove Gateway event. - * @see dpp::cluster::guild_ban_delete - * @see https://discord.com/developers/docs/resources/guild#remove-guild-ban - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * @param guild_id Guild to delete ban from - * @param user_id User ID to delete ban for - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation guild_ban_delete_sync(snowflake guild_id, snowflake user_id); - -/** - * @brief Create a guild - * - * Create a new guild. Returns a guild object on success. `Fires a Guild Create Gateway` event. - * - * When using the roles parameter, the first member of the array is used to change properties of the guild's everyone role. - * If you are trying to bootstrap a guild with additional roles, keep this in mind. The required id field within each role object is an - * integer placeholder, and will be replaced by the API upon consumption. Its purpose is to allow you to overwrite a role's permissions - * in a channel when also passing in channels with the channels array. - * When using the channels parameter, the position field is ignored, and none of the default channels are created. The id field within - * each channel object may be set to an integer placeholder, and will be replaced by the API upon consumption. Its purpose is to - * allow you to create `GUILD_CATEGORY` channels by setting the `parent_id` field on any children to the category's id field. - * Category channels must be listed before any children. - * - * @see dpp::cluster::guild_create - * @see https://discord.com/developers/docs/resources/guild#create-guild - * @note The region field is deprecated and is replaced by channel.rtc_region. This endpoint can be used only by bots in less than 10 guilds. - * @param g Guild to create - * @return guild returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") guild guild_create_sync(const class guild &g); - -/** - * @brief Delete a guild - * - * Delete a guild permanently. User must be owner. Fires a `Guild Delete Gateway` event. - * - * @see dpp::cluster::guild_delete - * @see https://discord.com/developers/docs/resources/guild#delete-guild - * @param guild_id Guild ID to delete - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation guild_delete_sync(snowflake guild_id); - -/** - * @brief Delete guild integration - * - * Delete the attached integration object for the guild. Deletes any associated webhooks and kicks the associated bot if there is one. - * Requires the `MANAGE_GUILD` permission. Fires a Guild Integrations Update Gateway event. - * - * @see dpp::cluster::guild_delete_integration - * @see https://discord.com/developers/docs/resources/guild#delete-guild-integration - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * @param guild_id Guild ID to delete integration for - * @param integration_id Integration ID to delete - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation guild_delete_integration_sync(snowflake guild_id, snowflake integration_id); - -/** - * @brief Edit a guild - * - * Modify a guild's settings. Requires the `MANAGE_GUILD` permission. Returns the updated guild object on success. - * Fires a `Guild Update Gateway` event. - * - * @see dpp::cluster::guild_edit - * @see https://discord.com/developers/docs/resources/guild#modify-guild - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * @param g Guild to edit - * @return guild returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") guild guild_edit_sync(const class guild &g); - -/** - * @brief Edit guild widget - * - * Requires the `MANAGE_GUILD` permission. - * - * @see dpp::cluster::guild_edit_widget - * @see https://discord.com/developers/docs/resources/guild#modify-guild-widget - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * @param guild_id Guild ID to edit widget for - * @param gw New guild widget information - * @return guild_widget returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") guild_widget guild_edit_widget_sync(snowflake guild_id, const class guild_widget &gw); - -/** - * @brief Get single guild ban - * - * Requires the `BAN_MEMBERS` permission. - * @see dpp::cluster::guild_get_ban - * @see https://discord.com/developers/docs/resources/guild#get-guild-ban - * @param guild_id Guild ID to get ban for - * @param user_id User ID of ban to retrieve - * @return ban returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") ban guild_get_ban_sync(snowflake guild_id, snowflake user_id); - -/** - * @brief Get guild ban list - * - * Requires the `BAN_MEMBERS` permission. - * @see dpp::cluster::guild_get_bans - * @see https://discord.com/developers/docs/resources/guild#get-guild-bans - * @note Provide a user ID to `before` and `after` for pagination. Users will always be returned in ascending order by the user ID. If both before and after are provided, only before is respected. - * @param guild_id Guild ID to get bans for - * @param before If non-zero, all bans for user ids before this user id will be returned up to the limit - * @param after if non-zero, all bans for user ids after this user id will be returned up to the limit - * @param limit the maximum number of bans to retrieve in this call up to a maximum of 1000 - * @return ban_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") ban_map guild_get_bans_sync(snowflake guild_id, snowflake before, snowflake after, snowflake limit); - - -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") guild guild_get_sync(snowflake guild_id); - -/** - * @brief Get guild integrations - * - * Requires the `MANAGE_GUILD` permission. - * - * @see dpp::cluster::guild_get_integrations - * @see https://discord.com/developers/docs/resources/guild#get-guild-integrations - * @param guild_id Guild ID to get integrations for - * @return integration_map returned object on completion - * - * @note This endpoint returns a maximum of 50 integrations. If a guild has more integrations, they cannot be accessed. - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") integration_map guild_get_integrations_sync(snowflake guild_id); - - -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") guild guild_get_preview_sync(snowflake guild_id); - -/** - * @brief Get guild vanity url, if enabled - * - * Returns a partial dpp::invite object for guilds with that feature enabled. Requires the `MANAGE_GUILD` permission. code will be null if a vanity url for the guild is not set. - * @see dpp::cluster::guild_get_vanity - * @see https://discord.com/developers/docs/resources/guild#get-guild-vanity-url - * @param guild_id Guild to get vanity URL for - * @return invite returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") invite guild_get_vanity_sync(snowflake guild_id); - -/** - * @brief Get guild widget - * - * Requires the `MANAGE_GUILD` permission. - * - * @see dpp::cluster::guild_get_widget - * @see https://discord.com/developers/docs/resources/guild#get-guild-widget - * @param guild_id Guild ID to get widget for - * @return guild_widget returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") guild_widget guild_get_widget_sync(snowflake guild_id); - -/** - * @brief Modify guild integration - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * - * @see dpp::cluster::guild_modify_integration - * @see https://discord.com/developers/docs/resources/guild#modify-guild-integration - * @param guild_id Guild ID to modify integration for - * @param i Integration to modify - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation guild_modify_integration_sync(snowflake guild_id, const class integration &i); - -/** - * @brief Get prune counts - * - * Returns a prune object indicating the number of members that would be removed in a prune operation. Requires the `KICK_MEMBERS` - * permission. By default, prune will not remove users with roles. You can optionally include specific roles in your prune by providing the - * include_roles parameter. Any inactive user that has a subset of the provided role(s) will be counted in the prune and users with additional - * roles will not. - * - * @see dpp::cluster::guild_get_prune_counts - * @see https://discord.com/developers/docs/resources/guild#get-guild-prune-count - * @param guild_id Guild ID to count for pruning - * @param pruneinfo Pruning info - * @return prune returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") prune guild_get_prune_counts_sync(snowflake guild_id, const struct prune& pruneinfo); - -/** - * @brief Begin guild prune - * - * Begin a prune operation. Requires the `KICK_MEMBERS` permission. Returns a prune object indicating the number of members - * that were removed in the prune operation. For large guilds it's recommended to set the `compute_prune_count` option to false, forcing - * 'pruned' to 0. Fires multiple `Guild Member Remove` Gateway events. - * By default, prune will not remove users with roles. You can optionally include specific roles in your prune by providing the `include_roles` - * parameter. Any inactive user that has a subset of the provided role(s) will be included in the prune and users with additional roles will not. - * - * @see dpp::cluster::guild_begin_prune - * @see https://discord.com/developers/docs/resources/guild#begin-guild-prune - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * @param guild_id Guild ID to prune - * @param pruneinfo Pruning info - * @return prune returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") prune guild_begin_prune_sync(snowflake guild_id, const struct prune& pruneinfo); - -/** - * @brief Change current user nickname - * - * Modifies the nickname of the current user in a guild. - * Fires a `Guild Member Update` Gateway event. - * - * @deprecated Deprecated in favor of Modify Current Member. Will be replaced by dpp::cluster::guild_current_member_edit - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * @see dpp::cluster::guild_set_nickname - * @see https://discord.com/developers/docs/resources/guild#modify-current-user-nick - * @param guild_id Guild ID to change nickname on - * @param nickname New nickname, or empty string to clear nickname - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation guild_set_nickname_sync(snowflake guild_id, const std::string &nickname); - -/** - * @brief Sync guild integration - * - * @see dpp::cluster::guild_sync_integration - * @see https://discord.com/developers/docs/resources/guild#sync-guild-integration - * @param guild_id Guild ID to sync integration on - * @param integration_id Integration ID to synchronise - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation guild_sync_integration_sync(snowflake guild_id, snowflake integration_id); - -/** - * @brief Get the guild's onboarding configuration - * - * @see dpp::cluster::guild_get_onboarding - * @see https://discord.com/developers/docs/resources/guild#get-guild-onboarding - * @param guild_id The guild to pull the onboarding configuration from. - * @return onboarding returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") onboarding guild_get_onboarding_sync(snowflake guild_id); - -/** - * @brief Edit the guild's onboarding configuration - * - * Requires the `MANAGE_GUILD` and `MANAGE_ROLES` permissions. - * - * @note Onboarding enforces constraints when enabled. These constraints are that there must be at least 7 Default Channels and at least 5 of them must allow sending messages to the \@everyone role. The `onboarding::mode` field modifies what is considered when enforcing these constraints. - * - * @see dpp::cluster::guild_edit_onboarding - * @see https://discord.com/developers/docs/resources/guild#modify-guild-onboarding - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * @param o The onboarding object - * @return onboarding returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") onboarding guild_edit_onboarding_sync(const struct onboarding& o); - -/** - * @brief Get the guild's welcome screen - * - * If the welcome screen is not enabled, the `MANAGE_GUILD` permission is required. - * - * @see dpp::cluster::guild_get_welcome_screen - * @see https://discord.com/developers/docs/resources/guild#get-guild-welcome-screen - * @param guild_id The guild ID to get the welcome screen from - * @return dpp::welcome_screen returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") dpp::welcome_screen guild_get_welcome_screen_sync(snowflake guild_id); - -/** - * @brief Edit the guild's welcome screen - * - * Requires the `MANAGE_GUILD` permission. May fire a `Guild Update` Gateway event. - * - * @see dpp::cluster::guild_edit_welcome_screen - * @see https://discord.com/developers/docs/resources/guild#modify-guild-welcome-screen - * @param guild_id The guild ID to edit the welcome screen for - * @param welcome_screen The welcome screen - * @param enabled Whether the welcome screen should be enabled or disabled - * @return dpp::welcome_screen returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") dpp::welcome_screen guild_edit_welcome_screen_sync(snowflake guild_id, const struct welcome_screen& welcome_screen, bool enabled); - -/** - * @brief Add guild member. Needs a specific oauth2 scope, from which you get the access_token. - * - * Adds a user to the guild, provided you have a valid oauth2 access token for the user with the guilds.join scope. - * Returns the guild_member, which is defaulted if the user is already a member of the guild. Fires a `Guild Member Add` Gateway event. - * - * For guilds with Membership Screening enabled, this endpoint will default to adding new members as pending in the guild member object. - * Members that are pending will have to complete membership screening before they become full members that can talk. - * - * @note All parameters to this endpoint except for access_token are optional. - * The bot must be a member of the guild with `CREATE_INSTANT_INVITE` permission. - * @see dpp::cluster::guild_add_member - * @see https://discord.com/developers/docs/resources/guild#add-guild-member - * @param gm Guild member to add - * @param access_token Access token from Oauth2 scope - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation guild_add_member_sync(const guild_member& gm, const std::string &access_token); - -/** - * @brief Edit the properties of an existing guild member - * - * Modify attributes of a guild member. Returns the guild_member. Fires a `Guild Member Update` Gateway event. - * To remove a timeout, set the `communication_disabled_until` to a non-zero time in the past, e.g. 1. - * When moving members to channels, the API user must have permissions to both connect to the channel and have the `MOVE_MEMBERS` permission. - * For moving and disconnecting users from voice, use dpp::cluster::guild_member_move. - * @see dpp::cluster::guild_edit_member - * @see https://discord.com/developers/docs/resources/guild#modify-guild-member - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * @param gm Guild member to edit - * @return guild_member returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") guild_member guild_edit_member_sync(const guild_member& gm); - -/** - * @brief Get a guild member - * @see dpp::cluster::guild_get_member - * @see https://discord.com/developers/docs/resources/guild#get-guild-member - * @param guild_id Guild ID to get member for - * @param user_id User ID of member to get - * @return guild_member returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") guild_member guild_get_member_sync(snowflake guild_id, snowflake user_id); - -/** - * @brief Get all guild members - * - * @note This endpoint is restricted according to whether the `GUILD_MEMBERS` Privileged Intent is enabled for your application. - * @see dpp::cluster::guild_get_members - * @see https://discord.com/developers/docs/resources/guild#get-guild-members - * @param guild_id Guild ID to get all members for - * @param limit max number of members to return (1-1000) - * @param after the highest user id in the previous page - * @return guild_member_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") guild_member_map guild_get_members_sync(snowflake guild_id, uint16_t limit, snowflake after); - -/** - * @brief Add role to guild member - * - * Adds a role to a guild member. Requires the `MANAGE_ROLES` permission. - * Fires a `Guild Member Update` Gateway event. - * @see dpp::cluster::guild_member_add_role - * @see https://discord.com/developers/docs/resources/guild#add-guild-member-role - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * @param guild_id Guild ID to add a role to - * @param user_id User ID to add role to - * @param role_id Role ID to add to the user - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation guild_member_add_role_sync(snowflake guild_id, snowflake user_id, snowflake role_id); - -/** - * @brief Remove (kick) a guild member - * - * Remove a member from a guild. Requires `KICK_MEMBERS` permission. - * Fires a `Guild Member Remove` Gateway event. - * @see dpp::cluster::guild_member_delete - * @see https://discord.com/developers/docs/resources/guild#remove-guild-member - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * @deprecated Replaced by dpp::cluster::guild_member_kick - * @param guild_id Guild ID to kick member from - * @param user_id User ID to kick - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation guild_member_delete_sync(snowflake guild_id, snowflake user_id); - -/** - * @brief Remove (kick) a guild member - * - * Remove a member from a guild. Requires `KICK_MEMBERS` permission. - * Fires a `Guild Member Remove` Gateway event. - * @see dpp::cluster::guild_member_kick - * @see https://discord.com/developers/docs/resources/guild#remove-guild-member - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * @param guild_id Guild ID to kick member from - * @param user_id User ID to kick - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation guild_member_kick_sync(snowflake guild_id, snowflake user_id); - -/** - * @brief Set the timeout of a guild member - * - * Fires a `Guild Member Update` Gateway event. - * @see dpp::cluster::guild_member_timeout - * @see https://discord.com/developers/docs/resources/guild#modify-guild-member - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * @param guild_id Guild ID to timeout the member in - * @param user_id User ID to set the timeout for - * @param communication_disabled_until The timestamp when the user's timeout will expire (up to 28 days in the future). Set to 0 to remove the timeout - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation guild_member_timeout_sync(snowflake guild_id, snowflake user_id, time_t communication_disabled_until); - -/** - * @brief Remove the timeout of a guild member. - * A shortcut for guild_member_timeout(guild_id, user_id, 0, callback) - * Fires a `Guild Member Update` Gateway event. - * @see dpp::cluster::guild_member_timeout_remove - * @see https://discord.com/developers/docs/resources/guild#modify-guild-member - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * @param guild_id Guild ID to remove the member timeout from - * @param user_id User ID to remove the timeout for - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation guild_member_timeout_remove_sync(snowflake guild_id, snowflake user_id); - -/** - * @brief Remove role from guild member - * - * Removes a role from a guild member. Requires the `MANAGE_ROLES` permission. - * Fires a `Guild Member Update` Gateway event. - * @see dpp::cluster::guild_member_delete_role - * @see https://discord.com/developers/docs/resources/guild#remove-guild-member-role - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * @param guild_id Guild ID to remove role from user on - * @param user_id User ID to remove role from - * @param role_id Role to remove - * @return confirmation returned object on completion - * @deprecated Use dpp::cluster::guild_member_remove_role instead - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation guild_member_delete_role_sync(snowflake guild_id, snowflake user_id, snowflake role_id); - -/** - * @brief Remove role from guild member - * - * Removes a role from a guild member. Requires the `MANAGE_ROLES` permission. - * Fires a `Guild Member Update` Gateway event. - * @see dpp::cluster::guild_member_remove_role - * @see https://discord.com/developers/docs/resources/guild#remove-guild-member-role - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * @param guild_id Guild ID to remove role from user on - * @param user_id User ID to remove role from - * @param role_id Role to remove - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation guild_member_remove_role_sync(snowflake guild_id, snowflake user_id, snowflake role_id); - -/** - * @brief Moves the guild member to a other voice channel, if member is connected to one. - * Set the `channel_id` to `0` to disconnect the user. - * - * Fires a `Guild Member Update` Gateway event. - * @note When moving members to channels, the API user __must__ have permissions to both connect to the channel and have the `MOVE_MEMBERS` permission. - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * @see dpp::cluster::guild_member_move - * @see https://discord.com/developers/docs/resources/guild#modify-guild-member - * @param channel_id Id of the channel to which the user is used. Set to `0` to disconnect the user - * @param guild_id Guild id to which the user is connected - * @param user_id User id, who should be moved - * @return guild_member returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") guild_member guild_member_move_sync(const snowflake channel_id, const snowflake guild_id, const snowflake user_id); - -/** - * @brief Search for guild members based on whether their username or nickname starts with the given string. - * - * @note This endpoint is restricted according to whether the `GUILD_MEMBERS` Privileged Intent is enabled for your application. - * @see dpp::cluster::guild_search_members - * @see https://discord.com/developers/docs/resources/guild#search-guild-members - * @param guild_id Guild ID to search in - * @param query Query string to match username(s) and nickname(s) against - * @param limit max number of members to return (1-1000) - * @return guild_member_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") guild_member_map guild_search_members_sync(snowflake guild_id, const std::string& query, uint16_t limit); - -/** - * @brief Get guild invites - * - * Returns a list of invite objects (with invite metadata) for the guild. Requires the `MANAGE_GUILD` permission. - * - * @see dpp::cluster::guild_get_invites - * @see https://discord.com/developers/docs/resources/guild#get-guild-invites - * @param guild_id Guild ID to get invites for - * @return invite_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") invite_map guild_get_invites_sync(snowflake guild_id); - - -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") invite invite_delete_sync(const std::string &invitecode); - -/** - * @brief Get details about an invite - * - * @see dpp::cluster::invite_get - * @see https://discord.com/developers/docs/resources/invite#get-invite - * @param invite_code Invite code to get information on - * @return invite returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") invite invite_get_sync(const std::string &invite_code); - -/** - * @brief Add a reaction to a message. The reaction string must be either an `emojiname:id` or a unicode character. - * - * @see dpp::cluster::message_add_reaction - * @see https://discord.com/developers/docs/resources/channel#create-reaction - * @param m Message to add a reaction to - * @param reaction Reaction to add. Emojis should be in the form emojiname:id - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation message_add_reaction_sync(const struct message &m, const std::string &reaction); - -/** - * @brief Add a reaction to a message by id. The reaction string must be either an `emojiname:id` or a unicode character. - * - * @see dpp::cluster::message_add_reaction - * @see https://discord.com/developers/docs/topics/gateway#message-reaction-add - * @param message_id Message to add reactions to - * @param channel_id Channel to add reactions to - * @param reaction Reaction to add. Emojis should be in the form emojiname:id - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation message_add_reaction_sync(snowflake message_id, snowflake channel_id, const std::string &reaction); - -/** - * @brief Send a message to a channel. The callback function is called when the message has been sent - * - * @see dpp::cluster::message_create - * @see https://discord.com/developers/docs/resources/channel#create-message - * @param m Message to send - * @return message returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") message message_create_sync(const struct message &m); - -/** - * @brief Crosspost a message. The callback function is called when the message has been sent - * - * @see dpp::cluster::message_crosspost - * @see https://discord.com/developers/docs/resources/channel#crosspost-message - * @param message_id Message to crosspost - * @param channel_id Channel ID to crosspost from - * @return message returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") message message_crosspost_sync(snowflake message_id, snowflake channel_id); - -/** - * @brief Delete all reactions on a message - * - * @see dpp::cluster::message_delete_all_reactions - * @see https://discord.com/developers/docs/resources/channel#delete-all-reactions - * @param m Message to delete reactions from - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation message_delete_all_reactions_sync(const struct message &m); - -/** - * @brief Delete all reactions on a message by id - * - * @see dpp::cluster::message_delete_all_reactions - * @see https://discord.com/developers/docs/resources/channel#delete-all-reactions - * @param message_id Message to delete reactions from - * @param channel_id Channel to delete reactions from - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation message_delete_all_reactions_sync(snowflake message_id, snowflake channel_id); - -/** - * @brief Bulk delete messages from a channel. The callback function is called when the message has been edited - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * - * @note If any message provided older than 2 weeks or any duplicate message ID, it will fail. - * - * @see dpp::cluster::message_delete_bulk - * @see https://discord.com/developers/docs/resources/channel#bulk-delete-messages - * @param message_ids List of message IDs to delete (at least 2 and at most 100 message IDs) - * @param channel_id Channel to delete from - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation message_delete_bulk_sync(const std::vector &message_ids, snowflake channel_id); - -/** - * @brief Delete a message from a channel. The callback function is called when the message has been edited - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * - * @see dpp::cluster::message_delete - * @see https://discord.com/developers/docs/resources/channel#delete-message - * @param message_id Message ID to delete - * @param channel_id Channel to delete from - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation message_delete_sync(snowflake message_id, snowflake channel_id); - -/** - * @brief Delete own reaction from a message. The reaction string must be either an `emojiname:id` or a unicode character. - * - * @see dpp::cluster::message_delete_own_reaction - * @see https://discord.com/developers/docs/resources/channel#delete-own-reaction - * @param m Message to delete own reaction from - * @param reaction Reaction to delete. The reaction should be in the form emojiname:id - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation message_delete_own_reaction_sync(const struct message &m, const std::string &reaction); - -/** - * @brief Delete own reaction from a message by id. The reaction string must be either an `emojiname:id` or a unicode character. - * - * @see dpp::cluster::message_delete_own_reaction - * @see https://discord.com/developers/docs/resources/channel#delete-own-reaction - * @param message_id Message to delete reactions from - * @param channel_id Channel to delete reactions from - * @param reaction Reaction to delete. The reaction should be in the form emojiname:id - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation message_delete_own_reaction_sync(snowflake message_id, snowflake channel_id, const std::string &reaction); - -/** - * @brief Delete a user's reaction from a message. The reaction string must be either an `emojiname:id` or a unicode character - * - * @see dpp::cluster::message_delete_reaction - * @see https://discord.com/developers/docs/resources/channel#delete-user-reaction - * @param m Message to delete a user's reaction from - * @param user_id User ID who's reaction you want to remove - * @param reaction Reaction to remove. Reactions should be in the form emojiname:id - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation message_delete_reaction_sync(const struct message &m, snowflake user_id, const std::string &reaction); - -/** - * @brief Delete a user's reaction from a message by id. The reaction string must be either an `emojiname:id` or a unicode character - * - * @see dpp::cluster::message_delete_reaction - * @see https://discord.com/developers/docs/resources/channel#delete-user-reaction - * @param message_id Message to delete reactions from - * @param channel_id Channel to delete reactions from - * @param user_id User ID who's reaction you want to remove - * @param reaction Reaction to remove. Reactions should be in the form emojiname:id - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation message_delete_reaction_sync(snowflake message_id, snowflake channel_id, snowflake user_id, const std::string &reaction); - -/** - * @brief Delete all reactions on a message using a particular emoji. The reaction string must be either an `emojiname:id` or a unicode character - * - * @see dpp::cluster::message_delete_reaction_emoji - * @see https://discord.com/developers/docs/resources/channel#delete-all-reactions-for-emoji - * @param m Message to delete reactions from - * @param reaction Reaction to delete, in the form emojiname:id or a unicode character - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation message_delete_reaction_emoji_sync(const struct message &m, const std::string &reaction); - -/** - * @brief Delete all reactions on a message using a particular emoji by id. The reaction string must be either an `emojiname:id` or a unicode character - * - * @see dpp::cluster::message_delete_reaction_emoji - * @see https://discord.com/developers/docs/resources/channel#delete-all-reactions-for-emoji - * @param message_id Message to delete reactions from - * @param channel_id Channel to delete reactions from - * @param reaction Reaction to delete, in the form emojiname:id or a unicode character - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation message_delete_reaction_emoji_sync(snowflake message_id, snowflake channel_id, const std::string &reaction); - -/** - * @brief Edit a message on a channel. The callback function is called when the message has been edited - * - * @see dpp::cluster::message_edit - * @see https://discord.com/developers/docs/resources/channel#edit-message - * @param m Message to edit - * @return message returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") message message_edit_sync(const struct message &m); - -/** - * @brief Edit the flags of a message on a channel. The callback function is called when the message has been edited - * - * @param m Message to edit the flags of - * @return message returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") message message_edit_flags_sync(const struct message &m); - -/** - * @brief Get a message - * - * @see dpp::cluster::message_get - * @see https://discord.com/developers/docs/resources/channel#get-channel-message - * @param message_id Message ID - * @param channel_id Channel ID - * @return message returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") message message_get_sync(snowflake message_id, snowflake channel_id); - -/** - * @brief Get reactions on a message for a particular emoji. The reaction string must be either an `emojiname:id` or a unicode character - * - * @see dpp::cluster::message_get_reactions - * @see https://discord.com/developers/docs/resources/channel#get-reactions - * @param m Message to get reactions for - * @param reaction Reaction should be in the form emojiname:id or a unicode character - * @param before Reactions before this ID should be retrieved if this is set to non-zero - * @param after Reactions before this ID should be retrieved if this is set to non-zero - * @param limit This number of reactions maximum should be returned - * @return user_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") user_map message_get_reactions_sync(const struct message &m, const std::string &reaction, snowflake before, snowflake after, snowflake limit); - -/** - * @brief Get reactions on a message for a particular emoji by id. The reaction string must be either an `emojiname:id` or a unicode character - * - * @see dpp::cluster::message_get_reactions - * @see https://discord.com/developers/docs/resources/channel#get-reactions - * @param message_id Message to get reactions for - * @param channel_id Channel to get reactions for - * @param reaction Reaction should be in the form emojiname:id or a unicode character - * @param before Reactions before this ID should be retrieved if this is set to non-zero - * @param after Reactions before this ID should be retrieved if this is set to non-zero - * @param limit This number of reactions maximum should be returned - * @return emoji_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") emoji_map message_get_reactions_sync(snowflake message_id, snowflake channel_id, const std::string &reaction, snowflake before, snowflake after, snowflake limit); - -/** - * @brief Pin a message - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * @see dpp::cluster::message_pin - * @see https://discord.com/developers/docs/resources/channel#pin-message - * @param channel_id Channel id to pin message on - * @param message_id Message id to pin message on - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation message_pin_sync(snowflake channel_id, snowflake message_id); - -/** - * @brief Get multiple messages. - * - * This function will attempt to fetch as many messages as possible using multiple API calls if needed. - * - * @see dpp::cluster::messages_get - * @see https://discord.com/developers/docs/resources/channel#get-channel-messages - * @param channel_id Channel ID to retrieve messages for - * @param around Messages should be retrieved around this ID if this is set to non-zero - * @param before Messages before this ID should be retrieved if this is set to non-zero - * @param after Messages after this ID should be retrieved if this is set to non-zero - * @param limit This number of messages maximum should be returned, up to a maximum of 100. - * @return message_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") message_map messages_get_sync(snowflake channel_id, snowflake around, snowflake before, snowflake after, uint64_t limit); - -/** - * @brief Unpin a message - * @see dpp::cluster::message_unpin - * @see https://discord.com/developers/docs/resources/channel#unpin-message - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * @param channel_id Channel id to unpin message on - * @param message_id Message id to unpin message on - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation message_unpin_sync(snowflake channel_id, snowflake message_id); - -/** - * @brief Get a list of users that voted for this specific answer. - * - * @param m Message that contains the poll to retrieve the answers from - * @param answer_id ID of the answer to retrieve votes from (see poll_answer::answer_id) - * @param after Users after this ID should be retrieved if this is set to non-zero - * @param limit This number of users maximum should be returned, up to 100 - * @return user_map returned object on completion - * @see dpp::cluster::poll_get_answer_voters - * @see https://discord.com/developers/docs/resources/poll#get-answer-voters - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") user_map poll_get_answer_voters_sync(const message& m, uint32_t answer_id, snowflake after, uint64_t limit); - -/** - * @brief Get a list of users that voted for this specific answer. - * - * @param message_id ID of the message with the poll to retrieve the answers from - * @param channel_id ID of the channel with the poll to retrieve the answers from - * @param answer_id ID of the answer to retrieve votes from (see poll_answer::answer_id) - * @param after Users after this ID should be retrieved if this is set to non-zero - * @param limit This number of users maximum should be returned, up to 100 - * @return user_map returned object on completion - * @see dpp::cluster::poll_get_answer_voters - * @see https://discord.com/developers/docs/resources/poll#get-answer-voters - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") user_map poll_get_answer_voters_sync(snowflake message_id, snowflake channel_id, uint32_t answer_id, snowflake after, uint64_t limit); - -/** - * @brief Immediately end a poll. - * - * @param m Message that contains the poll - * @return message returned object on completion - * @see dpp::cluster::poll_end - * @see https://discord.com/developers/docs/resources/poll#end-poll - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") message poll_end_sync(const message &m); - -/** - * @brief Immediately end a poll. - * - * @param message_id ID of the message with the poll to end - * @param channel_id ID of the channel with the poll to end - * @return message returned object on completion - * @see dpp::cluster::poll_end - * @see https://discord.com/developers/docs/resources/poll#end-poll - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") message poll_end_sync(snowflake message_id, snowflake channel_id); - -/** - * @brief Get a channel's pins - * @see dpp::cluster::channel_pins_get - * @see https://discord.com/developers/docs/resources/channel#get-pinned-messages - * @param channel_id Channel ID to get pins for - * @return message_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") message_map channel_pins_get_sync(snowflake channel_id); - -/** - * @brief Create a role on a guild - * - * Create a new role for the guild. Requires the `MANAGE_ROLES` permission. Returns the new role object on success. - * Fires a `Guild Role Create` Gateway event. - * - * @see dpp::cluster::role_create - * @see https://discord.com/developers/docs/resources/guild#create-guild-role - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * @param r Role to create (guild ID is encapsulated in the role object) - * @return role returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") role role_create_sync(const class role &r); - -/** - * @brief Delete a role - * - * Requires the `MANAGE_ROLES` permission. Fires a `Guild Role Delete` Gateway event. - * - * @see dpp::cluster::role_delete - * @see https://discord.com/developers/docs/resources/guild#delete-guild-role - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * @param guild_id Guild ID to delete the role on - * @param role_id Role ID to delete - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation role_delete_sync(snowflake guild_id, snowflake role_id); - -/** - * @brief Edit a role on a guild - * - * Requires the `MANAGE_ROLES` permission. Returns the updated role on success. Fires a `Guild Role Update` Gateway event. - * - * @see dpp::cluster::role_edit - * @see https://discord.com/developers/docs/resources/guild#modify-guild-role - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * @param r Role to edit - * @return role returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") role role_edit_sync(const class role &r); - -/** - * @brief Edit multiple role's position in a guild. Returns a list of all roles of the guild on success. - * - * Modify the positions of a set of role objects for the guild. Requires the `MANAGE_ROLES` permission. - * Fires multiple `Guild Role Update` Gateway events. - * - * @see dpp::cluster::roles_edit_position - * @see https://discord.com/developers/docs/resources/guild#modify-guild-role-positions - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * @param guild_id Guild ID to change the roles position on - * @param roles Vector of roles to change the positions of - * @return role_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") role_map roles_edit_position_sync(snowflake guild_id, const std::vector &roles); - -/** - * @brief Get a role for a guild - * - * @see dpp::cluster::roles_get - * @see https://discord.com/developers/docs/resources/guild#get-guild-roles - * @param guild_id Guild ID to get role for - * @return role_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") role_map roles_get_sync(snowflake guild_id); - -/** - * @brief Get the application's role connection metadata records - * - * @see dpp::cluster::application_role_connection_get - * @see https://discord.com/developers/docs/resources/application-role-connection-metadata#get-application-role-connection-metadata-records - * @param application_id The application ID - * @return application_role_connection returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") application_role_connection application_role_connection_get_sync(snowflake application_id); - -/** - * @brief Update the application's role connection metadata records - * - * @see dpp::cluster::application_role_connection_update - * @see https://discord.com/developers/docs/resources/application-role-connection-metadata#update-application-role-connection-metadata-records - * @param application_id The application ID - * @param connection_metadata The application role connection metadata to update - * @return application_role_connection returned object on completion - * @note An application can have a maximum of 5 metadata records. - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") application_role_connection application_role_connection_update_sync(snowflake application_id, const std::vector &connection_metadata); - -/** - * @brief Get user application role connection - * - * @see dpp::cluster::user_application_role_connection_get - * @see https://discord.com/developers/docs/resources/user#get-user-application-role-connection - * @param application_id The application ID - * @return application_role_connection returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") application_role_connection user_application_role_connection_get_sync(snowflake application_id); - -/** - * @brief Update user application role connection - * - * @see dpp::cluster::user_application_role_connection_update - * @see https://discord.com/developers/docs/resources/user#update-user-application-role-connection - * @param application_id The application ID - * @param connection The application role connection to update - * @return application_role_connection returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") application_role_connection user_application_role_connection_update_sync(snowflake application_id, const application_role_connection &connection); - -/** - * @brief Get all scheduled events for a guild - * @see dpp::cluster::guild_events_get - * @see https://discord.com/developers/docs/resources/guild-scheduled-event#list-scheduled-events-for-guild - * @param guild_id Guild to get events for - * @return scheduled_event_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") scheduled_event_map guild_events_get_sync(snowflake guild_id); - -/** - * @brief Create a scheduled event on a guild - * - * @see dpp::cluster::guild_event_create - * @see https://discord.com/developers/docs/resources/guild-scheduled-event#create-guild-scheduled-event - * @param event Event to create (guild ID must be populated) - * @return scheduled_event returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") scheduled_event guild_event_create_sync(const scheduled_event& event); - -/** - * @brief Delete a scheduled event from a guild - * - * @see dpp::cluster::guild_event_delete - * @see https://discord.com/developers/docs/resources/guild-scheduled-event#delete-guild-scheduled-event - * @param event_id Event ID to delete - * @param guild_id Guild ID of event to delete - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation guild_event_delete_sync(snowflake event_id, snowflake guild_id); - -/** - * @brief Edit/modify a scheduled event on a guild - * - * @see dpp::cluster::guild_event_edit - * @see https://discord.com/developers/docs/resources/guild-scheduled-event#modify-guild-scheduled-event - * @param event Event to create (event ID and guild ID must be populated) - * @return scheduled_event returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") scheduled_event guild_event_edit_sync(const scheduled_event& event); - -/** - * @brief Get a scheduled event for a guild - * - * @see dpp::cluster::guild_event_get - * @see https://discord.com/developers/docs/resources/guild-scheduled-event#get-guild-scheduled-event - * @param guild_id Guild to get event for - * @param event_id Event ID to get - * @return scheduled_event returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") scheduled_event guild_event_get_sync(snowflake guild_id, snowflake event_id); - -/** - * @brief Returns all SKUs for a given application. - * @note Because of how Discord's SKU and subscription systems work, you will see two SKUs for your premium offering. - * For integration and testing entitlements, you should use the SKU with type: 5. - * - * @see dpp::cluster::skus_get - * @see https://discord.com/developers/docs/monetization/skus#list-skus - * @return sku_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") sku_map skus_get_sync(); - - -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") stage_instance stage_instance_create_sync(const stage_instance& si); - -/** - * @brief Get the stage instance associated with the channel id, if it exists. - * @see dpp::cluster::stage_instance_get - * @see https://discord.com/developers/docs/resources/stage-instance#get-stage-instance - * @param channel_id ID of the associated channel - * @return stage_instance returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") stage_instance stage_instance_get_sync(const snowflake channel_id); - - -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") stage_instance stage_instance_edit_sync(const stage_instance& si); - -/** - * @brief Delete a stage instance. - * @see dpp::cluster::stage_instance_delete - * @see https://discord.com/developers/docs/resources/stage-instance#delete-stage-instance - * @param channel_id ID of the associated channel - * @return confirmation returned object on completion - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation stage_instance_delete_sync(const snowflake channel_id); - -/** - * @brief Create a sticker in a guild - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * @see dpp::cluster::guild_sticker_create - * @see https://discord.com/developers/docs/resources/sticker#create-guild-sticker - * @param s Sticker to create. Must have its guild ID set. - * @return sticker returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") sticker guild_sticker_create_sync(const sticker &s); - -/** - * @brief Delete a sticker from a guild - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * @see dpp::cluster::guild_sticker_delete - * @see https://discord.com/developers/docs/resources/sticker#delete-guild-sticker - * @param sticker_id sticker ID to delete - * @param guild_id guild ID to delete from - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation guild_sticker_delete_sync(snowflake sticker_id, snowflake guild_id); - -/** - * @brief Get a guild sticker - * @see dpp::cluster::guild_sticker_get - * @see https://discord.com/developers/docs/resources/sticker#get-guild-sticker - * @param id Id of sticker to get. - * @param guild_id Guild ID of the guild where the sticker is - * @return sticker returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") sticker guild_sticker_get_sync(snowflake id, snowflake guild_id); - -/** - * @brief Modify a sticker in a guild - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * @see dpp::cluster::guild_sticker_modify - * @see https://discord.com/developers/docs/resources/sticker#modify-guild-sticker - * @param s Sticker to modify. Must have its guild ID and sticker ID set. - * @return sticker returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") sticker guild_sticker_modify_sync(const sticker &s); - -/** - * @brief Get all guild stickers - * @see dpp::cluster::guild_stickers_get - * @see https://discord.com/developers/docs/resources/sticker#list-guild-stickers - * @param guild_id Guild ID of the guild where the sticker is - * @return sticker_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") sticker_map guild_stickers_get_sync(snowflake guild_id); - -/** - * @brief Get a nitro sticker - * @see dpp::cluster::nitro_sticker_get - * @see https://discord.com/developers/docs/resources/sticker#get-sticker - * @param id Id of sticker to get. - * @return sticker returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") sticker nitro_sticker_get_sync(snowflake id); - -/** - * @brief Get a list of available sticker packs - * @see dpp::cluster::sticker_packs_get - * @see https://discord.com/developers/docs/resources/sticker#list-sticker-packs - * @return sticker_pack_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") sticker_pack_map sticker_packs_get_sync(); - -/** - * @brief Create a new guild based on a template. - * @note This endpoint can be used only by bots in less than 10 guilds. - * @see dpp::cluster::guild_create_from_template - * @see https://discord.com/developers/docs/resources/guild-template#create-guild-from-guild-template - * @param code Template code to create guild from - * @param name Guild name to create - * @return guild returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") guild guild_create_from_template_sync(const std::string &code, const std::string &name); - -/** - * @brief Creates a template for the guild - * - * @see dpp::cluster::guild_template_create - * @see https://discord.com/developers/docs/resources/guild-template#create-guild-template - * @param guild_id Guild to create template from - * @param name Template name to create - * @param description Description of template to create - * @return dtemplate returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") dtemplate guild_template_create_sync(snowflake guild_id, const std::string &name, const std::string &description); - -/** - * @brief Deletes the template - * - * @see dpp::cluster::guild_template_delete - * @see https://discord.com/developers/docs/resources/guild-template#delete-guild-template - * @param guild_id Guild ID of template to delete - * @param code Template code to delete - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation guild_template_delete_sync(snowflake guild_id, const std::string &code); - -/** - * @brief Modifies the template's metadata. - * - * @see dpp::cluster::guild_template_modify - * @see https://discord.com/developers/docs/resources/guild-template#modify-guild-template - * @param guild_id Guild ID of template to modify - * @param code Template code to modify - * @param name New name of template - * @param description New description of template - * @return dtemplate returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") dtemplate guild_template_modify_sync(snowflake guild_id, const std::string &code, const std::string &name, const std::string &description); - -/** - * @brief Get guild templates - * - * @see dpp::cluster::guild_templates_get - * @see https://discord.com/developers/docs/resources/guild-template#get-guild-templates - * @param guild_id Guild ID to get templates for - * @return dtemplate_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") dtemplate_map guild_templates_get_sync(snowflake guild_id); - -/** - * @brief Syncs the template to the guild's current state. - * - * @see dpp::cluster::guild_template_sync - * @see https://discord.com/developers/docs/resources/guild-template#sync-guild-template - * @param guild_id Guild to synchronise template for - * @param code Code of template to synchronise - * @return dtemplate returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") dtemplate guild_template_sync_sync(snowflake guild_id, const std::string &code); - -/** - * @brief Get a template - * @see dpp::cluster::template_get - * @see https://discord.com/developers/docs/resources/guild-template#get-guild-template - * @param code Template code - * @return dtemplate returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") dtemplate template_get_sync(const std::string &code); - -/** - * @brief Join a thread - * @see dpp::cluster::current_user_join_thread - * @see https://discord.com/developers/docs/resources/channel#join-thread - * @param thread_id Thread ID to join - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation current_user_join_thread_sync(snowflake thread_id); - -/** - * @brief Leave a thread - * @see dpp::cluster::current_user_leave_thread - * @see https://discord.com/developers/docs/resources/channel#leave-thread - * @param thread_id Thread ID to leave - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation current_user_leave_thread_sync(snowflake thread_id); - -/** - * @brief Get all active threads in the guild, including public and private threads. Threads are ordered by their id, in descending order. - * @see dpp::cluster::threads_get_active - * @see https://discord.com/developers/docs/resources/guild#list-active-guild-threads - * @param guild_id Guild to get active threads for - * @return active_threads returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") active_threads threads_get_active_sync(snowflake guild_id); - -/** - * @brief Get private archived threads in a channel which current user has joined (Sorted by ID in descending order) - * @see dpp::cluster::threads_get_joined_private_archived - * @see https://discord.com/developers/docs/resources/channel#list-joined-private-archived-threads - * @param channel_id Channel to get public archived threads for - * @param before_id Get threads before this id - * @param limit Number of threads to get - * @return thread_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") thread_map threads_get_joined_private_archived_sync(snowflake channel_id, snowflake before_id, uint16_t limit); - -/** - * @brief Get private archived threads in a channel (Sorted by archive_timestamp in descending order) - * @see dpp::cluster::threads_get_private_archived - * @see https://discord.com/developers/docs/resources/channel#list-private-archived-threads - * @param channel_id Channel to get public archived threads for - * @param before_timestamp Get threads archived before this timestamp - * @param limit Number of threads to get - * @return thread_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") thread_map threads_get_private_archived_sync(snowflake channel_id, time_t before_timestamp, uint16_t limit); - -/** - * @brief Get public archived threads in a channel (Sorted by archive_timestamp in descending order) - * @see dpp::cluster::threads_get_public_archived - * @see https://discord.com/developers/docs/resources/channel#list-public-archived-threads - * @param channel_id Channel to get public archived threads for - * @param before_timestamp Get threads archived before this timestamp - * @param limit Number of threads to get - * @return thread_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") thread_map threads_get_public_archived_sync(snowflake channel_id, time_t before_timestamp, uint16_t limit); - -/** - * @brief Get a thread member - * @see dpp::cluster::thread_member_get - * @see https://discord.com/developers/docs/resources/channel#get-thread-member - * @param thread_id Thread to get member for - * @param user_id ID of the user to get - * @return thread_member returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") thread_member thread_member_get_sync(const snowflake thread_id, const snowflake user_id); - -/** - * @brief Get members of a thread - * @see dpp::cluster::thread_members_get - * @see https://discord.com/developers/docs/resources/channel#list-thread-members - * @param thread_id Thread to get members for - * @return thread_member_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") thread_member_map thread_members_get_sync(snowflake thread_id); - -/** - * @brief Create a thread in a forum or media channel - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * - * @see dpp::cluster::thread_create_in_forum - * @see https://discord.com/developers/docs/resources/channel#start-thread-in-forum-channel - * @param thread_name Name of the forum thread - * @param channel_id Forum channel in which thread to create - * @param msg The message to start the thread with - * @param auto_archive_duration Duration to automatically archive the thread after recent activity - * @param rate_limit_per_user amount of seconds a user has to wait before sending another message (0-21600); bots, as well as users with the permission manage_messages, manage_thread, or manage_channel, are unaffected - * @param applied_tags List of IDs of forum tags (dpp::forum_tag) to apply to this thread - * @return thread returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") thread thread_create_in_forum_sync(const std::string& thread_name, snowflake channel_id, const message& msg, auto_archive_duration_t auto_archive_duration, uint16_t rate_limit_per_user, std::vector applied_tags = {}); - -/** - * @brief Create a thread - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * - * @see dpp::cluster::thread_create - * @see https://discord.com/developers/docs/resources/channel#start-thread-without-message - * @param thread_name Name of the thread - * @param channel_id Channel in which thread to create - * @param auto_archive_duration Duration after which thread auto-archives. Can be set to - 60, 1440 (for boosted guilds can also be: 4320, 10080) - * @param thread_type Type of thread - CHANNEL_PUBLIC_THREAD, CHANNEL_ANNOUNCEMENT_THREAD, CHANNEL_PRIVATE_THREAD - * @param invitable whether non-moderators can add other non-moderators to a thread; only available when creating a private thread - * @param rate_limit_per_user amount of seconds a user has to wait before sending another message (0-21600); bots, as well as users with the permission manage_messages, manage_thread, or manage_channel, are unaffected - * @return thread returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") thread thread_create_sync(const std::string& thread_name, snowflake channel_id, uint16_t auto_archive_duration, channel_type thread_type, bool invitable, uint16_t rate_limit_per_user); - -/** - * @brief Edit a thread - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * - * @see dpp::cluster::thread_edit - * @see https://discord.com/developers/docs/topics/threads#editing-deleting-threads - * @param t Thread to edit - * @return thread returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") thread thread_edit_sync(const thread &t); - -/** - * @brief Create a thread with a message (Discord: ID of a thread is same as message ID) - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * @see dpp::cluster::thread_create_with_message - * @see https://discord.com/developers/docs/resources/channel#start-thread-from-message - * @param thread_name Name of the thread - * @param channel_id Channel in which thread to create - * @param message_id message to start thread with - * @param auto_archive_duration Duration after which thread auto-archives. Can be set to - 60, 1440 (for boosted guilds can also be: 4320, 10080) - * @param rate_limit_per_user amount of seconds a user has to wait before sending another message (0-21600); bots, as well as users with the permission manage_messages, manage_thread, or manage_channel, are unaffected - * @return thread returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") thread thread_create_with_message_sync(const std::string& thread_name, snowflake channel_id, snowflake message_id, uint16_t auto_archive_duration, uint16_t rate_limit_per_user); - -/** - * @brief Add a member to a thread - * @see dpp::cluster::thread_member_add - * @see https://discord.com/developers/docs/resources/channel#add-thread-member - * @param thread_id Thread ID to add to - * @param user_id Member ID to add - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation thread_member_add_sync(snowflake thread_id, snowflake user_id); - -/** - * @brief Remove a member from a thread - * @see dpp::cluster::thread_member_remove - * @see https://discord.com/developers/docs/resources/channel#remove-thread-member - * @param thread_id Thread ID to remove from - * @param user_id Member ID to remove - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation thread_member_remove_sync(snowflake thread_id, snowflake user_id); - -/** - * @brief Get the thread specified by thread_id. This uses the same call as dpp::cluster::channel_get but returns a thread object. - * @see dpp::cluster::thread_get - * @see https://discord.com/developers/docs/resources/channel#get-channel - * @param thread_id The id of the thread to obtain. - * @return thread returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") thread thread_get_sync(snowflake thread_id); - -/** - * @brief Edit current (bot) user. - * - * Modify the requester's user account settings. Returns a dpp::user object on success. - * Fires a User Update Gateway event. - * - * @note There appears to be no limit to the image size, however, if your image cannot be processed/uploaded in time, you will receive a malformed http request. - * - * @see dpp::cluster::current_user_edit - * @see https://discord.com/developers/docs/resources/user#modify-current-user - * @param nickname Nickname to set - * @param avatar_blob Avatar data to upload - * @param avatar_type Type of image for avatar. It can be one of `i_gif`, `i_jpg` or `i_png`. - * @param banner_blob Banner data to upload - * @param banner_type Type of image for Banner. It can be one of `i_gif`, `i_jpg` or `i_png`. - * @return user returned object on completion - * @throw dpp::length_exception Image data is larger than the maximum size of 256 kilobytes - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") user current_user_edit_sync(const std::string &nickname, const std::string& avatar_blob = "", const image_type avatar_type = i_png, const std::string& banner_blob = "", const image_type banner_type = i_png); - -/** - * @brief Get current (bot) application - * - * @see dpp::cluster::current_application_get - * @see https://discord.com/developers/docs/topics/oauth2#get-current-bot-application-information - * @return application returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") application current_application_get_sync(); - -/** - * @brief Get current (bot) user - * - * @see dpp::cluster::current_user_get - * @see https://discord.com/developers/docs/resources/user#get-current-user - * @return user_identified returned object on completion - * @note The user_identified object is a subclass of dpp::user which contains further details if you have the oauth2 identify or email scopes. - * If you do not have these scopes, these fields are empty. You can safely convert a user_identified to user with `dynamic_cast`. - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") user_identified current_user_get_sync(); - -/** - * @brief Set the bot's voice state on a stage channel - * - * **Caveats** - * - * There are currently several caveats for this endpoint: - * - * - `channel_id` must currently point to a stage channel. - * - current user must already have joined `channel_id`. - * - You must have the `MUTE_MEMBERS` permission to unsuppress yourself. You can always suppress yourself. - * - You must have the `REQUEST_TO_SPEAK` permission to request to speak. You can always clear your own request to speak. - * - You are able to set `request_to_speak_timestamp` to any present or future time. - * - * @see dpp::cluster::current_user_set_voice_state - * @see https://discord.com/developers/docs/resources/guild#modify-current-user-voice-state - * @param guild_id Guild to set voice state on - * @param channel_id Stage channel to set voice state on - * @return confirmation returned object on completion - * @param suppress True if the user's audio should be suppressed, false if it should not - * @param request_to_speak_timestamp The time at which we requested to speak, or 0 to clear the request. The time set here must be the current time or in the future. - * @throw std::logic_exception You attempted to set a request_to_speak_timestamp in the past which is not the value of 0. - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation current_user_set_voice_state_sync(snowflake guild_id, snowflake channel_id, bool suppress = false, time_t request_to_speak_timestamp = 0); - -/** - * @brief Get the bot's voice state in a guild without a Gateway connection - * - * @see dpp::cluster::current_user_get_voice_state - * @see https://discord.com/developers/docs/resources/voice#get-current-user-voice-state - * @param guild_id Guild to get the voice state for - * @return voicestate returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") voicestate current_user_get_voice_state_sync(snowflake guild_id); - -/** - * @brief Set a user's voice state on a stage channel - * - * **Caveats** - * - * There are currently several caveats for this endpoint: - * - * - `channel_id` must currently point to a stage channel. - * - User must already have joined `channel_id`. - * - You must have the `MUTE_MEMBERS` permission. (Since suppression is the only thing that is available currently) - * - When unsuppressed, non-bot users will have their `request_to_speak_timestamp` set to the current time. Bot users will not. - * - When suppressed, the user will have their `request_to_speak_timestamp` removed. - * - * @see dpp::cluster::user_set_voice_state - * @see https://discord.com/developers/docs/resources/guild#modify-user-voice-state - * @param user_id The user to set the voice state of - * @param guild_id Guild to set voice state on - * @param channel_id Stage channel to set voice state on - * @return confirmation returned object on completion - * @param suppress True if the user's audio should be suppressed, false if it should not - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation user_set_voice_state_sync(snowflake user_id, snowflake guild_id, snowflake channel_id, bool suppress = false); - -/** - * @brief Get a user's voice state in a guild without a Gateway connection - * - * @see dpp::cluster::user_get_voice_state - * @see https://discord.com/developers/docs/resources/voice#get-user-voice-state - * @param guild_id Guild to get the voice state for - * @param user_id The user to get the voice state of - * @return voicestate returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") voicestate user_get_voice_state_sync(snowflake guild_id, snowflake user_id); - -/** - * @brief Get current user's connections (linked accounts, e.g. steam, xbox). - * This call requires the oauth2 `connections` scope and cannot be executed - * against a bot token. - * @see dpp::cluster::current_user_connections_get - * @see https://discord.com/developers/docs/resources/user#get-user-connections - * @return connection_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") connection_map current_user_connections_get_sync(); - -/** - * @brief Get current (bot) user guilds - * @see dpp::cluster::current_user_get_guilds - * @see https://discord.com/developers/docs/resources/user#get-current-user-guilds - * @return guild_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") guild_map current_user_get_guilds_sync(); - -/** - * @brief Leave a guild - * @see dpp::cluster::current_user_leave_guild - * @see https://discord.com/developers/docs/resources/user#leave-guild - * @param guild_id Guild ID to leave - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation current_user_leave_guild_sync(snowflake guild_id); - -/** - * @brief Get a user by id, without using the cache - * - * @see dpp::cluster::user_get - * @see https://discord.com/developers/docs/resources/user#get-user - * @param user_id User ID to retrieve - * @return user_identified returned object on completion - * @note The user_identified object is a subclass of dpp::user which contains further details if you have the oauth2 identify or email scopes. - * If you do not have these scopes, these fields are empty. You can safely convert a user_identified to user with `dynamic_cast`. - * @note unless you want something special from `dpp::user_identified` or you've turned off caching, you have no need to call this. - * Call `dpp::find_user` instead that looks up the user in the cache rather than a REST call. - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") user_identified user_get_sync(snowflake user_id); - -/** - * @brief Get a user by id, checking in the cache first - * - * @see dpp::cluster::user_get_cached - * @see https://discord.com/developers/docs/resources/user#get-user - * @param user_id User ID to retrieve - * @return user_identified returned object on completion - * @note The user_identified object is a subclass of dpp::user which contains further details if you have the oauth2 identify or email scopes. - * If you do not have these scopes, these fields are empty. You can safely convert a user_identified to user with `dynamic_cast`. - * @note If the user is found in the cache, special values set in `dpp::user_identified` will be undefined. This call should be used - * where you want to for example resolve a user who may no longer be in the bot's guilds, for something like a ban log message. - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") user_identified user_get_cached_sync(snowflake user_id); - -/** - * @brief Get all voice regions - * @see dpp::cluster::get_voice_regions - * @see https://discord.com/developers/docs/resources/voice#list-voice-regions - * @return voiceregion_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") voiceregion_map get_voice_regions_sync(); - -/** - * @brief Get guild voice regions. - * - * Voice regions per guild are somewhat deprecated in preference of per-channel voice regions. - * Returns a list of voice region objects for the guild. Unlike the similar /voice route, this returns VIP servers when - * the guild is VIP-enabled. - * - * @see dpp::cluster::guild_get_voice_regions - * @see https://discord.com/developers/docs/resources/guild#get-guild-voice-regions - * @param guild_id Guild ID to get voice regions for - * @return voiceregion_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") voiceregion_map guild_get_voice_regions_sync(snowflake guild_id); - - -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") webhook create_webhook_sync(const class webhook &wh); - -/** - * @brief Delete a webhook - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * @see dpp::cluster::delete_webhook - * @see https://discord.com/developers/docs/resources/webhook#delete-webhook - * @param webhook_id Webhook ID to delete - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation delete_webhook_sync(snowflake webhook_id); - -/** - * @brief Delete webhook message - * - * @see dpp::cluster::delete_webhook_message - * @see https://discord.com/developers/docs/resources/webhook#delete-webhook-message - * @param wh Webhook to delete message for - * @param message_id Message ID to delete - * @param thread_id ID of the thread the message is in - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation delete_webhook_message_sync(const class webhook &wh, snowflake message_id, snowflake thread_id = 0); - -/** - * @brief Delete webhook with token - * @see dpp::cluster::delete_webhook_with_token - * @see https://discord.com/developers/docs/resources/webhook#delete-webhook-with-token - * @param webhook_id Webhook ID to delete - * @param token Token of webhook to delete - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation delete_webhook_with_token_sync(snowflake webhook_id, const std::string &token); - -/** - * @brief Edit webhook - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * @see dpp::cluster::edit_webhook - * @see https://discord.com/developers/docs/resources/webhook#modify-webhook - * @param wh Webhook to edit - * @return webhook returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") webhook edit_webhook_sync(const class webhook& wh); - -/** - * @brief Edit webhook message - * - * When the content field is edited, the mentions array in the message object will be reconstructed from scratch based on - * the new content. The allowed_mentions field of the edit request controls how this happens. If there is no explicit - * allowed_mentions in the edit request, the content will be parsed with default allowances, that is, without regard to - * whether or not an allowed_mentions was present in the request that originally created the message. - * - * @see dpp::cluster::edit_webhook_message - * @see https://discord.com/developers/docs/resources/webhook#edit-webhook-message - * @note the attachments array must contain all attachments that should be present after edit, including retained and new attachments provided in the request body. - * @param wh Webhook to edit message for - * @param m New message - * @param thread_id ID of the thread the message is in - * @return message returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") message edit_webhook_message_sync(const class webhook &wh, const struct message &m, snowflake thread_id = 0); - -/** - * @brief Edit webhook with token (token is encapsulated in the webhook object) - * @see dpp::cluster::edit_webhook_with_token - * @see https://discord.com/developers/docs/resources/webhook#modify-webhook-with-token - * @param wh Webhook to edit (should include token) - * @return webhook returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") webhook edit_webhook_with_token_sync(const class webhook& wh); - -/** - * @brief Execute webhook - * - * @see dpp::cluster::execute_webhook - * @see https://discord.com/developers/docs/resources/webhook#execute-webhook - * @param wh Webhook to execute - * @param m Message to send - * @param wait waits for server confirmation of message send before response, and returns the created message body - * @param thread_id Send a message to the specified thread within a webhook's channel. The thread will automatically be unarchived - * @param thread_name Name of thread to create (requires the webhook channel to be a forum channel) - * @return message returned object on completion - * @note If the webhook channel is a forum channel, you must provide either `thread_id` or `thread_name`. If `thread_id` is provided, the message will send in that thread. If `thread_name` is provided, a thread with that name will be created in the forum channel. - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") message execute_webhook_sync(const class webhook &wh, const struct message &m, bool wait = false, snowflake thread_id = 0, const std::string& thread_name = ""); - -/** - * @brief Get channel webhooks - * @see dpp::cluster::get_channel_webhooks - * @see https://discord.com/developers/docs/resources/webhook#get-guild-webhooks - * @param channel_id Channel ID to get webhooks for - * @return webhook_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") webhook_map get_channel_webhooks_sync(snowflake channel_id); - -/** - * @brief Get guild webhooks - * @see dpp::cluster::get_guild_webhooks - * @see https://discord.com/developers/docs/resources/webhook#get-guild-webhooks - * @param guild_id Guild ID to get webhooks for - * @return webhook_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") webhook_map get_guild_webhooks_sync(snowflake guild_id); - -/** - * @brief Get webhook - * @see dpp::cluster::get_webhook - * @see https://discord.com/developers/docs/resources/webhook#get-webhook - * @param webhook_id Webhook ID to get - * @return webhook returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") webhook get_webhook_sync(snowflake webhook_id); - -/** - * @brief Get webhook message - * - * @see dpp::cluster::get_webhook_message - * @see https://discord.com/developers/docs/resources/webhook#get-webhook-message - * @param wh Webhook to get the original message for - * @param message_id The message ID - * @param thread_id ID of the thread the message is in - * @return message returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") message get_webhook_message_sync(const class webhook &wh, snowflake message_id, snowflake thread_id = 0); - -/** - * @brief Get webhook using token - * @see dpp::cluster::get_webhook_with_token - * @see https://discord.com/developers/docs/resources/webhook#get-webhook-with-token - * @param webhook_id Webhook ID to retrieve - * @param token Token of webhook - * @return webhook returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") webhook get_webhook_with_token_sync(snowflake webhook_id, const std::string &token); - - -/* End of auto-generated definitions */ diff --git a/include/dpp/coro/awaitable.h b/include/dpp/coro/awaitable.h index e8974c0a88..296ace2490 100644 --- a/include/dpp/coro/awaitable.h +++ b/include/dpp/coro/awaitable.h @@ -44,6 +44,7 @@ struct awaitable_dummy { #include #include #include +#include namespace dpp { diff --git a/include/dpp/discordclient.h b/include/dpp/discordclient.h index d57c2d775b..9e0222466a 100644 --- a/include/dpp/discordclient.h +++ b/include/dpp/discordclient.h @@ -31,25 +31,113 @@ #include #include #include +#include #include +#include #include #include +#include +namespace dpp { +/** + * @brief Discord API version for shard websockets and HTTPS API requests + */ +#define DISCORD_API_VERSION "10" -#define DISCORD_API_VERSION "10" -#define API_PATH "/api/v" DISCORD_API_VERSION -namespace dpp { +/** + * @brief HTTPS Request base path for API calls + */ +#define API_PATH "/api/v" DISCORD_API_VERSION -// Forward declarations +/* Forward declarations */ class cluster; /** - * @brief This is an opaque class containing zlib library specific structures. - * We define it this way so that the public facing D++ library doesn't require - * the zlib headers be available to build against it. + * @brief How many seconds to wait between (re)connections. DO NOT change this. + * It is mandated by the Discord API spec! */ -class zlibcontext; +constexpr time_t RECONNECT_INTERVAL = 5; + +/** + * @brief Represents different event opcodes sent and received on a shard websocket + * + * These are used internally to route frames. + */ +enum shard_frame_type : int { + + /** + * @brief An event was dispatched. + * @note Receive only + */ + ft_dispatch = 0, + + /** + * @brief Fired periodically by the client to keep the connection alive. + * @note Send/Receive + */ + ft_heartbeat = 1, + + /** + * @brief Starts a new session during the initial handshake. + * @note Send only + */ + ft_identify = 2, + + /** + * @brief Update the client's presence. + * @note Send only + */ + ft_presence = 3, + + /** + * @brief Used to join/leave or move between voice channels. + * @note Send only + */ + ft_voice_state_update = 4, + + /** + * @brief Resume a previous session that was disconnected. + * @note Send only + */ + ft_resume = 6, + + /** + * @brief You should attempt to reconnect and resume immediately. + * @note Receive only + */ + ft_reconnect = 7, + + /** + * @brief Request information about offline guild members in a large guild. + * @note Send only + */ + ft_request_guild_members = 8, + + /** + * @brief The session has been invalidated. You should reconnect and identify/resume accordingly. + * @note Receive only + */ + ft_invalid_session = 9, + + /** + * @brief Sent immediately after connecting, contains the heartbeat interval to use. + * @note Receive only + */ + ft_hello = 10, + + /** + * @brief Sent in response to receiving a heartbeat to acknowledge that it has been received. + * @note Receive only + */ + ft_heartbeat_ack = 11, + + /** + * @brief Request information about soundboard sounds in a set of guilds. + * @note Send only + */ + ft_request_soundboard_sounds = 31, +}; /** * @brief Represents a connection to a voice channel. @@ -119,14 +207,14 @@ class DPP_EXPORT voiceconn { * * @return true if ready to connect */ - bool is_ready(); + bool is_ready() const; /** * @brief return true if the connection is active (websocket exists) * * @return true if has an active websocket */ - bool is_active(); + bool is_active() const; /** * @brief Create websocket object and connect it. @@ -165,11 +253,6 @@ class DPP_EXPORT discord_client : public websocket_client */ friend class dpp::cluster; - /** - * @brief True if the shard is terminating - */ - bool terminating; - /** * @brief Disconnect from the connected voice channel on a guild * @@ -179,38 +262,40 @@ class DPP_EXPORT discord_client : public websocket_client */ void disconnect_voice_internal(snowflake guild_id, bool send_json = true); -private: - /** - * @brief Mutex for message queue + * @brief Start connecting the websocket + * + * Called from the constructor, or during reconnection */ - std::shared_mutex queue_mutex; + void start_connecting(); /** - * @brief Queue of outbound messages + * @brief Stores the most recent ping message on this shard, which we check + * for to monitor latency */ - std::deque message_queue; + std::string last_ping_message; + +private: /** - * @brief Thread this shard is executing on + * @brief Mutex for message queue */ - std::thread* runner; + std::shared_mutex queue_mutex; /** - * @brief Run shard loop under a thread. - * Calls discord_client::run() from within a std::thread. + * @brief Mutex for zlib pointer */ - void thread_run(); + std::mutex zlib_mutex; /** - * @brief If true, stream compression is enabled + * @brief Queue of outbound messages */ - bool compressed; + std::deque message_queue; /** - * @brief ZLib decompression buffer + * @brief If true, stream compression is enabled */ - unsigned char* decomp_buffer; + bool compressed; /** * @brief Decompressed string @@ -223,12 +308,7 @@ class DPP_EXPORT discord_client : public websocket_client * are wrapped within this opaque object so that this header * file does not bring in a dependency on zlib.h. */ - zlibcontext* zlib; - - /** - * @brief Total decompressed received bytes - */ - uint64_t decompressed_total; + std::unique_ptr zlib{}; /** * @brief Last connect time of cluster @@ -243,7 +323,7 @@ class DPP_EXPORT discord_client : public websocket_client /** * @brief ETF parser for when in ws_etf mode */ - class etf_parser* etf; + std::unique_ptr etf; /** * @brief Convert a JSON object to string. @@ -255,17 +335,6 @@ class DPP_EXPORT discord_client : public websocket_client */ std::string jsonobj_to_string(const nlohmann::json& json); - /** - * @brief Initialise ZLib (websocket compression) - * @throw dpp::exception if ZLib cannot be initialised - */ - void setup_zlib(); - - /** - * @brief Shut down ZLib (websocket compression) - */ - void end_zlib(); - /** * @brief Update the websocket hostname with the resume url * from the last READY event @@ -303,11 +372,6 @@ class DPP_EXPORT discord_client : public websocket_client */ uint32_t max_shards; - /** - * @brief Thread ID - */ - std::thread::native_handle_type thread_id; - /** * @brief Last sequence number received, for resumes and pings */ @@ -381,7 +445,7 @@ class DPP_EXPORT discord_client : public websocket_client * @param severity The log level from dpp::loglevel * @param msg The log message to output */ - virtual void log(dpp::loglevel severity, const std::string &msg) const; + virtual void log(dpp::loglevel severity, const std::string &msg) const override; /** * @brief Handle an event (opcode 0) @@ -412,8 +476,11 @@ class DPP_EXPORT discord_client : public websocket_client */ uint64_t get_channel_count(); - /** Fires every second from the underlying socket I/O loop, used for sending heartbeats */ - virtual void one_second_timer(); + /** + * @brief Fires every second from the underlying socket I/O loop, used for sending heartbeats + * and any queued outbound websocket frames. + */ + virtual void one_second_timer() override; /** * @brief Queue a message to be sent via the websocket @@ -467,14 +534,27 @@ class DPP_EXPORT discord_client : public websocket_client */ discord_client(dpp::cluster* _cluster, uint32_t _shard_id, uint32_t _max_shards, const std::string &_token, uint32_t intents = 0, bool compressed = true, websocket_protocol_t ws_protocol = ws_json); + /** + * @brief Construct a discord_client object from another discord_client object + * Used when resuming, the url to connect to will be taken from the resume url of the + * other object, along with the seq number. + * + * @param old Previous connection to resume from + * @param sequence Sequence number of previous session + * @param session_id Session ID of previous session + */ + explicit discord_client(discord_client& old, uint64_t sequence, const std::string& session_id); + /** * @brief Destroy the discord client object */ - virtual ~discord_client(); + virtual ~discord_client() = default; /** - * @brief Get the decompressed bytes in objectGet decompressed total bytes received - * @return uint64_t bytes received + * @brief Get decompressed total bytes received + * + * This will always return 0 if the connection is not compressed + * @return uint64_t compressed bytes received */ uint64_t get_decompressed_bytes_in(); @@ -484,21 +564,24 @@ class DPP_EXPORT discord_client : public websocket_client * @param opcode The type of frame, e.g. text or binary * @returns True if a frame has been handled */ - virtual bool handle_frame(const std::string &buffer, ws_opcode opcode); + virtual bool handle_frame(const std::string &buffer, ws_opcode opcode) override; /** * @brief Handle a websocket error. * @param errorcode The error returned from the websocket */ - virtual void error(uint32_t errorcode); + virtual void error(uint32_t errorcode) override; /** * @brief Start and monitor I/O loop. - * @note this is a blocking call and is usually executed within a - * thread by whatever creates the object. */ void run(); + /** + * @brief Called when the HTTP socket is closed + */ + virtual void on_disconnect() override; + /** * @brief Connect to a voice channel * diff --git a/include/dpp/discordvoiceclient.h b/include/dpp/discordvoiceclient.h index ad0c5e93fa..2fefa11524 100644 --- a/include/dpp/discordvoiceclient.h +++ b/include/dpp/discordvoiceclient.h @@ -39,6 +39,7 @@ #include #include #include +#include #include #include #include @@ -200,7 +201,6 @@ struct dave_binary_header_t { /** * Get the data package from the packed binary frame, as a vector of uint8_t * for use in the libdave functions - * @return data blob */ [[nodiscard]] std::vector get_data() const; @@ -267,20 +267,10 @@ class DPP_EXPORT discord_voice_client : public websocket_client */ std::deque message_queue; - /** - * @brief Thread this connection is executing on - */ - std::thread* runner; - - /** - * @brief Run shard loop under a thread - */ - void thread_run(); - /** * @brief Last connect time of voice session */ - time_t connect_time; + time_t connect_time{}; /* * @brief For mixing outgoing voice data. @@ -295,12 +285,12 @@ class DPP_EXPORT discord_voice_client : public websocket_client /** * @brief Port number of UDP/RTP endpoint */ - uint16_t port; + uint16_t port{}; /** * @brief SSRC value */ - uint64_t ssrc; + uint64_t ssrc{}; /** * @brief List of supported audio encoding modes @@ -310,7 +300,7 @@ class DPP_EXPORT discord_voice_client : public websocket_client /** * @brief Timescale in nanoseconds */ - uint64_t timescale; + uint64_t timescale{}; /** * @brief Output buffer @@ -437,26 +427,36 @@ class DPP_EXPORT discord_voice_client : public websocket_client /** * @brief If true, audio packet sending is paused */ - bool paused; + bool paused{}; /** * @brief Whether has sent 5 frame of silence before stopping on pause. * * This is to avoid unintended Opus interpolation with subsequent transmissions. */ - bool sent_stop_frames; + bool sent_stop_frames{}; + + /** + * @brief Number of times we have tried to reconnect in the last few seconds + */ + size_t times_looped{0}; + + /** + * @brief Last time we reconnected + */ + time_t last_loop_time{0}; #ifdef HAVE_VOICE /** * @brief libopus encoder */ - OpusEncoder* encoder; + OpusEncoder* encoder{}; /** * @brief libopus repacketizer * (merges frames into one packet) */ - OpusRepacketizer* repacketizer; + OpusRepacketizer* repacketizer{}; /** * @brief This holds the state information for DAVE E2EE. @@ -498,14 +498,14 @@ class DPP_EXPORT discord_voice_client : public websocket_client /** * @brief File descriptor for UDP connection */ - dpp::socket fd; + dpp::socket fd{}; /** * @brief Secret key for encrypting voice. * If it has been sent, this contains a sequence of exactly 32 bytes * (secret_key_size) and has_secret_key is set to true. */ - std::array secret_key; + std::array secret_key{}; /** * @brief True if the voice client has a secret key @@ -516,21 +516,21 @@ class DPP_EXPORT discord_voice_client : public websocket_client * @brief Sequence number of outbound audio. This is incremented * once per frame sent. */ - uint16_t sequence; + uint16_t sequence{}; /** * @brief Last received sequence from gateway. * * Needed for heartbeat and resume payload. */ - int32_t receive_sequence; + int32_t receive_sequence{}; /** * @brief Timestamp value used in outbound audio. Each packet * has the timestamp value which is incremented to match * how many frames are sent. */ - uint32_t timestamp; + uint32_t timestamp{}; /** * @brief Each packet should have a nonce, a 32-bit incremental @@ -541,7 +541,7 @@ class DPP_EXPORT discord_voice_client : public websocket_client * * Current initial value is hardcoded to 1. */ - uint32_t packet_nonce; + uint32_t packet_nonce{}; /** * @brief Last sent packet high-resolution timestamp @@ -551,7 +551,7 @@ class DPP_EXPORT discord_voice_client : public websocket_client /** * @brief Fraction of the sleep that was not executed after the last audio packet was sent */ - std::chrono::nanoseconds last_sleep_remainder; + std::chrono::nanoseconds last_sleep_remainder{}; /** * @brief Maps receiving ssrc to user id @@ -563,7 +563,7 @@ class DPP_EXPORT discord_voice_client : public websocket_client * When this moves from false to true, this causes the * client to send the 'talking' notification to the websocket. */ - bool sending; + bool sending{}; /** * @brief Number of track markers in the buffer. For example if there @@ -574,7 +574,7 @@ class DPP_EXPORT discord_voice_client : public websocket_client * If the buffer is empty, there are zero tracks in the * buffer. */ - uint32_t tracks; + uint32_t tracks{}; /** * @brief Meta data associated with each track. @@ -586,7 +586,7 @@ class DPP_EXPORT discord_voice_client : public websocket_client /** * @brief Encoding buffer for opus repacketizer and encode */ - uint8_t encode_buffer[65536]; + uint8_t encode_buffer[65536]{}; /** * @brief DAVE - Discord Audio Visual Encryption @@ -623,25 +623,7 @@ class DPP_EXPORT discord_voice_client : public websocket_client int udp_recv(char* data, size_t max_length); /** - * @brief This hooks the ssl_client, returning the file - * descriptor if we want to send buffered data, or - * -1 if there is nothing to send - * - * @return int file descriptor or -1 - */ - dpp::socket want_write(); - - /** - * @brief This hooks the ssl_client, returning the file - * descriptor if we want to receive buffered data, or - * -1 if we are not wanting to receive - * - * @return int file descriptor or -1 - */ - dpp::socket want_read(); - - /** - * @brief Called by ssl_client when the socket is ready + * @brief Called by socketengine when the socket is ready * for writing, at this point we pick the head item off * the buffer and send it. So long as it doesn't error * completely, we pop it off the head of the queue. @@ -649,7 +631,7 @@ class DPP_EXPORT discord_voice_client : public websocket_client void write_ready(); /** - * @brief Called by ssl_client when there is data to be + * @brief Called by socketengine when there is data to be * read. At this point we insert that data into the * input queue. * @throw dpp::voice_exception if voice support is not compiled into D++ @@ -710,47 +692,52 @@ class DPP_EXPORT discord_voice_client : public websocket_client */ void update_ratchets(bool force = false); + /** + * @brief Called in constructor and on reconnection of websocket + */ + void setup(); + + /** + * @brief Events for UDP Socket IO + */ + dpp::socket_events udp_events; + public: /** * @brief Owning cluster */ - class dpp::cluster* creator; + class dpp::cluster* creator{}; /** * @brief True when the thread is shutting down */ - bool terminating; + bool terminating{}; /** * @brief The gain value for the end of the current voice iteration. */ - float end_gain; + float end_gain{}; /** * @brief The gain value for the current voice iteration. */ - float current_gain; + float current_gain{}; /** * @brief The amount to increment each successive sample for, for the current voice iteration. */ - float increment; + float increment{}; /** * @brief Heartbeat interval for sending heartbeat keepalive */ - uint32_t heartbeat_interval; + uint32_t heartbeat_interval{}; /** * @brief Last voice channel websocket heartbeat */ - time_t last_heartbeat; - - /** - * @brief Thread ID - */ - std::thread::native_handle_type thread_id; + time_t last_heartbeat{}; /** * @brief Discord voice session token @@ -846,13 +833,13 @@ class DPP_EXPORT discord_voice_client : public websocket_client * @param severity The log level from dpp::loglevel * @param msg The log message to output */ - virtual void log(dpp::loglevel severity, const std::string &msg) const; + virtual void log(dpp::loglevel severity, const std::string &msg) const override; /** * @brief Fires every second from the underlying socket I/O loop, used for sending heartbeats * @throw dpp::exception if the socket needs to disconnect */ - virtual void one_second_timer(); + virtual void one_second_timer() override; /** * @brief voice client is ready to stream audio. @@ -899,7 +886,7 @@ class DPP_EXPORT discord_voice_client : public websocket_client /** * @brief Destroy the discord voice client object */ - virtual ~discord_voice_client(); + virtual ~discord_voice_client() override; /** * @brief Handle JSON from the websocket. @@ -908,16 +895,16 @@ class DPP_EXPORT discord_voice_client : public websocket_client * @return bool True if a frame has been handled * @throw dpp::exception If there was an error processing the frame, or connection to UDP socket failed */ - virtual bool handle_frame(const std::string &buffer, ws_opcode opcode); + virtual bool handle_frame(const std::string &buffer, ws_opcode opcode) override; /** * @brief Handle a websocket error. * @param errorcode The error returned from the websocket */ - virtual void error(uint32_t errorcode); + virtual void error(uint32_t errorcode) override; /** - * @brief Start and monitor I/O loop + * @brief Start and monitor websocket I/O */ void run(); @@ -1269,6 +1256,11 @@ class DPP_EXPORT discord_voice_client : public websocket_client * @param rmap Roster map */ void process_mls_group_rosters(const std::map>& rmap); + + /** + * @brief Called on websocket disconnection + */ + void on_disconnect() override; }; } diff --git a/include/dpp/dispatcher.h b/include/dpp/dispatcher.h index be08488b7b..dad53f509c 100644 --- a/include/dpp/dispatcher.h +++ b/include/dpp/dispatcher.h @@ -85,9 +85,13 @@ struct DPP_EXPORT event_dispatch_t { /** * @brief Shard the event came from. - * Note that for some events, notably voice events, this may be nullptr. */ - discord_client* from = nullptr; + uint32_t shard = 0; + + /** + * @brief Cluster owning the event dispatch + */ + dpp::cluster* owner = nullptr; /** * @brief Whether the event was cancelled using cancel_event(). @@ -116,18 +120,24 @@ struct DPP_EXPORT event_dispatch_t { /** * @brief Construct a new event_dispatch_t object * - * @param client The shard the event originated on. May be a nullptr, e.g. for voice events + * @param shard_id The shard the event originated on. * @param raw Raw event data as JSON or ETF */ - event_dispatch_t(discord_client* client, const std::string& raw); + event_dispatch_t(dpp::cluster* creator, uint32_t shard_id, const std::string& raw); + + /** + * @brief Returns the shard object for the events shard id + * @return discord client object + */ + discord_client* from() const; /** * @brief Construct a new event_dispatch_t object * - * @param client The shard the event originated on. May be a nullptr, e.g. for voice events + * @param shard_id The shard the event originated on. * @param raw Raw event data as JSON or ETF */ - event_dispatch_t(discord_client* client, std::string&& raw); + event_dispatch_t(dpp::cluster* creator, uint32_t shard_id, std::string&& raw); /** * @brief Copy another event_dispatch_t object @@ -2018,28 +2028,28 @@ struct DPP_EXPORT voice_receive_t : public event_dispatch_t { /** * @brief Construct a new voice receive t object * - * @param client The shard the event originated on. - * WILL ALWAYS be NULL. + * @param creator The creating cluster + * @param shard_id Shard the voice channel exists on * @param raw Raw event text as UDP packet. * @param vc owning voice client pointer * @param _user_id user id who is speaking, 0 for a mix of all user audio * @param pcm user audio to set * @param length length of user audio in bytes */ - voice_receive_t(discord_client* client, const std::string& raw, class discord_voice_client* vc, snowflake _user_id, const uint8_t* pcm, size_t length); + voice_receive_t(dpp::cluster* creator, uint32_t shard_id, const std::string& raw, class discord_voice_client* vc, snowflake _user_id, const uint8_t* pcm, size_t length); /** * @brief Construct a new voice receive t object * - * @param client The shard the event originated on. - * WILL ALWAYS be NULL. + * @param creator The creating cluster + * @param shard_id Shard the voice channel exists on * @param raw Raw event text as UDP packet. * @param vc owning voice client pointer * @param _user_id user id who is speaking, 0 for a mix of all user audio * @param pcm user audio to set * @param length length of user audio in bytes */ - voice_receive_t(discord_client* client, std::string&& raw, class discord_voice_client* vc, snowflake _user_id, const uint8_t* pcm, size_t length); + voice_receive_t(dpp::cluster* creator, uint32_t shard_id, std::string&& raw, class discord_voice_client* vc, snowflake _user_id, const uint8_t* pcm, size_t length); /** * @brief Voice client diff --git a/include/dpp/dns.h b/include/dpp/dns.h index 48cebcd563..3d77db5282 100644 --- a/include/dpp/dns.h +++ b/include/dpp/dns.h @@ -32,6 +32,7 @@ #include #include #include +#include #include namespace dpp { @@ -40,7 +41,7 @@ namespace dpp { * @brief Represents a cached DNS result. * Used by the ssl_client class to store cached copies of dns lookups. */ - struct dns_cache_entry { + struct DPP_EXPORT dns_cache_entry { /** * @brief Resolved address metadata */ @@ -83,7 +84,7 @@ namespace dpp { /** * @brief Cache container type */ - using dns_cache_t = std::unordered_map; + using dns_cache_t = std::unordered_map>; /** * @brief Resolve a hostname to an addrinfo @@ -93,5 +94,5 @@ namespace dpp { * @return dns_cache_entry* First IP address associated with the hostname DNS record * @throw dpp::connection_exception On failure to resolve hostname */ - const dns_cache_entry* resolve_hostname(const std::string& hostname, const std::string& port); -} + DPP_EXPORT const dns_cache_entry *resolve_hostname(const std::string &hostname, const std::string &port); + } diff --git a/include/dpp/dpp.h b/include/dpp/dpp.h index 5e40a88a0e..2d44ee9040 100644 --- a/include/dpp/dpp.h +++ b/include/dpp/dpp.h @@ -37,6 +37,7 @@ #include #include #include +#include #include #include #include @@ -69,9 +70,9 @@ #include #include #include -#include #include #include #include #include #include +#include diff --git a/include/dpp/exception.h b/include/dpp/exception.h index 3d5f41ff08..9d6dd2d69d 100644 --- a/include/dpp/exception.h +++ b/include/dpp/exception.h @@ -98,6 +98,7 @@ enum exception_error_code { err_no_voice_support = 29, err_invalid_voice_packet_length = 30, err_opus = 31, + err_cant_start_shard = 32, err_etf = 33, err_cache = 34, err_icon_size = 35, diff --git a/include/dpp/httpsclient.h b/include/dpp/httpsclient.h index 5a0ff481cf..a347fc19c4 100644 --- a/include/dpp/httpsclient.h +++ b/include/dpp/httpsclient.h @@ -33,9 +33,7 @@ namespace dpp { static inline const std::string http_version = "DiscordBot (https://github.com/brainboxdotcc/DPP, " - + to_hex(DPP_VERSION_MAJOR, false) + "." - + to_hex(DPP_VERSION_MINOR, false) + "." - + to_hex(DPP_VERSION_PATCH, false) + ")"; + + to_hex(DPP_VERSION_MAJOR, false) + "." + to_hex(DPP_VERSION_MINOR, false) + "." + to_hex(DPP_VERSION_PATCH, false) + ")"; static inline constexpr const char* DISCORD_HOST = "https://discord.com"; @@ -111,7 +109,7 @@ struct http_connect_info { /** * @brief True if the connection should be SSL */ - bool is_ssl; + bool is_ssl{}; /** * @brief The request scheme, e.g. 'https' or 'http' @@ -127,18 +125,16 @@ struct http_connect_info { * @brief The port number, either determined from the scheme, * or from the part of the hostname after a colon ":" character */ - uint16_t port; + uint16_t port{}; }; +using https_client_completion_event = std::function; + /** * @brief Implements a HTTPS socket client based on the SSL client. * @note plaintext HTTP without SSL is also supported via a "downgrade" setting */ class DPP_EXPORT https_client : public ssl_client { - /** - * @brief Current connection state - */ - http_state state; /** * @brief The type of the request, e.g. GET, POST @@ -209,19 +205,11 @@ class DPP_EXPORT https_client : public ssl_client { */ std::multimap response_headers; - /** - * @brief Handle input buffer - * - * @param buffer Buffer to read - * @return returns true if the connection should remain open - */ - bool do_buffer(std::string& buffer); - protected: /** * @brief Start the connection */ - virtual void connect(); + virtual void connect() override; /** * @brief Get request state @@ -233,8 +221,18 @@ class DPP_EXPORT https_client : public ssl_client { /** * @brief If true the response timed out while waiting */ - bool timed_out; - + bool timed_out; + + /** + * @brief Function to call when HTTP request is completed + */ + https_client_completion_event completed; + + /** + * @brief Current connection state + */ + http_state state; + /** * @brief Connect to a specific HTTP(S) server and complete a request. * @@ -255,13 +253,14 @@ class DPP_EXPORT https_client : public ssl_client { * @param plaintext_connection Set to true to make the connection plaintext (turns off SSL) * @param request_timeout How many seconds before the connection is considered failed if not finished * @param protocol Request HTTP protocol (default: 1.1) + * @param done Function to call when https_client request is completed */ - https_client(const std::string &hostname, uint16_t port = 443, const std::string &urlpath = "/", const std::string &verb = "GET", const std::string &req_body = "", const http_headers& extra_headers = {}, bool plaintext_connection = false, uint16_t request_timeout = 5, const std::string &protocol = "1.1"); + https_client(cluster* creator, const std::string &hostname, uint16_t port = 443, const std::string &urlpath = "/", const std::string &verb = "GET", const std::string &req_body = "", const http_headers& extra_headers = {}, bool plaintext_connection = false, uint16_t request_timeout = 5, const std::string &protocol = "1.1", https_client_completion_event done = {}); /** * @brief Destroy the https client object */ - virtual ~https_client() = default; + virtual ~https_client() override; /** * @brief Build a multipart content from a set of files and some json @@ -279,17 +278,17 @@ class DPP_EXPORT https_client : public ssl_client { * * @param buffer The buffer contents. Can modify this value removing the head elements when processed. */ - virtual bool handle_buffer(std::string &buffer); + virtual bool handle_buffer(std::string &buffer) override; /** * @brief Close HTTPS socket */ - virtual void close(); + virtual void close() override; /** * @brief Fires every second from the underlying socket I/O loop, used for timeouts */ - virtual void one_second_timer(); + virtual void one_second_timer() override; /** * @brief Get a HTTP response header @@ -353,7 +352,6 @@ class DPP_EXPORT https_client : public ssl_client { * @return Split URL */ static http_connect_info get_host_info(std::string url); - }; } diff --git a/include/dpp/queues.h b/include/dpp/queues.h index c502647936..90ac8a4028 100644 --- a/include/dpp/queues.h +++ b/include/dpp/queues.h @@ -25,19 +25,19 @@ #include #include #include -#include #include +#include #include #include -#include #include +#include namespace dpp { /** * @brief Error values. Most of these are currently unused in https_client. */ -enum http_error { +enum http_error : uint8_t { /** * @brief Request successful. */ @@ -171,8 +171,11 @@ struct DPP_EXPORT http_request_completion_t { * @brief Results of HTTP requests are called back to these std::function types. * * @note Returned http_completion_events are called ASYNCHRONOUSLY in your - * code which means they execute in a separate thread. The completion events - * arrive in order. + * code which means they execute in a separate thread, results for the requests going + * into a dpp::thread_pool. Completion events may not arrive in order depending on if + * one request takes longer than another. Using the callbacks or using coroutines + * correctly ensures that the order they arrive in the queue does not negatively affect + * your code. */ typedef std::function http_completion_event; @@ -231,6 +234,12 @@ class DPP_EXPORT http_request { * @brief True for requests that are not going to discord (rate limits code skipped). */ bool non_discord; + + /** + * @brief HTTPS client + */ + std::unique_ptr cli; + public: /** * @brief Endpoint name @@ -294,13 +303,6 @@ class DPP_EXPORT http_request { */ std::string protocol; - /** - * @brief How many seconds before the connection is considered failed if not finished - * - * @deprecated Please now use dpp::cluster::request_timeout - */ - DPP_DEPRECATED("Please now use dpp::cluster::request_timeout") time_t request_timeout; - /** * @brief Constructor. When constructing one of these objects it should be passed to request_queue::post_request(). * @param _endpoint The API endpoint, e.g. /api/guilds @@ -340,9 +342,8 @@ class DPP_EXPORT http_request { * @param _mimetype POST data mime type * @param _headers HTTP headers to send * @param http_protocol HTTP protocol - * @param _request_timeout How many seconds before the connection is considered failed if not finished */ - http_request(const std::string &_url, http_completion_event completion, http_method method = m_get, const std::string &_postdata = "", const std::string &_mimetype = "text/plain", const std::multimap &_headers = {}, const std::string &http_protocol = "1.1", time_t _request_timeout = 5); + http_request(const std::string &_url, http_completion_event completion, http_method method = m_get, const std::string &_postdata = "", const std::string &_mimetype = "text/plain", const std::multimap &_headers = {}, const std::string &http_protocol = "1.1"); /** * @brief Destroy the http request object @@ -357,12 +358,22 @@ class DPP_EXPORT http_request { /** * @brief Execute the HTTP request and mark the request complete. + * @param processor Request concurrency queue this request was created by * @param owner creating cluster */ - http_request_completion_t run(class cluster* owner); + http_request_completion_t run(class request_concurrency_queue* processor, class cluster* owner); - /** @brief Returns true if the request is complete */ + /** + * @brief Returns true if the request is complete + * @return True if completed + * */ bool is_completed(); + + /** + * @brief Get the HTTPS client used to perform this request, or nullptr if there is none + * @return https_client object + */ + https_client* get_client() const; }; /** @@ -398,27 +409,32 @@ struct DPP_EXPORT bucket_t { /** - * @brief Represents a thread in the thread pool handling requests to HTTP(S) servers. - * There are several of these, the total defined by a constant in queues.cpp, and each + * @brief Represents a timer instance in a pool handling requests to HTTP(S) servers. + * There are several of these, the total defined by a constant in cluster.cpp, and each * one will always receive requests for the same rate limit bucket based on its endpoint * portion of the url. This makes rate limit handling reliable and easy to manage. - * Each of these also has its own mutex, so that requests are less likely to block while - * waiting for internal containers to be usable. + * Each of these also has its own mutex, making it thread safe to call and use these + * from anywhere in the code. */ -class DPP_EXPORT in_thread { -private: +class DPP_EXPORT request_concurrency_queue { +public: + /** + * @brief Queue index + */ + int in_index{0}; + /** * @brief True if ending. */ std::atomic terminating; /** - * @brief Request queue that owns this in_thread. + * @brief Request queue that owns this request_concurrency_queue. */ class request_queue* requests; /** - * @brief The cluster that owns this in_thread. + * @brief The cluster that owns this request_concurrency_queue. */ class cluster* creator; @@ -428,14 +444,12 @@ class DPP_EXPORT in_thread { std::shared_mutex in_mutex; /** - * @brief Inbound queue thread. + * @brief Inbound queue timer. The timer is called every second, + * and when it wakes up it checks for requests pending to be sent in the queue. + * If there are any requests and we are not waiting on rate limit, it will send them, + * else it will wait for the rate limit to expire. */ - std::thread* in_thr; - - /** - * @brief Inbound queue condition, signalled when there are requests to fulfill. - */ - std::condition_variable in_ready; + dpp::timer in_timer; /** * @brief Rate-limit bucket counters. @@ -448,34 +462,39 @@ class DPP_EXPORT in_thread { std::vector> requests_in; /** - * @brief Inbound queue thread loop. - * @param index Thread index + * @brief Requests to remove after a set amount of time has passed */ - void in_loop(uint32_t index); -public: + std::vector> removals; + /** - * @brief Construct a new in thread object + * @brief Timer callback + * @param index Index ID for this timer + */ + void tick_and_deliver_requests(uint32_t index); + + /** + * @brief Construct a new concurrency queue object * * @param owner Owning cluster * @param req_q Owning request queue - * @param index Thread index number + * @param index Queue index number, uniquely identifies this queue for hashing */ - in_thread(class cluster* owner, class request_queue* req_q, uint32_t index); + request_concurrency_queue(class cluster* owner, class request_queue* req_q, uint32_t index); /** - * @brief Destroy the in thread object - * This will end the thread that is owned by this object by joining it. + * @brief Destroy the concurrency queue object + * This will stop the timer. */ - ~in_thread(); + ~request_concurrency_queue(); /** - * @brief Terminates the thread - * This will end the thread that is owned by this object, but will not join it. + * @brief Flags the queue as terminating + * This will set the internal atomic bool that indicates this queue is to accept no more requests */ void terminate(); /** - * @brief Post a http_request to this thread. + * @brief Post a http_request to this queue. * * @param req http_request to post. The pointer will be freed when it has * been executed. @@ -489,48 +508,30 @@ class DPP_EXPORT in_thread { * * It ensures asynchronous delivery of events and queueing of requests. * - * It will spawn two threads, one to make outbound HTTP requests and push the returned - * results into a queue, and the second to call the callback methods with these results. - * They are separated so that if the user decides to take a long time processing a reply - * in their callback it won't affect when other requests are sent, and if a HTTP request - * takes a long time due to latency, it won't hold up user processing. + * It will spawn multiple timers to make outbound HTTP requests and then call the callbacks + * of those requests on completion within the dpp::thread_pool for the cluster. + * If the user decides to take a long time processing a reply in their callback it won't affect + * when other requests are sent, and if a HTTP request takes a long time due to latency, it won't + * hold up user processing. * * There are usually two request_queue objects in each dpp::cluster, one of which is used * internally for the various REST methods to Discord such as sending messages, and the other - * used to support user REST calls via dpp::cluster::request(). + * used to support user REST calls via dpp::cluster::request(). They are separated so that the + * one for user requests can be specifically configured to never ever send the Discord token + * unless it is explicitly placed into the request, for security reasons. */ class DPP_EXPORT request_queue { -protected: +public: /** - * @brief Required so in_thread can access these member variables + * @brief Required so request_concurrency_queue can access these member variables */ - friend class in_thread; + friend class request_concurrency_queue; /** * @brief The cluster that owns this request_queue */ class cluster* creator; - /** - * @brief Outbound queue mutex thread safety - */ - std::shared_mutex out_mutex; - - /** - * @brief Outbound queue thread - * Note that although there are many 'in queues', which handle the HTTP requests, - * there is only ever one 'out queue' which dispatches the results to the caller. - * This is to simplify thread management in bots that use the library, as less mutexing - * and thread safety boilerplate is required. - */ - std::thread* out_thread; - - /** - * @brief Outbound queue condition. - * Signalled when there are requests completed to call callbacks for. - */ - std::condition_variable out_ready; - /** * @brief A completed request. Contains both the request and the response */ @@ -547,111 +548,69 @@ class DPP_EXPORT request_queue { }; /** - * @brief Completed requests queue - */ - std::queue responses_out; - - /** - * @brief A vector of inbound request threads forming a pool. - * There are a set number of these defined by a constant in queues.cpp. A request is always placed + * @brief A vector of timers forming a pool. + * + * There are a set number of these defined by a constant in cluster.cpp. A request is always placed * on the same element in this vector, based upon its url, so that two conditions are satisfied: - * 1) Any requests for the same ratelimit bucket are handled by the same thread in the pool so that + * + * 1) Any requests for the same ratelimit bucket are handled by the same concurrency queue in the pool so that * they do not create unnecessary 429 errors, * 2) Requests for different endpoints go into different buckets, so that they may be requested in parallel - * A global ratelimit event pauses all threads in the pool. These are few and far between. - */ - std::vector> requests_in; - - /** - * @brief A request queued for deletion in the queue. - */ - struct queued_deleting_request { - /** - * @brief Time to delete the request - */ - time_t time_to_delete; - - /** - * @brief The request to delete - */ - completed_request request; - - /** - * @brief Comparator for sorting purposes - * @param other Other queued request to compare the deletion time with - * @return bool Whether this request comes before another in strict ordering - */ - bool operator<(const queued_deleting_request& other) const noexcept; - - /** - * @brief Comparator for sorting purposes - * @param time Time to compare with - * @return bool Whether this request's deletion time is lower than the time given, for strict ordering - */ - bool operator<(time_t time) const noexcept; - }; - - /** - * @brief Completed requests to delete. Sorted by deletion time + * A global ratelimit event pauses all timers in the pool. These are few and far between. */ - std::vector responses_to_delete; + std::vector> requests_in; /** - * @brief Set to true if the threads should terminate + * @brief Set to true if the timers should terminate. + * When this is set to true no further requests are accepted to the queues. */ std::atomic terminating; /** - * @brief True if globally rate limited - makes the entire request thread wait + * @brief True if globally rate limited + * + * When globally rate limited the concurrency queues associated with this request queue + * will not process any requests in their timers until the global rate limit expires. */ bool globally_ratelimited; /** - * @brief How many seconds we are globally rate limited for + * @brief When we are globally rate limited until (unix epoch) * - * @note Only if globally_ratelimited is true. + * @note Only valid if globally_rate limited is true. If we are globally rate limited, + * queues in this class will not process requests until the current unix epoch time + * is greater than this time. */ - uint64_t globally_limited_for; + time_t globally_limited_until; /** - * @brief Number of request threads in the thread pool + * @brief Number of request queues in the pool. This is the direct size of the requests_in + * vector. */ - uint32_t in_thread_pool_size; - - /** - * @brief Outbound queue thread loop - */ - void out_loop(); -public: + uint32_t in_queue_pool_size; /** * @brief constructor * @param owner The creating cluster. - * @param request_threads The number of http request threads to allocate to the threadpool. - * By default eight threads are allocated. - * Side effects: Creates threads for the queue - */ - request_queue(class cluster* owner, uint32_t request_threads = 8); - - /** - * @brief Add more request threads to the library at runtime. - * @note You should do this at a quiet time when there are few requests happening. - * This will reorganise the hashing used to place requests into the thread pool so if you do - * this while the bot is busy there is a small chance of receiving "429 rate limited" errors. - * @param request_threads Number of threads to add. It is not possible to scale down at runtime. - * @return reference to self + * @param request_concurrency The number of http request queues to allocate. + * Each request queue is a dpp::timer which ticks every second looking for new + * requests to run. The timer will hold back requests if we are waiting as to comply + * with rate limits. Adding a request to this class will cause the queue it is placed in + * to run immediately but this cannot override rate limits. + * By default eight concurrency queues are allocated. + * Side effects: Creates timers for the queue */ - request_queue& add_request_threads(uint32_t request_threads); + request_queue(class cluster* owner, uint32_t request_concurrency = 8); /** - * @brief Get the request thread count - * @return uint32_t number of request threads that are active + * @brief Get the request queue concurrency count + * @return uint32_t number of request queues that are active */ - uint32_t get_request_thread_count() const; + uint32_t get_request_queue_count() const; /** * @brief Destroy the request queue object. - * Side effects: Joins and deletes queue threads + * Side effects: Ends and deletes concurrency timers */ ~request_queue(); @@ -669,6 +628,12 @@ class DPP_EXPORT request_queue { * @return true if globally rate limited */ bool is_globally_ratelimited() const; + + /** + * @brief Returns the number of active requests on this queue + * @return Total number of active requests + */ + size_t get_active_request_count() const; }; } diff --git a/include/dpp/restrequest.h b/include/dpp/restrequest.h index 2fcff85a77..2dcb4f56cf 100644 --- a/include/dpp/restrequest.h +++ b/include/dpp/restrequest.h @@ -293,5 +293,4 @@ template inline void rest_request_vector(dpp::cluster* c, const char* b }); } - } diff --git a/include/dpp/restresults.h b/include/dpp/restresults.h index a78b9e9dfe..902a72ada4 100644 --- a/include/dpp/restresults.h +++ b/include/dpp/restresults.h @@ -38,7 +38,6 @@ #include #include #include -#include #include #include #include @@ -53,6 +52,11 @@ namespace dpp { */ typedef std::map shard_list; +/** + * @brief List of shards awaiting reconnection, by id with earliest possible reconnect time + */ +typedef std::map reconnect_list; + /** * @brief Represents the various information from the 'get gateway bot' api call */ diff --git a/include/dpp/socketengine.h b/include/dpp/socketengine.h new file mode 100644 index 0000000000..051aa2f801 --- /dev/null +++ b/include/dpp/socketengine.h @@ -0,0 +1,273 @@ +/************************************************************************************ + * + * 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. + * + ************************************************************************************/ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include + +namespace dpp { + +/** + * @brief Types of IO events a socket may subscribe to. + */ +enum socket_event_flags : uint8_t { + /** + * @brief Socket wants to receive events when it can be read from. + * This is provided by the underlying implementation. + */ + WANT_READ = 1, + /** + * @brief Socket wants to receive events when it can be written to. + * This is provided by the underlying implementation, and will be + * a one-off event. If you want to receive ongoing write events you + * must re-request this event type each time. + */ + WANT_WRITE = 2, + /** + * @brief Socket wants to receive events that indicate an error condition. + * Note that EOF (graceful close) is not an error condition and is indicated + * by errno being 0 and ::read() returning 0. + */ + WANT_ERROR = 4, + /** + * @brief Socket should be removed as soon as is safe to do so. Generally, this is + * after the current iteration through the active event list. + */ + WANT_DELETION = 8, +}; + +/** + * @brief Read ready event + */ +using socket_read_event = std::function; + +/** + * @brief Write ready event + */ +using socket_write_event = std::function; + +/** + * @brief Error event + */ +using socket_error_event = std::function; + +/** + * @brief Represents an active socket event set in the socket engine. + * + * An event set contains a file descriptor, a set of event handler callbacks, and + * a set of bitmask flags which indicate which events it wants to receive. + * It is possible to quickly toggle event types on or off, as it is not always necessary + * or desired to receive all events all the time, in fact doing so can cause an event + * storm which will consume 100% CPU (e.g. if you request to receive write events all + * the time). + */ +struct DPP_EXPORT socket_events { + /** + * @brief File descriptor + * + * This should be a valid file descriptor created via ::socket(). + */ + dpp::socket fd{INVALID_SOCKET}; + + /** + * @brief Flag bit mask of values from dpp::socket_event_flags + */ + uint8_t flags{0}; + + /** + * @brief Read ready event + * @note This function will be called from a different thread to that + * which adds the event set to the socket engine. + */ + socket_read_event on_read{}; + + /** + * @brief Write ready event + * @note This function will be called from a different thread to that + * which adds the event set to the socket engine. + */ + socket_write_event on_write{}; + + /** + * @brief Error event + * @note This function will be called from a different thread to that + * which adds the event set to the socket engine. + */ + socket_error_event on_error{}; + + /** + * @brief Construct a new socket_events + * @param socket_fd file descriptor + * @param _flags initial flags bitmask + * @param read_event read ready event + * @param write_event write ready event + * @param error_event error event + */ + socket_events(dpp::socket socket_fd, uint8_t _flags, const socket_read_event& read_event, const socket_write_event& write_event = {}, const socket_error_event& error_event = {}) + : fd(socket_fd), flags(_flags), on_read(read_event), on_write(write_event), on_error(error_event) { } + + /** + * @brief Default constructor + */ + socket_events() = default; +}; + +/** + * @brief Container of event sets keyed by socket file descriptor + */ +using socket_container = std::unordered_map>; + +/** + * @brief This is the base class for socket engines. + * The actual implementation is OS specific and the correct implementation is detected by + * CMake. It is then compiled specifically into DPP so only one implementation can exist + * in the implementation. All implementations should behave identically to the user, abstracting + * out implementation-specific behaviours (e.g. difference between edge and level triggered + * event mechanisms etc). + */ +struct DPP_EXPORT socket_engine_base { + + /** + * @brief Mutex for fds + */ + std::shared_mutex fds_mutex; + + /** + * @brief File descriptors, and their states + */ + socket_container fds; + + /** + * @brief Number of file descriptors we are waiting to delete + */ + size_t to_delete_count{0}; + + /** + * @brief Owning cluster + */ + class cluster* owner{nullptr}; + + /** + * @brief Default constructor + * @param creator Owning cluster + */ + socket_engine_base(class cluster* creator); + + /** + * @brief Non-copyable + */ + socket_engine_base(const socket_engine_base&) = delete; + + /** + * @brief Non-copyable + */ + socket_engine_base(socket_engine_base&&) = delete; + + /** + * @brief Non-movable + */ + socket_engine_base& operator=(const socket_engine_base&) = delete; + + /** + * @brief Non-movable + */ + socket_engine_base& operator=(socket_engine_base&&) = delete; + + /** + * @brief Default destructor + */ + virtual ~socket_engine_base() = default; + + /** + * @brief Should be called repeatedly in a loop. + * Will run for a maximum of 1 second. + */ + virtual void process_events() = 0; + + /** + * @brief Register a new socket with the socket engine + * @param e Socket events + * @return true if socket was added + */ + virtual bool register_socket(const socket_events& e); + + /** + * @brief Update an existing socket in the socket engine + * @param e Socket events + * @return true if socket was updated + */ + virtual bool update_socket(const socket_events& e); + + /** + * @brief Delete a socket from the socket engine + * @note This will not remove the socket immediately. It will set the + * WANT_DELETION flag causing it to be removed as soon as is safe to do so + * (once all events associated with it are completed). + * @param e File descriptor + * @return true if socket was queued for deletion + */ + bool delete_socket(dpp::socket fd); + + /** + * @brief Iterate through the list of sockets and remove any + * with WANT_DELETION set. This will also call implementation-specific + * remove_socket() on each entry to be removed. + */ + void prune(); + +protected: + + /** + * @brief Called by the prune() function to remove sockets when safe to do so. + * This is normally at the end or before an iteration of the event loop. + * @param fd File descriptor to remove + */ + virtual bool remove_socket(dpp::socket fd); + + /** + * @brief Find a file descriptors socket events + * @param fd file descriptor + * @return file descriptor or nullptr if doesn't exist + */ + socket_events* get_fd(dpp::socket fd); +}; + +/** + * @brief This is implemented by whatever derived form socket_engine takes + * @param creator Creating cluster + */ +DPP_EXPORT std::unique_ptr create_socket_engine(class cluster *creator); + +#ifndef _WIN32 + /** + * @brief Set up a signal handler to be ignored + * @param signal Signal to set. If the signal is already set up with a handler, + * this will do nothing. + */ + void set_signal_handler(int signal); +#endif + +}; diff --git a/include/dpp/sslclient.h b/include/dpp/sslclient.h index cd1828fcb1..896a60baf5 100644 --- a/include/dpp/sslclient.h +++ b/include/dpp/sslclient.h @@ -25,8 +25,10 @@ #include #include #include +#include #include #include +#include namespace dpp { @@ -37,23 +39,13 @@ namespace dpp { */ class openssl_connection; -/** - * @brief A callback for socket status - */ -typedef std::function socket_callback_t; - -/** - * @brief A socket notification callback - */ -typedef std::function socket_notification_t; - /** * @brief Close a socket * * @param sfd Socket to close * @return false on error, true on success */ -bool close_socket(dpp::socket sfd); +DPP_EXPORT bool close_socket(dpp::socket sfd); /** * @brief Set a socket to blocking or non-blocking IO @@ -62,7 +54,27 @@ bool close_socket(dpp::socket sfd); * @param non_blocking should socket be non-blocking? * @return false on error, true on success */ -bool set_nonblocking(dpp::socket sockfd, bool non_blocking); +DPP_EXPORT bool set_nonblocking(dpp::socket sockfd, bool non_blocking); + +/** + * @brief SSL_read buffer size + * + * You'd think that we would get better performance with a bigger buffer, but SSL frames are 16k each. + * SSL_read in non-blocking mode will only read 16k at a time. There's no point in a bigger buffer as + * it'd go unused. + */ +constexpr uint16_t DPP_BUFSIZE{16 * 1024}; + +/** + * @brief Represents a failed socket system call, e.g. connect() failure + */ +constexpr int ERROR_STATUS{-1}; + +/** + * @brief Maximum number of internal connect() retries on TCP connections + */ +constexpr int MAX_RETRIES{4}; + /** * @brief Implements a simple non-blocking SSL stream client. @@ -78,6 +90,32 @@ class DPP_EXPORT ssl_client * @brief Clean up resources */ void cleanup(); + + /** + * @brief Mutex for creation of internal SSL pointers by openssl + */ + std::mutex ssl_mutex; + + /** + * @brief Start offset into internal ring buffer for client to server IO + */ + size_t client_to_server_length = 0; + + /** + * @brief Start offset into internal ring buffer for server to client IO + */ + size_t client_to_server_offset = 0; + + /** + * @brief Internal ring buffer for client to server IO + */ + char client_to_server_buffer[DPP_BUFSIZE]; + + /** + * @brief Internal ring buffer for server to client IO + */ + char server_to_client_buffer[DPP_BUFSIZE]; + protected: /** * @brief Input buffer received from socket @@ -89,12 +127,6 @@ class DPP_EXPORT ssl_client */ std::string obuffer; - /** - * @brief True if in nonblocking mode. The socket switches to nonblocking mode - * once ReadLoop is called. - */ - bool nonblocking; - /** * @brief Raw file descriptor of connection */ @@ -115,6 +147,16 @@ class DPP_EXPORT ssl_client */ time_t last_tick; + /** + * @brief Start time of connection + */ + time_t start; + + /** + * @brief How many times we retried connect() + */ + uint8_t connect_retries{0}; + /** * @brief Hostname connected to */ @@ -141,10 +183,28 @@ class DPP_EXPORT ssl_client bool plaintext; /** - * @brief True if we are establishing a new connection, false if otherwise. + * @brief True if connection is completed + */ + bool connected{false}; + + /** + * @brief True if tcp connect() succeeded */ - bool make_new; + bool tcp_connect_done{false}; + /** + * @brief Timer handle for one second timer + */ + timer timer_handle; + + /** + * @brief Unique ID of socket used as a nonce + * You can use this to identify requests vs reply + * if you want. D++ itself only sets this, and does + * not use it in any logic. It starts at 1 and increments + * for each request made. + */ + uint64_t unique_id; /** * @brief Called every second @@ -156,6 +216,7 @@ class DPP_EXPORT ssl_client * @throw dpp::exception Failed to initialise connection */ virtual void connect(); + public: /** * @brief Get the bytes out objectGet total bytes sent @@ -170,45 +231,32 @@ class DPP_EXPORT ssl_client uint64_t get_bytes_in(); /** - * @brief Get SSL cipher name - * @return std::string ssl cipher name - */ - std::string get_cipher(); - - /** - * @brief Attaching an additional file descriptor to this function will send notifications when there is data to read. - * - * NOTE: Only hook this if you NEED it as it can increase CPU usage of the thread! - * Returning -1 means that you don't want to be notified. - */ - socket_callback_t custom_readable_fd; - - /** - * @brief Attaching an additional file descriptor to this function will send notifications when you are able to write - * to the socket. - * - * NOTE: Only hook this if you NEED it as it can increase CPU usage of the thread! You should toggle this - * to -1 when you do not have anything to write otherwise it'll keep triggering repeatedly (it is level triggered). + * @brief Every request made has a unique ID. This increments + * for every request, starting at 1. You can use this for statistics, + * or to associate requests and replies in external event loops. + * @return Unique ID */ - socket_callback_t custom_writeable_fd; + uint64_t get_unique_id() const; /** - * @brief This event will be called when you can read from the custom fd + * @brief Get SSL cipher name + * @return std::string ssl cipher name */ - socket_notification_t custom_readable_ready; + std::string get_cipher(); /** - * @brief This event will be called when you can write to a custom fd + * @brief True if we are keeping the connection alive after it has finished */ - socket_notification_t custom_writeable_ready; + bool keepalive; /** - * @brief True if we are keeping the connection alive after it has finished + * @brief Owning cluster */ - bool keepalive; + class cluster* owner; /** * @brief Connect to a specified host and port. Throws std::runtime_error on fatal error. + * @param creator Creating cluster * @param _hostname The hostname to connect to * @param _port the Port number to connect to * @param plaintext_downgrade Set to true to connect using plaintext only, without initialising SSL. @@ -217,11 +265,11 @@ class DPP_EXPORT ssl_client * connection to non-Discord addresses such as within dpp::cluster::request(). * @throw dpp::exception Failed to initialise connection */ - ssl_client(const std::string &_hostname, const std::string &_port = "443", bool plaintext_downgrade = false, bool reuse = false); + ssl_client(cluster* creator, const std::string &_hostname, const std::string &_port = "443", bool plaintext_downgrade = false, bool reuse = false); /** - * @brief Nonblocking I/O loop - * @throw std::exception Any std::exception (or derivative) thrown from read_loop() causes reconnection of the shard + * @brief Set up non blocking I/O and configure on_read, on_write and on_error. + * @throw std::exception Any std::exception (or derivative) thrown from read_loop() indicates setup failed */ void read_loop(); @@ -256,6 +304,35 @@ class DPP_EXPORT ssl_client * @param msg Log message to send */ virtual void log(dpp::loglevel severity, const std::string &msg) const; + + /** + * @brief Called while SSL handshake is in progress. + * If the handshake completes, the state of the socket is progressed to + * an established state. + * @param ev Socket events for the socket + */ + void complete_handshake(const struct socket_events* ev); + + /** + * @brief Called when the TCP socket has data to read + * @param fd File descriptor + * @param ev Socket events + */ + void on_read(dpp::socket fd, const struct dpp::socket_events& ev); + + /** + * @brief Called when the TCP socket can be written to without blocking + * @param fd File descriptor + * @param e Socket events + */ + void on_write(dpp::socket fd, const struct dpp::socket_events& e); + + /** + * @brief Called when there is an error on the TCP socket + * @param fd File descriptor + * @param error_code Error code + */ + void on_error(dpp::socket fd, const struct dpp::socket_events&, int error_code); }; } diff --git a/include/dpp/sync.h b/include/dpp/sync.h deleted file mode 100644 index fdce669b2d..0000000000 --- a/include/dpp/sync.h +++ /dev/null @@ -1,81 +0,0 @@ -/************************************************************************************ - * - * D++, A Lightweight C++ library for Discord - * - * Copyright 2022 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. - * - ************************************************************************************/ -#pragma once -#include -#include -#include -#include -#include - -namespace dpp { - -/** - * @brief Call a D++ REST function synchronously. - * - * Synchronously calling a REST function means *IT WILL BLOCK* - This is a Bad Thing™ and strongly discouraged. - * There are very few circumstances you actually need this. If you do need to use this, you'll know it. - * - * Example: - * - * ```cpp - * dpp::message m = dpp::sync(&bot, &dpp::cluster::message_create, dpp::message(channel_id, "moo.")); - * ``` - * - * @warning As previously mentioned, this template will block. It is ill-advised to call this outside of - * a separate thread and this should never be directly used in any event such as dpp::cluster::on_interaction_create! - * @tparam T type of expected return value, should match up with the method called - * @tparam F Type of class method in dpp::cluster to call. - * @tparam Ts Function parameters in method call - * @param c A pointer to dpp::cluster object - * @param func pointer to class method in dpp::cluster to call. This can call any - * dpp::cluster member function who's last parameter is a dpp::command_completion_event_t callback type. - * @param args Zero or more arguments for the method call - * @return An instantiated object of type T - * @throw dpp::rest_exception On failure of the method call, an exception is thrown - */ -template T sync(class cluster* c, F func, Ts&&... args) { - std::promise _p; - std::future _f = _p.get_future(); - /* (obj ->* func) is the obscure syntax for calling a method pointer on an object instance */ - (c ->* func)(std::forward(args)..., [&_p](const auto& cc) { - try { - if (cc.is_error()) { - const auto& error = cc.get_error(); - throw dpp::rest_exception((exception_error_code)error.code, error.message); - } else { - try { - _p.set_value(std::get(cc.value)); - } catch (const std::exception& e) { - throw dpp::rest_exception(err_unknown, e.what()); - } - } - } catch (const dpp::rest_exception&) { - _p.set_exception(std::current_exception()); - } - }); - - /* Blocking calling thread until rest request is completed. - * Exceptions encountered on the other thread are re-thrown. - */ - return _f.get(); -} - -} diff --git a/include/dpp/thread_pool.h b/include/dpp/thread_pool.h new file mode 100644 index 0000000000..724ce24a54 --- /dev/null +++ b/include/dpp/thread_pool.h @@ -0,0 +1,117 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * SPDX-License-Identifier: Apache-2.0 + * 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. + * + ************************************************************************************/ + +#pragma once +#include +#include +#include +#include +#include +#include + +namespace dpp { + +/** + * @brief A work unit is a lambda executed in the thread pool + */ +using work_unit = std::function; + +/** + * @brief A task within a thread pool. A simple lambda that accepts no parameters and returns void. + */ +struct DPP_EXPORT thread_pool_task { + /** + * @brief Task priority, lower value is higher priority + */ + int priority; + /** + * @brief Work unit to execute as the task + */ + work_unit function; +}; + +/** + * @brief Compares two thread pool tasks by priority + */ +struct DPP_EXPORT thread_pool_task_comparator { + /** + * @brief Compare two tasks + * @param a first task + * @param b second task + * @return true if a > b + */ + bool operator()(const thread_pool_task &a, const thread_pool_task &b) const { + return a.priority > b.priority; + }; +}; + +/** + * @brief A thread pool contains 1 or more worker threads which accept thread_pool_task lambadas + * into a queue, which is processed in-order by whichever thread is free. + */ +struct DPP_EXPORT thread_pool { + + /** + * @brief Threads that comprise the thread pool + */ + std::vector threads; + + /** + * @brief Priority queue of tasks to be executed + */ + std::priority_queue, thread_pool_task_comparator> tasks; + + /** + * @brief Mutex for accessing the priority queue + */ + std::mutex queue_mutex; + + /** + * @brief Condition variable to notify for new tasks to run + */ + std::condition_variable cv; + + /** + * @brief True if the thread pool is due to stop + */ + bool stop{false}; + + /** + * @brief Create a new priority thread pool + * @param creator creating cluster (for logging) + * @param num_threads number of threads in the pool + */ + explicit thread_pool(class cluster* creator, size_t num_threads = std::thread::hardware_concurrency()); + + /** + * @brief Destroy the thread pool + */ + ~thread_pool(); + + /** + * @brief Enqueue a new task to the thread pool + * @param task task to enqueue + */ + void enqueue(thread_pool_task task); +}; + +} \ No newline at end of file diff --git a/include/dpp/timer.h b/include/dpp/timer.h index c3dfbfa97e..b5c9827ddf 100644 --- a/include/dpp/timer.h +++ b/include/dpp/timer.h @@ -22,11 +22,13 @@ #pragma once #include -#include +#include #include #include -#include +#include #include +#include +#include #include namespace dpp { @@ -50,39 +52,55 @@ struct DPP_EXPORT timer_t { /** * @brief Timer handle */ - timer handle; + timer handle{0}; /** * @brief Next timer tick as unix epoch */ - time_t next_tick; + time_t next_tick{0}; /** * @brief Frequency between ticks */ - uint64_t frequency; + uint64_t frequency{0}; /** * @brief Lambda to call on tick */ - timer_callback_t on_tick; + timer_callback_t on_tick{}; /** * @brief Lambda to call on stop (optional) */ - timer_callback_t on_stop; + timer_callback_t on_stop{}; }; /** - * @brief A map of timers, ordered by earliest first so that map::begin() is always the + * @brief Used to compare two timers next tick times in a priority queue + */ +struct DPP_EXPORT timer_comparator { + /** + * @brief Compare two timers + * @param a first timer + * @param b second timer + * @return returns true if a > b + */ + bool operator()(const timer_t &a, const timer_t &b) const { + return a.next_tick > b.next_tick; + }; +}; + + +/** + * @brief A priority timers, ordered by earliest first so that the head is always the * soonest to be due. */ -typedef std::multimap timer_next_t; +typedef std::priority_queue, timer_comparator> timer_next_t; /** - * @brief A map of timers stored by handle + * @brief A set of deleted timer handles */ -typedef std::unordered_map timer_reg_t; +typedef std::set timers_deleted_t; /** * @brief Trigger a timed event once. diff --git a/include/dpp/version.h b/include/dpp/version.h index 16665e519e..3380c1ddf1 100644 --- a/include/dpp/version.h +++ b/include/dpp/version.h @@ -22,9 +22,9 @@ #pragma once #ifndef DPP_VERSION_LONG -#define DPP_VERSION_LONG 0x00100035 -#define DPP_VERSION_SHORT 100035 -#define DPP_VERSION_TEXT "D++ 10.0.35 (29-Oct-2024)" +#define DPP_VERSION_LONG 0x00100100 +#define DPP_VERSION_SHORT 100100 +#define DPP_VERSION_TEXT "D++ 10.1.0 (06-Dec-2024)" #define DPP_VERSION_MAJOR ((DPP_VERSION_LONG & 0x00ff0000) >> 16) #define DPP_VERSION_MINOR ((DPP_VERSION_LONG & 0x0000ff00) >> 8) diff --git a/include/dpp/wsclient.h b/include/dpp/wsclient.h index fbbf72dee7..9380d2d072 100644 --- a/include/dpp/wsclient.h +++ b/include/dpp/wsclient.h @@ -135,15 +135,6 @@ class DPP_EXPORT websocket_client : public ssl_client { */ bool parseheader(std::string& buffer); - /** - * @brief Unpack a frame and pass completed frames up the stack. - * @param buffer The buffer to operate on. Gets modified to remove completed frames on the head of the buffer - * @param offset The offset to start at (reserved for future use) - * @param first True if is the first element (reserved for future use) - * @return true if a complete frame has been received - */ - bool unpack(std::string& buffer, uint32_t offset, bool first = true); - /** * @brief Fill a header for outbound messages * @param outbuf The raw frame to fill @@ -162,9 +153,9 @@ class DPP_EXPORT websocket_client : public ssl_client { protected: /** - * @brief (Re)connect + * @brief Connect to websocket server */ - virtual void connect(); + virtual void connect() override; /** * @brief Get websocket state @@ -172,18 +163,33 @@ class DPP_EXPORT websocket_client : public ssl_client { */ [[nodiscard]] ws_state get_state() const; + /** + * @brief If true the connection timed out while waiting, + * when waiting for SSL negotiation, TCP connect(), or HTTP. + */ + bool timed_out; + + /** + * @brief Time at which the connection should be abandoned, + * if we are still connecting or negotiating with a HTTP server + */ + time_t timeout; + public: /** * @brief Connect to a specific websocket server. + * @param creator Creating cluster * @param hostname Hostname to connect to * @param port Port to connect to * @param urlpath The URL path components of the HTTP request to send * @param opcode The encoding type to use, either OP_BINARY or OP_TEXT - * @note Voice websockets only support OP_TEXT, and other websockets must be - * OP_BINARY if you are going to send ETF. + * @note This just indicates the default for frames sent. Certain sockets, + * such as voice websockets, may send a combination of OP_TEXT and OP_BINARY + * frames, whereas shard websockets will only ever send OP_BINARY for ETF and + * OP_TEXT for JSON. */ - websocket_client(const std::string& hostname, const std::string& port = "443", const std::string& urlpath = "", ws_opcode opcode = OP_BINARY); + websocket_client(cluster* creator, const std::string& hostname, const std::string& port = "443", const std::string& urlpath = "", ws_opcode opcode = OP_BINARY); /** * @brief Destroy the websocket client object @@ -202,12 +208,12 @@ class DPP_EXPORT websocket_client : public ssl_client { * @brief Processes incoming frames from the SSL socket input buffer. * @param buffer The buffer contents. Can modify this value removing the head elements when processed. */ - virtual bool handle_buffer(std::string& buffer); + virtual bool handle_buffer(std::string& buffer) override; /** * @brief Close websocket */ - virtual void close(); + virtual void close() override; /** * @brief Receives raw frame content only without headers @@ -228,13 +234,19 @@ class DPP_EXPORT websocket_client : public ssl_client { /** * @brief Fires every second from the underlying socket I/O loop, used for sending websocket pings */ - virtual void one_second_timer(); + virtual void one_second_timer() override; /** * @brief Send OP_CLOSE error code 1000 to the other side of the connection. * This indicates graceful close. + * @note This informs Discord to invalidate the session, you cannot resume if you send this */ void send_close_packet(); + + /** + * @brief Called on HTTP socket closure + */ + virtual void on_disconnect(); }; } diff --git a/include/dpp/zlibcontext.h b/include/dpp/zlibcontext.h new file mode 100644 index 0000000000..a1a7f48350 --- /dev/null +++ b/include/dpp/zlibcontext.h @@ -0,0 +1,85 @@ +/************************************************************************************ + * + * 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. + * + ************************************************************************************/ +#pragma once +#include +#include +#include +#include +#include + +/** + * @brief Forward declaration for zlib stream type + */ +typedef struct z_stream_s z_stream; + +namespace dpp { + +/** + * @brief Size of decompression buffer for zlib compressed traffic + */ +constexpr size_t DECOMP_BUFFER_SIZE = 512 * 1024; + +/** + * @brief This is an opaque class containing zlib library specific structures. + * This wraps the C pointers needed for zlib with unique_ptr and gives us a nice + * buffer abstraction so we don't need to wrestle with raw pointers. + */ +class zlibcontext { +public: + /** + * @brief Zlib stream struct. The actual type is defined in zlib.h + * so is only defined in the implementation file. + */ + z_stream* d_stream{}; + + /** + * @brief ZLib decompression buffer. + * This is automatically set to DECOMP_BUFFER_SIZE bytes when + * the class is constructed. + */ + std::vector decomp_buffer{}; + + /** + * @brief Total decompressed received bytes counter + */ + uint64_t decompressed_total{}; + + /** + * @brief Initialise zlib struct via inflateInit() + * and size the buffer + */ + zlibcontext(); + + /** + * @brief Destroy zlib struct via inflateEnd() + */ + ~zlibcontext(); + + /** + * @brief Decompress zlib deflated buffer + * @param buffer input compressed stream + * @param decompressed output decompressed content + * @return an error code on error, or err_no_code_specified (0) on success + */ + exception_error_code decompress(const std::string& buffer, std::string& decompressed); +}; + +} \ No newline at end of file diff --git a/library-vcpkg/CMakeLists.txt b/library-vcpkg/CMakeLists.txt index 5e7b9ae6d7..9de5127093 100644 --- a/library-vcpkg/CMakeLists.txt +++ b/library-vcpkg/CMakeLists.txt @@ -27,6 +27,22 @@ if(NOT WIN32) find_package(Threads REQUIRED) endif() +include("${CMAKE_CURRENT_SOURCE_DIR}/../cmake/epoll.cmake") +include("${CMAKE_CURRENT_SOURCE_DIR}/../cmake/kqueue.cmake") +check_epoll(HAS_EPOLL) +check_kqueue(HAS_KQUEUE) +if (HAS_EPOLL) + message("-- Building with ${Green}epoll socket engine${ColourReset} -- ${Green}good!${ColourReset}") + target_sources("${LIB_NAME}" PRIVATE "${DPP_ROOT_PATH}/src/dpp/socketengines/epoll.cpp") +elseif (HAS_KQUEUE) + message("-- Building with ${Green}kqueue socket engine${ColourReset} -- ${Green}good!${ColourReset}") + target_sources("${LIB_NAME}" PRIVATE "${DPP_ROOT_PATH}/src/dpp/socketengines/kqueue.cpp") +else() + message("-- Building with ${Green}poll socket engine${ColourReset} -- ${Red}meh${ColourReset}!") + target_sources("${LIB_NAME}" PRIVATE "${DPP_ROOT_PATH}/src/dpp/socketengines/poll.cpp") +endif() + + add_library("${PROJECT_NAME}::${LIB_NAME}" ALIAS "${LIB_NAME}") if(${AVX_TYPE} STREQUAL "OFF") diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index 497a2defaf..d7905a11d1 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -122,12 +122,6 @@ if (UNIX) if (${HAS_PHP} STREQUAL "0") message("-- Checking for update to autogenerated files") - # target for rebuild of cluster::*_sync() functions - execute_process( - WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/.." - COMMAND php buildtools/make_struct.php "\\Dpp\\Generator\\SyncGenerator" - ) - set_source_files_properties( "${CMAKE_CURRENT_SOURCE_DIR}/../include/dpp/cluster_sync_calls.h" PROPERTIES GENERATED TRUE ) # target for unicode_emojis.h execute_process( WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/.." @@ -227,7 +221,7 @@ if(MSVC) string(REGEX REPLACE "/W[1|2|3|4]" "/W3" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") else() set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC -Wall -Wno-unused-private-field -Wno-psabi -Wempty-body -Wignored-qualifiers -Wimplicit-fallthrough -Wmissing-field-initializers -Wsign-compare -Wtype-limits -Wuninitialized -Wshift-negative-value -pthread") - set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Og") + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0 -g") if (NOT MINGW) add_link_options("-rdynamic") @@ -311,6 +305,20 @@ foreach (fullmodname ${subdirlist}) endif() endforeach() +include("${CMAKE_CURRENT_SOURCE_DIR}/../cmake/epoll.cmake") +include("${CMAKE_CURRENT_SOURCE_DIR}/../cmake/kqueue.cmake") +check_epoll(HAS_EPOLL) +check_kqueue(HAS_KQUEUE) +if (HAS_EPOLL) + message("-- Building with ${Green}epoll socket engine${ColourReset} -- ${Green}good!${ColourReset}") + target_sources("dpp" PRIVATE "${modules_dir}/dpp/socketengines/epoll.cpp") +elseif (HAS_KQUEUE) + message("-- Building with ${Green}kqueue socket engine${ColourReset} -- ${Green}good!${ColourReset}") + target_sources("dpp" PRIVATE "${modules_dir}/dpp/socketengines/kqueue.cpp") +else() + message("-- Building with ${Green}poll socket engine${ColourReset} -- ${Red}meh!${ColourReset}") + target_sources("dpp" PRIVATE "${modules_dir}/dpp/socketengines/poll.cpp") +endif() if (HAVE_VOICE) # Private statically linked dependencies if(NOT BUILD_SHARED_LIBS AND NOT APPLE) diff --git a/src/davetest/dave.cpp b/src/davetest/dave.cpp index 94812e0d89..83ebc5e78f 100644 --- a/src/davetest/dave.cpp +++ b/src/davetest/dave.cpp @@ -84,5 +84,5 @@ int main() { s->connect_voice(TEST_GUILD_ID, TEST_VC_ID, muted, deaf, enable_dave); } }); - dave_test.start(false); + dave_test.start(dpp::st_wait); } diff --git a/src/dpp/cluster.cpp b/src/dpp/cluster.cpp index e39bc75b1e..1169db32d2 100644 --- a/src/dpp/cluster.cpp +++ b/src/dpp/cluster.cpp @@ -84,14 +84,21 @@ template bool DPP_EXPORT validate_configuration(); template bool DPP_EXPORT validate_configuration(); -cluster::cluster(const std::string &_token, uint32_t _intents, uint32_t _shards, uint32_t _cluster_id, uint32_t _maxclusters, bool comp, cache_policy_t policy, uint32_t request_threads, uint32_t request_threads_raw) +cluster::cluster(uint32_t pool_threads) : cluster("", 0, NO_SHARDS, 1, 1, false, cache_policy::cpol_none, pool_threads) +{ +} + +cluster::cluster(const std::string &_token, uint32_t _intents, uint32_t _shards, uint32_t _cluster_id, uint32_t _maxclusters, bool comp, cache_policy_t policy, uint32_t pool_threads) : default_gateway("gateway.discord.gg"), rest(nullptr), raw_rest(nullptr), compressed(comp), start_time(0), token(_token), last_identify(time(nullptr) - 5), intents(_intents), numshards(_shards), cluster_id(_cluster_id), maxclusters(_maxclusters), rest_ping(0.0), cache_policy(policy), ws_mode(ws_json) { + socketengine = create_socket_engine(this); + pool = std::make_unique(this, pool_threads > 4 ? pool_threads : 4); /* Instantiate REST request queues */ try { - rest = new request_queue(this, request_threads); - raw_rest = new request_queue(this, request_threads_raw); + /* NOTE: These no longer use threads. This instantiates 16+4 dpp::timer instances. */ + rest = new request_queue(this, 16); + raw_rest = new request_queue(this, 4); } catch (std::bad_alloc&) { delete rest; @@ -150,12 +157,9 @@ cluster::cluster(const std::string &_token, uint32_t _intents, uint32_t _shards, cluster::~cluster() { - this->shutdown(); delete rest; delete raw_rest; -#ifdef _WIN32 - WSACleanup(); -#endif + this->shutdown(); } request_queue* cluster::get_rest() { @@ -174,10 +178,14 @@ cluster& cluster::set_websocket_protocol(websocket_protocol_t mode) { return *this; } +void cluster::queue_work(int priority, work_unit task) { + pool->enqueue({priority, task}); +} + void cluster::log(dpp::loglevel severity, const std::string &msg) const { if (!on_log.empty()) { /* Pass to user if they've hooked the event */ - dpp::log_t logmsg(nullptr, msg); + dpp::log_t logmsg(nullptr, 0, msg); logmsg.severity = severity; logmsg.message = msg; size_t pos{0}; @@ -194,12 +202,73 @@ dpp::utility::uptime cluster::uptime() return dpp::utility::uptime(time(nullptr) - start_time); } -void cluster::start(bool return_after) { +void cluster::add_reconnect(uint32_t shard_id) { + reconnections[shard_id] = time(nullptr) + RECONNECT_INTERVAL; + log(ll_trace, "Reconnecting shard " + std::to_string(shard_id) + " in " + std::to_string(RECONNECT_INTERVAL) + " seconds..."); +} - auto block_calling_thread = [this]() { - std::mutex thread_mutex; - std::unique_lock thread_lock(thread_mutex); - this->terminating.wait(thread_lock); +void cluster::start(start_type return_after) { + + auto event_loop = [this]() -> void { + auto reconnect_monitor = numshards != NO_SHARDS ? start_timer([this](auto t) { + time_t now = time(nullptr); + for (auto reconnect = reconnections.begin(); reconnect != reconnections.end(); ++reconnect) { + auto shard_id = reconnect->first; + auto shard_reconnect_time = reconnect->second; + if (now >= shard_reconnect_time) { + /* This shard needs to be reconnected */ + reconnections.erase(reconnect); + discord_client* old = shards[shard_id]; + /* These values must be copied to the new connection + * to attempt to resume it + */ + auto seq_no = old->last_seq; + auto session_id = old->sessionid; + log(ll_info, "Reconnecting shard " + std::to_string(shard_id)); + /* Make a new resumed connection based off the old one */ + try { + if (shards[shard_id] != nullptr) { + log(ll_trace, "Attempting resume..."); + shards[shard_id] = nullptr; + shards[shard_id] = new discord_client(*old, seq_no, session_id); + } else { + log(ll_trace, "Attempting full reconnection..."); + shards[shard_id] = new discord_client(this, shard_id, numshards, token, intents, compressed, ws_mode); + } + /* Delete the old one */ + log(ll_trace, "Attempting to delete old connection..."); + delete old; + old = nullptr; + /* Set up the new shard's IO events */ + log(ll_trace, "Running new connection..."); + shards[shard_id]->run(); + } + catch (const std::exception& e) { + log(ll_info, "Exception when reconnecting shard " + std::to_string(shard_id) + ": " + std::string(e.what())); + delete shards[shard_id]; + delete old; + old = nullptr; + shards[shard_id] = nullptr; + add_reconnect(shard_id); + } + /* It is not possible to reconnect another shard within the same 5-second window, + * due to discords strict rate limiting on shard connections, so we bail out here + * and only try another reconnect in the next timer interval. Do not try and make + * this support multiple reconnects per loop iteration or Discord will smack us + * with the rate limiting clue-by-four. + */ + return; + } else { + log(ll_trace, "Shard " + std::to_string(shard_id) + " not ready to reconnect yet."); + } + } + }, 5) : 0; + while (!this->terminating && socketengine.get()) { + socketengine->process_events(); + } + if (reconnect_monitor) { + stop_timer(reconnect_monitor); + } }; if (on_guild_member_add && !(intents & dpp::i_guild_members)) { @@ -218,111 +287,126 @@ void cluster::start(bool return_after) { log(ll_warning, "You have attached an event to cluster::on_presence_update() but have not specified the privileged intent dpp::i_guild_presences. This event will not fire."); } - /* Start up all shards */ - gateway g; - try { -#ifdef DPP_CORO - confirmation_callback_t cc = co_get_gateway_bot().sync_wait(); - g = std::get(cc.value); -#else - g = dpp::sync(this, &cluster::get_gateway_bot); -#endif - log(ll_debug, "Cluster: " + std::to_string(g.session_start_remaining) + " of " + std::to_string(g.session_start_total) + " session starts remaining"); - if (g.session_start_remaining < g.shards) { - throw dpp::connection_exception(err_no_sessions_left, "Discord indicates you cannot start enough sessions to boot this cluster! Cluster startup aborted. Try again later."); - } - if (g.session_start_max_concurrency > 1) { - log(ll_debug, "Cluster: Large bot sharding; Using session concurrency: " + std::to_string(g.session_start_max_concurrency)); - } - if (numshards == 0) { - if (g.shards) { - log(ll_info, "Auto Shard: Bot requires " + std::to_string(g.shards) + std::string(" shard") + ((g.shards > 1) ? "s" : "")); - } else { - throw dpp::connection_exception(err_auto_shard, "Auto Shard: Cannot determine number of shards. Cluster startup aborted. Check your connection."); - } - numshards = g.shards; - } - } - catch (const dpp::rest_exception& e) { - if (std::string(e.what()) == "401: Unauthorized") { - /* Throw special form of exception for invalid token */ - throw dpp::invalid_token_exception(err_unauthorized, "Invalid bot token (401: Unauthorized when getting gateway shard count)"); - } else { - /* Rethrow */ - throw e; - } - } - - start_time = time(nullptr); + if (numshards != NO_SHARDS) { + /* Start up all shards */ + get_gateway_bot([this, return_after](const auto &response) { - log(ll_debug, "Starting with " + std::to_string(numshards) + " shards..."); + auto throw_if_not_threaded = [this, return_after](exception_error_code error_id, const std::string &msg) { + log(ll_critical, msg); + if (return_after == st_wait) { + throw dpp::connection_exception(error_id, msg); + } + }; - for (uint32_t s = 0; s < numshards; ++s) { - /* Filter out shards that aren't part of the current cluster, if the bot is clustered */ - if (s % maxclusters == cluster_id) { - /* Each discord_client spawns its own thread in its run() */ - try { - this->shards[s] = new discord_client(this, s, numshards, token, intents, compressed, ws_mode); - this->shards[s]->run(); + if (response.is_error()) { + if (response.http_info.status == 401) { + throw_if_not_threaded(err_unauthorized, "Invalid bot token (401: Unauthorized when getting gateway shard count)"); + } else { + throw_if_not_threaded(err_auto_shard, "get_gateway_bot: " + response.http_info.body); + } + return; } - catch (const std::exception &e) { - log(dpp::ll_critical, "Could not start shard " + std::to_string(s) + ": " + std::string(e.what())); + auto g = std::get(response.value); + log(ll_debug, "Cluster: " + std::to_string(g.session_start_remaining) + " of " + std::to_string(g.session_start_total) + " session starts remaining"); + if (g.session_start_remaining < g.shards || g.shards == 0) { + throw_if_not_threaded(err_no_sessions_left, "Discord indicates you cannot start enough sessions to boot this cluster! Cluster startup aborted. Try again later."); + return; + } else if (g.session_start_max_concurrency == 0) { + throw_if_not_threaded(err_auto_shard, "Cluster: Could not determine concurrency, startup aborted!"); + return; + } else if (g.session_start_max_concurrency > 1) { + log(ll_debug, "Cluster: Large bot sharding; Using session concurrency: " + std::to_string(g.session_start_max_concurrency)); + } + if (numshards == 0) { + log(ll_info, "Auto Shard: Bot requires " + std::to_string(g.shards) + std::string(" shard") + ((g.shards > 1) ? "s" : "")); + numshards = g.shards; } - /* Stagger the shard startups, pausing every 'session_start_max_concurrency' shards for 5 seconds. - * This means that for bots that don't have large bot sharding, any number % 1 is always 0, - * so it will pause after every shard. For any with non-zero concurrency it'll pause 5 seconds - * after every batch. - */ - if (((s + 1) % g.session_start_max_concurrency) == 0) { - size_t wait_time = 5; - if (g.session_start_max_concurrency > 1) { - /* If large bot sharding, be sure to give the batch of shards time to settle */ - bool all_connected = true; - do { - all_connected = true; - for (auto& shard : this->shards) { - if (!shard.second->ready) { - all_connected = false; - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - break; - } + log(ll_debug, "Starting with " + std::to_string(numshards) + " shards..."); + start_time = time(nullptr); + + for (uint32_t s = 0; s < numshards; ++s) { + /* Filter out shards that aren't part of the current cluster, if the bot is clustered */ + if (s % maxclusters == cluster_id) { + /* Each discord_client is inserted into the socket engine when we call run() */ + try { + this->shards[s] = new discord_client(this, s, numshards, token, intents, compressed, ws_mode); + this->shards[s]->run(); + } + catch (const std::exception &e) { + throw_if_not_threaded(err_cant_start_shard, "Could not start shard " + std::to_string(s) + ": " + std::string(e.what())); + return; + } + /* Stagger the shard startups, pausing every 'session_start_max_concurrency' shards for 5 seconds. + * This means that for bots that don't have large bot sharding, any number % 1 is always 0, + * so it will pause after every shard. For any with non-zero concurrency it'll pause 5 seconds + * after every batch. + */ + if (((s + 1) % g.session_start_max_concurrency) == 0) { + size_t wait_time = 5; + if (g.session_start_max_concurrency > 1) { + /* If large bot sharding, be sure to give the batch of shards time to settle */ + bool all_connected = true; + do { + all_connected = true; + for (auto &shard: this->shards) { + if (!shard.second->ready) { + all_connected = false; + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + break; + } + } + } while (all_connected); } - } while (all_connected); + std::this_thread::sleep_for(std::chrono::seconds(wait_time)); + } } - std::this_thread::sleep_for(std::chrono::seconds(wait_time)); } - } - } - /* Get all active DM channels and map them to user id -> dm id */ - this->current_user_get_dms([this](const dpp::confirmation_callback_t& completion) { - dpp::channel_map dmchannels = std::get(completion.value); - for (auto & c : dmchannels) { - for (auto & u : c.second.recipients) { - this->set_dm_channel(u, c.second.id); - } - } - }); + /* Get all active DM channels and map them to user id -> dm id */ + current_user_get_dms([this](const dpp::confirmation_callback_t &completion) { + if (completion.is_error()) { + log(dpp::ll_debug, "Failed to get bot DM list"); + return; + } + dpp::channel_map dmchannels = std::get(completion.value); + for (auto &c: dmchannels) { + for (auto &u: c.second.recipients) { + set_dm_channel(u, c.second.id); + } + } + }); + + log(ll_debug, "Shards started."); + }); - log(ll_debug, "Shards started."); - - if (!return_after) { - block_calling_thread(); + } + + if (return_after == st_return) { + engine_thread = std::thread([event_loop]() { + dpp::utility::set_thread_name("event_loop"); + event_loop(); + }); + } else { + event_loop(); } } void cluster::shutdown() { - /* Signal condition variable to terminate */ - terminating.notify_all(); - /* Free memory for active timers */ - for (auto & t : timer_list) { - delete t.second; + /* Signal termination */ + terminating = true; + + if (engine_thread.joinable()) { + /* Join engine_thread if it ever started */ + engine_thread.join(); + } + + { + std::lock_guard l(timer_guard); + next_timer = {}; } - timer_list.clear(); - next_timer.clear(); + /* Terminate shards */ for (const auto& sh : shards) { - log(ll_info, "Terminating shard id " + std::to_string(sh.second->shard_id)); delete sh.second; } shards.clear(); @@ -419,8 +503,8 @@ void cluster::post_rest_multipart(const std::string &endpoint, const std::string } -void cluster::request(const std::string &url, http_method method, http_completion_event callback, const std::string &postdata, const std::string &mimetype, const std::multimap &headers, const std::string &protocol, time_t request_timeout) { - raw_rest->post_request(std::make_unique(url, callback, method, postdata, mimetype, headers, protocol, request_timeout)); +void cluster::request(const std::string &url, http_method method, http_completion_event callback, const std::string &postdata, const std::string &mimetype, const std::multimap &headers, const std::string &protocol) { + raw_rest->post_request(std::make_unique(url, callback, method, postdata, mimetype, headers, protocol)); } gateway::gateway() : shards(0), session_start_total(0), session_start_remaining(0), session_start_reset_after(0), session_start_max_concurrency(0) { @@ -504,4 +588,8 @@ bool cluster::unregister_command(const std::string &name) { return named_commands.erase(name) == 1; } +size_t cluster::active_requests() { + return rest->get_active_request_count() + raw_rest->get_active_request_count(); +} + }; diff --git a/src/dpp/cluster/timer.cpp b/src/dpp/cluster/timer.cpp index a6520306f2..2fa610c002 100644 --- a/src/dpp/cluster/timer.cpp +++ b/src/dpp/cluster/timer.cpp @@ -21,98 +21,82 @@ #include #include #include +#include namespace dpp { -timer lasthandle = 1; -std::mutex timer_guard; +std::atomic next_handle = 1; timer cluster::start_timer(timer_callback_t on_tick, uint64_t frequency, timer_callback_t on_stop) { - std::lock_guard l(timer_guard); - timer_t* newtimer = new timer_t(); - - newtimer->handle = lasthandle++; - newtimer->next_tick = time(nullptr) + frequency; - newtimer->on_tick = on_tick; - newtimer->on_stop = on_stop; - newtimer->frequency = frequency; - timer_list[newtimer->handle] = newtimer; - next_timer.emplace(newtimer->next_tick, newtimer); + timer_t new_timer; - return newtimer->handle; -} + new_timer.handle = next_handle++; + new_timer.next_tick = time(nullptr) + frequency; + new_timer.on_tick = on_tick; + new_timer.on_stop = on_stop; + new_timer.frequency = frequency; -bool cluster::stop_timer(timer t) { std::lock_guard l(timer_guard); + next_timer.emplace(new_timer); - auto i = timer_list.find(t); - if (i != timer_list.end()) { - timer_t* tptr = i->second; - if (tptr->on_stop) { - /* If there is an on_stop event, call it */ - tptr->on_stop(t); - } - timer_list.erase(i); - auto j = next_timer.find(tptr->next_tick); - if (j != next_timer.end()) { - next_timer.erase(j); - } - delete tptr; - return true; - } - return false; + return new_timer.handle; } -void cluster::timer_reschedule(timer_t* t) { +bool cluster::stop_timer(timer t) { + /* + * Because iterating a priority queue is at best O(log n) we don't actually walk the queue + * looking for the timer to remove. Instead, we just insert the timer handle into a std::set + * to inform the tick_timers() function later if it sees a handle in this set, it is to + * have its on_stop() called and it is not to be rescheduled. + */ std::lock_guard l(timer_guard); - for (auto i = next_timer.begin(); i != next_timer.end(); ++i) { - /* Rescheduling the timer means finding it in the next tick map. - * It should be pretty much near the start of the map so this loop - * should only be at most a handful of iterations. - */ - if (i->second->handle == t->handle) { - next_timer.erase(i); - t->next_tick = time(nullptr) + t->frequency; - next_timer.emplace(t->next_tick, t); - break; - } - } + deleted_timers.emplace(t); + return true; } void cluster::tick_timers() { - std::vector scheduled; - { - time_t now = time(nullptr); - std::lock_guard l(timer_guard); - for (auto i = next_timer.begin(); i != next_timer.end(); ++i) { - if (now >= i->second->next_tick) { - scheduled.push_back(i->second); - } else { - /* The first time we encounter an entry which is not due, - * we can bail out, because std::map is ordered storage so - * we know at this point no more will match either. - */ + time_t now = time(nullptr); + + if (next_timer.empty()) { + return; + } + do { + timer_t cur_timer; + { + std::lock_guard l(timer_guard); + cur_timer = next_timer.top(); + if (cur_timer.next_tick > now) { + /* Nothing to do */ break; } + next_timer.pop(); } - } - for (auto & t : scheduled) { - timer handle = t->handle; - /* Call handler */ - t->on_tick(t->handle); - /* Reschedule if it wasn't deleted. - * Note: We wrap the .contains() check in a lambda as it needs locking - * for thread safety, but timer_rescheudle also locks the container, so this - * is the cleanest way to do it. - */ - bool not_deleted = ([handle, this]() -> bool { - std::lock_guard l(timer_guard); - return timer_list.find(handle) != timer_list.end(); - }()); - if (not_deleted) { - timer_reschedule(t); + timers_deleted_t::iterator deleted_iter{}; + bool deleted{}; + { + deleted_iter = deleted_timers.find(cur_timer.handle); + deleted = deleted_iter != deleted_timers.end(); } - } + + if (!deleted) { + cur_timer.on_tick(cur_timer.handle); + cur_timer.next_tick += cur_timer.frequency; + { + std::lock_guard l(timer_guard); + next_timer.emplace(cur_timer); + } + } else { + /* Deleted timers are not reinserted into the priority queue and their on_stop is called */ + if (cur_timer.on_stop) { + cur_timer.on_stop(cur_timer.handle); + } + { + std::lock_guard l(timer_guard); + deleted_timers.erase(deleted_iter); + } + } + + } while (true); } #ifdef DPP_CORO diff --git a/src/dpp/cluster_coro_calls.cpp b/src/dpp/cluster_coro_calls.cpp index 3ec3aeff93..d04ad63c99 100644 --- a/src/dpp/cluster_coro_calls.cpp +++ b/src/dpp/cluster_coro_calls.cpp @@ -879,8 +879,8 @@ async cluster::co_get_webhook_with_token(snowflake webh }; /* End of auto-generated definitions */ -dpp::async dpp::cluster::co_request(const std::string &url, http_method method, const std::string &postdata, const std::string &mimetype, const std::multimap &headers, const std::string &protocol, time_t request_timeout) { - return async{ [&, this] (C &&cc) { return this->request(url, method, std::forward(cc), postdata, mimetype, headers, protocol, request_timeout); }}; +dpp::async dpp::cluster::co_request(const std::string &url, http_method method, const std::string &postdata, const std::string &mimetype, const std::multimap &headers, const std::string &protocol) { + return async{ [&, this] (C &&cc) { return this->request(url, method, std::forward(cc), postdata, mimetype, headers, protocol); }}; } #endif diff --git a/src/dpp/cluster_sync_calls.cpp b/src/dpp/cluster_sync_calls.cpp deleted file mode 100644 index c49c9d9a3e..0000000000 --- a/src/dpp/cluster_sync_calls.cpp +++ /dev/null @@ -1,879 +0,0 @@ -/************************************************************************************ - * - * D++, A Lightweight C++ library for Discord - * - * Copyright 2022 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. - * - ************************************************************************************/ - - -/* Auto @generated by buildtools/make_sync_struct.php. - * - * DO NOT EDIT BY HAND! - * - * To re-generate this header file re-run the script! - */ - -#include -#include -#include - -namespace dpp { - -slashcommand_map cluster::global_bulk_command_create_sync(const std::vector &commands) { - return dpp::sync(this, static_cast &, command_completion_event_t)>(&cluster::global_bulk_command_create), commands); -} - -slashcommand_map cluster::global_bulk_command_delete_sync() { - return dpp::sync(this, static_cast(&cluster::global_bulk_command_delete)); -} - -slashcommand cluster::global_command_create_sync(const slashcommand &s) { - return dpp::sync(this, static_cast(&cluster::global_command_create), s); -} - -slashcommand cluster::global_command_get_sync(snowflake id) { - return dpp::sync(this, static_cast(&cluster::global_command_get), id); -} - -confirmation cluster::global_command_delete_sync(snowflake id) { - return dpp::sync(this, static_cast(&cluster::global_command_delete), id); -} - -confirmation cluster::global_command_edit_sync(const slashcommand &s) { - return dpp::sync(this, static_cast(&cluster::global_command_edit), s); -} - -slashcommand_map cluster::global_commands_get_sync() { - return dpp::sync(this, static_cast(&cluster::global_commands_get)); -} - -slashcommand_map cluster::guild_bulk_command_create_sync(const std::vector &commands, snowflake guild_id) { - return dpp::sync(this, static_cast &, snowflake, command_completion_event_t)>(&cluster::guild_bulk_command_create), commands, guild_id); -} - -slashcommand_map cluster::guild_bulk_command_delete_sync(snowflake guild_id) { - return dpp::sync(this, static_cast(&cluster::guild_bulk_command_delete), guild_id); -} - -guild_command_permissions_map cluster::guild_commands_get_permissions_sync(snowflake guild_id) { - return dpp::sync(this, static_cast(&cluster::guild_commands_get_permissions), guild_id); -} - -guild_command_permissions_map cluster::guild_bulk_command_edit_permissions_sync(const std::vector &commands, snowflake guild_id) { - return dpp::sync(this, static_cast &, snowflake, command_completion_event_t)>(&cluster::guild_bulk_command_edit_permissions), commands, guild_id); -} - -slashcommand cluster::guild_command_create_sync(const slashcommand &s, snowflake guild_id) { - return dpp::sync(this, static_cast(&cluster::guild_command_create), s, guild_id); -} - -confirmation cluster::guild_command_delete_sync(snowflake id, snowflake guild_id) { - return dpp::sync(this, static_cast(&cluster::guild_command_delete), id, guild_id); -} - -confirmation cluster::guild_command_edit_permissions_sync(const slashcommand &s, snowflake guild_id) { - return dpp::sync(this, static_cast(&cluster::guild_command_edit_permissions), s, guild_id); -} - -slashcommand cluster::guild_command_get_sync(snowflake id, snowflake guild_id) { - return dpp::sync(this, static_cast(&cluster::guild_command_get), id, guild_id); -} - -guild_command_permissions cluster::guild_command_get_permissions_sync(snowflake id, snowflake guild_id) { - return dpp::sync(this, static_cast(&cluster::guild_command_get_permissions), id, guild_id); -} - -confirmation cluster::guild_command_edit_sync(const slashcommand &s, snowflake guild_id) { - return dpp::sync(this, static_cast(&cluster::guild_command_edit), s, guild_id); -} - -slashcommand_map cluster::guild_commands_get_sync(snowflake guild_id) { - return dpp::sync(this, static_cast(&cluster::guild_commands_get), guild_id); -} - -confirmation cluster::interaction_response_create_sync(snowflake interaction_id, const std::string &token, const interaction_response &r) { - return dpp::sync(this, static_cast(&cluster::interaction_response_create), interaction_id, token, r); -} - -confirmation cluster::interaction_response_edit_sync(const std::string &token, const message &m) { - return dpp::sync(this, static_cast(&cluster::interaction_response_edit), token, m); -} - -message cluster::interaction_response_get_original_sync(const std::string &token) { - return dpp::sync(this, static_cast(&cluster::interaction_response_get_original), token); -} - -confirmation cluster::interaction_followup_create_sync(const std::string &token, const message &m) { - return dpp::sync(this, static_cast(&cluster::interaction_followup_create), token, m); -} - -confirmation cluster::interaction_followup_edit_original_sync(const std::string &token, const message &m) { - return dpp::sync(this, static_cast(&cluster::interaction_followup_edit_original), token, m); -} - -confirmation cluster::interaction_followup_delete_sync(const std::string &token) { - return dpp::sync(this, static_cast(&cluster::interaction_followup_delete), token); -} - -confirmation cluster::interaction_followup_edit_sync(const std::string &token, const message &m) { - return dpp::sync(this, static_cast(&cluster::interaction_followup_edit), token, m); -} - -message cluster::interaction_followup_get_sync(const std::string &token, snowflake message_id) { - return dpp::sync(this, static_cast(&cluster::interaction_followup_get), token, message_id); -} - -message cluster::interaction_followup_get_original_sync(const std::string &token) { - return dpp::sync(this, static_cast(&cluster::interaction_followup_get_original), token); -} - -automod_rule_map cluster::automod_rules_get_sync(snowflake guild_id) { - return dpp::sync(this, static_cast(&cluster::automod_rules_get), guild_id); -} - -automod_rule cluster::automod_rule_get_sync(snowflake guild_id, snowflake rule_id) { - return dpp::sync(this, static_cast(&cluster::automod_rule_get), guild_id, rule_id); -} - -automod_rule cluster::automod_rule_create_sync(snowflake guild_id, const automod_rule& r) { - return dpp::sync(this, static_cast(&cluster::automod_rule_create), guild_id, r); -} - -automod_rule cluster::automod_rule_edit_sync(snowflake guild_id, const automod_rule& r) { - return dpp::sync(this, static_cast(&cluster::automod_rule_edit), guild_id, r); -} - -confirmation cluster::automod_rule_delete_sync(snowflake guild_id, snowflake rule_id) { - return dpp::sync(this, static_cast(&cluster::automod_rule_delete), guild_id, rule_id); -} - -channel cluster::channel_create_sync(const class channel &c) { - return dpp::sync(this, static_cast(&cluster::channel_create), c); -} - -confirmation cluster::channel_delete_permission_sync(const class channel &c, snowflake overwrite_id) { - return dpp::sync(this, static_cast(&cluster::channel_delete_permission), c, overwrite_id); -} - -confirmation cluster::channel_delete_sync(snowflake channel_id) { - return dpp::sync(this, static_cast(&cluster::channel_delete), channel_id); -} - -confirmation cluster::channel_edit_permissions_sync(const class channel &c, const snowflake overwrite_id, const uint64_t allow, const uint64_t deny, const bool member) { - return dpp::sync(this, static_cast(&cluster::channel_edit_permissions), c, overwrite_id, allow, deny, member); -} - -confirmation cluster::channel_edit_permissions_sync(const snowflake channel_id, const snowflake overwrite_id, const uint64_t allow, const uint64_t deny, const bool member) { - return dpp::sync(this, static_cast(&cluster::channel_edit_permissions), channel_id, overwrite_id, allow, deny, member); -} - -confirmation cluster::channel_edit_positions_sync(const std::vector &c) { - return dpp::sync(this, static_cast &, command_completion_event_t)>(&cluster::channel_edit_positions), c); -} - -channel cluster::channel_edit_sync(const class channel &c) { - return dpp::sync(this, static_cast(&cluster::channel_edit), c); -} - -confirmation cluster::channel_follow_news_sync(const class channel &c, snowflake target_channel_id) { - return dpp::sync(this, static_cast(&cluster::channel_follow_news), c, target_channel_id); -} - -channel cluster::channel_get_sync(snowflake c) { - return dpp::sync(this, static_cast(&cluster::channel_get), c); -} - -invite cluster::channel_invite_create_sync(const class channel &c, const class invite &i) { - return dpp::sync(this, static_cast(&cluster::channel_invite_create), c, i); -} - -invite_map cluster::channel_invites_get_sync(const class channel &c) { - return dpp::sync(this, static_cast(&cluster::channel_invites_get), c); -} - -confirmation cluster::channel_typing_sync(const class channel &c) { - return dpp::sync(this, static_cast(&cluster::channel_typing), c); -} - -confirmation cluster::channel_typing_sync(snowflake cid) { - return dpp::sync(this, static_cast(&cluster::channel_typing), cid); -} - -channel_map cluster::channels_get_sync(snowflake guild_id) { - return dpp::sync(this, static_cast(&cluster::channels_get), guild_id); -} - -confirmation cluster::channel_set_voice_status_sync(snowflake channel_id, const std::string& status) { - return dpp::sync(this, static_cast(&cluster::channel_set_voice_status), channel_id, status); -} - -channel cluster::create_dm_channel_sync(snowflake user_id) { - return dpp::sync(this, static_cast(&cluster::create_dm_channel), user_id); -} - -channel_map cluster::current_user_get_dms_sync() { - return dpp::sync(this, static_cast(&cluster::current_user_get_dms)); -} - -message cluster::direct_message_create_sync(snowflake user_id, const message &m) { - return dpp::sync(this, static_cast(&cluster::direct_message_create), user_id, m); -} - -confirmation cluster::gdm_add_sync(snowflake channel_id, snowflake user_id, const std::string &access_token, const std::string &nick) { - return dpp::sync(this, static_cast(&cluster::gdm_add), channel_id, user_id, access_token, nick); -} - -confirmation cluster::gdm_remove_sync(snowflake channel_id, snowflake user_id) { - return dpp::sync(this, static_cast(&cluster::gdm_remove), channel_id, user_id); -} - -emoji cluster::guild_emoji_create_sync(snowflake guild_id, const class emoji& newemoji) { - return dpp::sync(this, static_cast(&cluster::guild_emoji_create), guild_id, newemoji); -} - -confirmation cluster::guild_emoji_delete_sync(snowflake guild_id, snowflake emoji_id) { - return dpp::sync(this, static_cast(&cluster::guild_emoji_delete), guild_id, emoji_id); -} - -emoji cluster::guild_emoji_edit_sync(snowflake guild_id, const class emoji& newemoji) { - return dpp::sync(this, static_cast(&cluster::guild_emoji_edit), guild_id, newemoji); -} - -emoji cluster::guild_emoji_get_sync(snowflake guild_id, snowflake emoji_id) { - return dpp::sync(this, static_cast(&cluster::guild_emoji_get), guild_id, emoji_id); -} - -emoji_map cluster::guild_emojis_get_sync(snowflake guild_id) { - return dpp::sync(this, static_cast(&cluster::guild_emojis_get), guild_id); -} - -emoji_map cluster::application_emojis_get_sync() { - return dpp::sync(this, static_cast(&cluster::application_emojis_get)); -} - -emoji cluster::application_emoji_get_sync(snowflake emoji_id) { - return dpp::sync(this, static_cast(&cluster::application_emoji_get), emoji_id); -} - -emoji cluster::application_emoji_create_sync(const class emoji& newemoji) { - return dpp::sync(this, static_cast(&cluster::application_emoji_create), newemoji); -} - -emoji cluster::application_emoji_edit_sync(const class emoji& newemoji) { - return dpp::sync(this, static_cast(&cluster::application_emoji_edit), newemoji); -} - -confirmation cluster::application_emoji_delete_sync(snowflake emoji_id) { - return dpp::sync(this, static_cast(&cluster::application_emoji_delete), emoji_id); -} - -entitlement_map cluster::entitlements_get_sync(snowflake user_id, const std::vector& sku_ids, snowflake before_id, snowflake after_id, uint8_t limit, snowflake guild_id, bool exclude_ended) { - return dpp::sync(this, static_cast&, snowflake, snowflake, uint8_t, snowflake, bool, command_completion_event_t)>(&cluster::entitlements_get), user_id, sku_ids, before_id, after_id, limit, guild_id, exclude_ended); -} - -entitlement cluster::entitlement_test_create_sync(const class entitlement& new_entitlement) { - return dpp::sync(this, static_cast(&cluster::entitlement_test_create), new_entitlement); -} - -confirmation cluster::entitlement_test_delete_sync(const class snowflake entitlement_id) { - return dpp::sync(this, static_cast(&cluster::entitlement_test_delete), entitlement_id); -} - -confirmation cluster::entitlement_consume_sync(const class snowflake entitlement_id) { - return dpp::sync(this, static_cast(&cluster::entitlement_consume), entitlement_id); -} - -gateway cluster::get_gateway_bot_sync() { - return dpp::sync(this, static_cast(&cluster::get_gateway_bot)); -} - -confirmation cluster::guild_current_member_edit_sync(snowflake guild_id, const std::string &nickname) { - return dpp::sync(this, static_cast(&cluster::guild_current_member_edit), guild_id, nickname); -} - -auditlog cluster::guild_auditlog_get_sync(snowflake guild_id, snowflake user_id, uint32_t action_type, snowflake before, snowflake after, uint32_t limit) { - return dpp::sync(this, static_cast(&cluster::guild_auditlog_get), guild_id, user_id, action_type, before, after, limit); -} - -confirmation cluster::guild_ban_add_sync(snowflake guild_id, snowflake user_id, uint32_t delete_message_seconds) { - return dpp::sync(this, static_cast(&cluster::guild_ban_add), guild_id, user_id, delete_message_seconds); -} - -confirmation cluster::guild_ban_delete_sync(snowflake guild_id, snowflake user_id) { - return dpp::sync(this, static_cast(&cluster::guild_ban_delete), guild_id, user_id); -} - -guild cluster::guild_create_sync(const class guild &g) { - return dpp::sync(this, static_cast(&cluster::guild_create), g); -} - -confirmation cluster::guild_delete_sync(snowflake guild_id) { - return dpp::sync(this, static_cast(&cluster::guild_delete), guild_id); -} - -confirmation cluster::guild_delete_integration_sync(snowflake guild_id, snowflake integration_id) { - return dpp::sync(this, static_cast(&cluster::guild_delete_integration), guild_id, integration_id); -} - -guild cluster::guild_edit_sync(const class guild &g) { - return dpp::sync(this, static_cast(&cluster::guild_edit), g); -} - -guild_widget cluster::guild_edit_widget_sync(snowflake guild_id, const class guild_widget &gw) { - return dpp::sync(this, static_cast(&cluster::guild_edit_widget), guild_id, gw); -} - -ban cluster::guild_get_ban_sync(snowflake guild_id, snowflake user_id) { - return dpp::sync(this, static_cast(&cluster::guild_get_ban), guild_id, user_id); -} - -ban_map cluster::guild_get_bans_sync(snowflake guild_id, snowflake before, snowflake after, snowflake limit) { - return dpp::sync(this, static_cast(&cluster::guild_get_bans), guild_id, before, after, limit); -} - -guild cluster::guild_get_sync(snowflake guild_id) { - return dpp::sync(this, static_cast(&cluster::guild_get), guild_id); -} - -integration_map cluster::guild_get_integrations_sync(snowflake guild_id) { - return dpp::sync(this, static_cast(&cluster::guild_get_integrations), guild_id); -} - -guild cluster::guild_get_preview_sync(snowflake guild_id) { - return dpp::sync(this, static_cast(&cluster::guild_get_preview), guild_id); -} - -invite cluster::guild_get_vanity_sync(snowflake guild_id) { - return dpp::sync(this, static_cast(&cluster::guild_get_vanity), guild_id); -} - -guild_widget cluster::guild_get_widget_sync(snowflake guild_id) { - return dpp::sync(this, static_cast(&cluster::guild_get_widget), guild_id); -} - -confirmation cluster::guild_modify_integration_sync(snowflake guild_id, const class integration &i) { - return dpp::sync(this, static_cast(&cluster::guild_modify_integration), guild_id, i); -} - -prune cluster::guild_get_prune_counts_sync(snowflake guild_id, const struct prune& pruneinfo) { - return dpp::sync(this, static_cast(&cluster::guild_get_prune_counts), guild_id, pruneinfo); -} - -prune cluster::guild_begin_prune_sync(snowflake guild_id, const struct prune& pruneinfo) { - return dpp::sync(this, static_cast(&cluster::guild_begin_prune), guild_id, pruneinfo); -} - -confirmation cluster::guild_set_nickname_sync(snowflake guild_id, const std::string &nickname) { - return dpp::sync(this, static_cast(&cluster::guild_set_nickname), guild_id, nickname); -} - -confirmation cluster::guild_sync_integration_sync(snowflake guild_id, snowflake integration_id) { - return dpp::sync(this, static_cast(&cluster::guild_sync_integration), guild_id, integration_id); -} - -onboarding cluster::guild_get_onboarding_sync(snowflake guild_id) { - return dpp::sync(this, static_cast(&cluster::guild_get_onboarding), guild_id); -} - -onboarding cluster::guild_edit_onboarding_sync(const struct onboarding& o) { - return dpp::sync(this, static_cast(&cluster::guild_edit_onboarding), o); -} - -dpp::welcome_screen cluster::guild_get_welcome_screen_sync(snowflake guild_id) { - return dpp::sync(this, static_cast(&cluster::guild_get_welcome_screen), guild_id); -} - -dpp::welcome_screen cluster::guild_edit_welcome_screen_sync(snowflake guild_id, const struct welcome_screen& welcome_screen, bool enabled) { - return dpp::sync(this, static_cast(&cluster::guild_edit_welcome_screen), guild_id, welcome_screen, enabled); -} - -confirmation cluster::guild_add_member_sync(const guild_member& gm, const std::string &access_token) { - return dpp::sync(this, static_cast(&cluster::guild_add_member), gm, access_token); -} - -guild_member cluster::guild_edit_member_sync(const guild_member& gm) { - return dpp::sync(this, static_cast(&cluster::guild_edit_member), gm); -} - -guild_member cluster::guild_get_member_sync(snowflake guild_id, snowflake user_id) { - return dpp::sync(this, static_cast(&cluster::guild_get_member), guild_id, user_id); -} - -guild_member_map cluster::guild_get_members_sync(snowflake guild_id, uint16_t limit, snowflake after) { - return dpp::sync(this, static_cast(&cluster::guild_get_members), guild_id, limit, after); -} - -confirmation cluster::guild_member_add_role_sync(snowflake guild_id, snowflake user_id, snowflake role_id) { - return dpp::sync(this, static_cast(&cluster::guild_member_add_role), guild_id, user_id, role_id); -} - -confirmation cluster::guild_member_delete_sync(snowflake guild_id, snowflake user_id) { - return dpp::sync(this, static_cast(&cluster::guild_member_delete), guild_id, user_id); -} - -confirmation cluster::guild_member_kick_sync(snowflake guild_id, snowflake user_id) { - return dpp::sync(this, static_cast(&cluster::guild_member_kick), guild_id, user_id); -} - -confirmation cluster::guild_member_timeout_sync(snowflake guild_id, snowflake user_id, time_t communication_disabled_until) { - return dpp::sync(this, static_cast(&cluster::guild_member_timeout), guild_id, user_id, communication_disabled_until); -} - -confirmation cluster::guild_member_timeout_remove_sync(snowflake guild_id, snowflake user_id) { - return dpp::sync(this, static_cast(&cluster::guild_member_timeout_remove), guild_id, user_id); -} - -confirmation cluster::guild_member_delete_role_sync(snowflake guild_id, snowflake user_id, snowflake role_id) { - return dpp::sync(this, static_cast(&cluster::guild_member_delete_role), guild_id, user_id, role_id); -} - -confirmation cluster::guild_member_remove_role_sync(snowflake guild_id, snowflake user_id, snowflake role_id) { - return dpp::sync(this, static_cast(&cluster::guild_member_remove_role), guild_id, user_id, role_id); -} - -guild_member cluster::guild_member_move_sync(const snowflake channel_id, const snowflake guild_id, const snowflake user_id) { - return dpp::sync(this, static_cast(&cluster::guild_member_move), channel_id, guild_id, user_id); -} - -guild_member_map cluster::guild_search_members_sync(snowflake guild_id, const std::string& query, uint16_t limit) { - return dpp::sync(this, static_cast(&cluster::guild_search_members), guild_id, query, limit); -} - -invite_map cluster::guild_get_invites_sync(snowflake guild_id) { - return dpp::sync(this, static_cast(&cluster::guild_get_invites), guild_id); -} - -invite cluster::invite_delete_sync(const std::string &invitecode) { - return dpp::sync(this, static_cast(&cluster::invite_delete), invitecode); -} - -invite cluster::invite_get_sync(const std::string &invite_code) { - return dpp::sync(this, static_cast(&cluster::invite_get), invite_code); -} - -confirmation cluster::message_add_reaction_sync(const struct message &m, const std::string &reaction) { - return dpp::sync(this, static_cast(&cluster::message_add_reaction), m, reaction); -} - -confirmation cluster::message_add_reaction_sync(snowflake message_id, snowflake channel_id, const std::string &reaction) { - return dpp::sync(this, static_cast(&cluster::message_add_reaction), message_id, channel_id, reaction); -} - -message cluster::message_create_sync(const message &m) { - return dpp::sync(this, static_cast(&cluster::message_create), m); -} - -message cluster::message_crosspost_sync(snowflake message_id, snowflake channel_id) { - return dpp::sync(this, static_cast(&cluster::message_crosspost), message_id, channel_id); -} - -confirmation cluster::message_delete_all_reactions_sync(const struct message &m) { - return dpp::sync(this, static_cast(&cluster::message_delete_all_reactions), m); -} - -confirmation cluster::message_delete_all_reactions_sync(snowflake message_id, snowflake channel_id) { - return dpp::sync(this, static_cast(&cluster::message_delete_all_reactions), message_id, channel_id); -} - -confirmation cluster::message_delete_bulk_sync(const std::vector& message_ids, snowflake channel_id) { - return dpp::sync(this, static_cast&, snowflake, command_completion_event_t)>(&cluster::message_delete_bulk), message_ids, channel_id); -} - -confirmation cluster::message_delete_sync(snowflake message_id, snowflake channel_id) { - return dpp::sync(this, static_cast(&cluster::message_delete), message_id, channel_id); -} - -confirmation cluster::message_delete_own_reaction_sync(const struct message &m, const std::string &reaction) { - return dpp::sync(this, static_cast(&cluster::message_delete_own_reaction), m, reaction); -} - -confirmation cluster::message_delete_own_reaction_sync(snowflake message_id, snowflake channel_id, const std::string &reaction) { - return dpp::sync(this, static_cast(&cluster::message_delete_own_reaction), message_id, channel_id, reaction); -} - -confirmation cluster::message_delete_reaction_sync(const struct message &m, snowflake user_id, const std::string &reaction) { - return dpp::sync(this, static_cast(&cluster::message_delete_reaction), m, user_id, reaction); -} - -confirmation cluster::message_delete_reaction_sync(snowflake message_id, snowflake channel_id, snowflake user_id, const std::string &reaction) { - return dpp::sync(this, static_cast(&cluster::message_delete_reaction), message_id, channel_id, user_id, reaction); -} - -confirmation cluster::message_delete_reaction_emoji_sync(const struct message &m, const std::string &reaction) { - return dpp::sync(this, static_cast(&cluster::message_delete_reaction_emoji), m, reaction); -} - -confirmation cluster::message_delete_reaction_emoji_sync(snowflake message_id, snowflake channel_id, const std::string &reaction) { - return dpp::sync(this, static_cast(&cluster::message_delete_reaction_emoji), message_id, channel_id, reaction); -} - -message cluster::message_edit_sync(const message &m) { - return dpp::sync(this, static_cast(&cluster::message_edit), m); -} - -message cluster::message_edit_flags_sync(const message &m) { - return dpp::sync(this, static_cast(&cluster::message_edit_flags), m); -} - -message cluster::message_get_sync(snowflake message_id, snowflake channel_id) { - return dpp::sync(this, static_cast(&cluster::message_get), message_id, channel_id); -} - -user_map cluster::message_get_reactions_sync(const struct message &m, const std::string &reaction, snowflake before, snowflake after, snowflake limit) { - return dpp::sync(this, static_cast(&cluster::message_get_reactions), m, reaction, before, after, limit); -} - -emoji_map cluster::message_get_reactions_sync(snowflake message_id, snowflake channel_id, const std::string &reaction, snowflake before, snowflake after, snowflake limit) { - return dpp::sync(this, static_cast(&cluster::message_get_reactions), message_id, channel_id, reaction, before, after, limit); -} - -confirmation cluster::message_pin_sync(snowflake channel_id, snowflake message_id) { - return dpp::sync(this, static_cast(&cluster::message_pin), channel_id, message_id); -} - -message_map cluster::messages_get_sync(snowflake channel_id, snowflake around, snowflake before, snowflake after, uint64_t limit) { - return dpp::sync(this, static_cast(&cluster::messages_get), channel_id, around, before, after, limit); -} - -confirmation cluster::message_unpin_sync(snowflake channel_id, snowflake message_id) { - return dpp::sync(this, static_cast(&cluster::message_unpin), channel_id, message_id); -} - -user_map cluster::poll_get_answer_voters_sync(const message& m, uint32_t answer_id, snowflake after, uint64_t limit) { - return dpp::sync(this, static_cast(&cluster::poll_get_answer_voters), m, answer_id, after, limit); -} - -user_map cluster::poll_get_answer_voters_sync(snowflake message_id, snowflake channel_id, uint32_t answer_id, snowflake after, uint64_t limit) { - return dpp::sync(this, static_cast(&cluster::poll_get_answer_voters), message_id, channel_id, answer_id, after, limit); -} - -message cluster::poll_end_sync(const message &m) { - return dpp::sync(this, static_cast(&cluster::poll_end), m); -} - -message cluster::poll_end_sync(snowflake message_id, snowflake channel_id) { - return dpp::sync(this, static_cast(&cluster::poll_end), message_id, channel_id); -} - -message_map cluster::channel_pins_get_sync(snowflake channel_id) { - return dpp::sync(this, static_cast(&cluster::channel_pins_get), channel_id); -} - -role cluster::role_create_sync(const class role &r) { - return dpp::sync(this, static_cast(&cluster::role_create), r); -} - -confirmation cluster::role_delete_sync(snowflake guild_id, snowflake role_id) { - return dpp::sync(this, static_cast(&cluster::role_delete), guild_id, role_id); -} - -role cluster::role_edit_sync(const class role &r) { - return dpp::sync(this, static_cast(&cluster::role_edit), r); -} - -role_map cluster::roles_edit_position_sync(snowflake guild_id, const std::vector &roles) { - return dpp::sync(this, static_cast &, command_completion_event_t)>(&cluster::roles_edit_position), guild_id, roles); -} - -role_map cluster::roles_get_sync(snowflake guild_id) { - return dpp::sync(this, static_cast(&cluster::roles_get), guild_id); -} - -application_role_connection cluster::application_role_connection_get_sync(snowflake application_id) { - return dpp::sync(this, static_cast(&cluster::application_role_connection_get), application_id); -} - -application_role_connection cluster::application_role_connection_update_sync(snowflake application_id, const std::vector &connection_metadata) { - return dpp::sync(this, static_cast &, command_completion_event_t)>(&cluster::application_role_connection_update), application_id, connection_metadata); -} - -application_role_connection cluster::user_application_role_connection_get_sync(snowflake application_id) { - return dpp::sync(this, static_cast(&cluster::user_application_role_connection_get), application_id); -} - -application_role_connection cluster::user_application_role_connection_update_sync(snowflake application_id, const application_role_connection &connection) { - return dpp::sync(this, static_cast(&cluster::user_application_role_connection_update), application_id, connection); -} - -scheduled_event_map cluster::guild_events_get_sync(snowflake guild_id) { - return dpp::sync(this, static_cast(&cluster::guild_events_get), guild_id); -} - -scheduled_event cluster::guild_event_create_sync(const scheduled_event& event) { - return dpp::sync(this, static_cast(&cluster::guild_event_create), event); -} - -confirmation cluster::guild_event_delete_sync(snowflake event_id, snowflake guild_id) { - return dpp::sync(this, static_cast(&cluster::guild_event_delete), event_id, guild_id); -} - -scheduled_event cluster::guild_event_edit_sync(const scheduled_event& event) { - return dpp::sync(this, static_cast(&cluster::guild_event_edit), event); -} - -scheduled_event cluster::guild_event_get_sync(snowflake guild_id, snowflake event_id) { - return dpp::sync(this, static_cast(&cluster::guild_event_get), guild_id, event_id); -} - -sku_map cluster::skus_get_sync() { - return dpp::sync(this, static_cast(&cluster::skus_get)); -} - -stage_instance cluster::stage_instance_create_sync(const stage_instance& si) { - return dpp::sync(this, static_cast(&cluster::stage_instance_create), si); -} - -stage_instance cluster::stage_instance_get_sync(const snowflake channel_id) { - return dpp::sync(this, static_cast(&cluster::stage_instance_get), channel_id); -} - -stage_instance cluster::stage_instance_edit_sync(const stage_instance& si) { - return dpp::sync(this, static_cast(&cluster::stage_instance_edit), si); -} - -confirmation cluster::stage_instance_delete_sync(const snowflake channel_id) { - return dpp::sync(this, static_cast(&cluster::stage_instance_delete), channel_id); -} - -sticker cluster::guild_sticker_create_sync(const sticker &s) { - return dpp::sync(this, static_cast(&cluster::guild_sticker_create), s); -} - -confirmation cluster::guild_sticker_delete_sync(snowflake sticker_id, snowflake guild_id) { - return dpp::sync(this, static_cast(&cluster::guild_sticker_delete), sticker_id, guild_id); -} - -sticker cluster::guild_sticker_get_sync(snowflake id, snowflake guild_id) { - return dpp::sync(this, static_cast(&cluster::guild_sticker_get), id, guild_id); -} - -sticker cluster::guild_sticker_modify_sync(const sticker &s) { - return dpp::sync(this, static_cast(&cluster::guild_sticker_modify), s); -} - -sticker_map cluster::guild_stickers_get_sync(snowflake guild_id) { - return dpp::sync(this, static_cast(&cluster::guild_stickers_get), guild_id); -} - -sticker cluster::nitro_sticker_get_sync(snowflake id) { - return dpp::sync(this, static_cast(&cluster::nitro_sticker_get), id); -} - -sticker_pack_map cluster::sticker_packs_get_sync() { - return dpp::sync(this, static_cast(&cluster::sticker_packs_get)); -} - -guild cluster::guild_create_from_template_sync(const std::string &code, const std::string &name) { - return dpp::sync(this, static_cast(&cluster::guild_create_from_template), code, name); -} - -dtemplate cluster::guild_template_create_sync(snowflake guild_id, const std::string &name, const std::string &description) { - return dpp::sync(this, static_cast(&cluster::guild_template_create), guild_id, name, description); -} - -confirmation cluster::guild_template_delete_sync(snowflake guild_id, const std::string &code) { - return dpp::sync(this, static_cast(&cluster::guild_template_delete), guild_id, code); -} - -dtemplate cluster::guild_template_modify_sync(snowflake guild_id, const std::string &code, const std::string &name, const std::string &description) { - return dpp::sync(this, static_cast(&cluster::guild_template_modify), guild_id, code, name, description); -} - -dtemplate_map cluster::guild_templates_get_sync(snowflake guild_id) { - return dpp::sync(this, static_cast(&cluster::guild_templates_get), guild_id); -} - -dtemplate cluster::guild_template_sync_sync(snowflake guild_id, const std::string &code) { - return dpp::sync(this, static_cast(&cluster::guild_template_sync), guild_id, code); -} - -dtemplate cluster::template_get_sync(const std::string &code) { - return dpp::sync(this, static_cast(&cluster::template_get), code); -} - -confirmation cluster::current_user_join_thread_sync(snowflake thread_id) { - return dpp::sync(this, static_cast(&cluster::current_user_join_thread), thread_id); -} - -confirmation cluster::current_user_leave_thread_sync(snowflake thread_id) { - return dpp::sync(this, static_cast(&cluster::current_user_leave_thread), thread_id); -} - -active_threads cluster::threads_get_active_sync(snowflake guild_id) { - return dpp::sync(this, static_cast(&cluster::threads_get_active), guild_id); -} - -thread_map cluster::threads_get_joined_private_archived_sync(snowflake channel_id, snowflake before_id, uint16_t limit) { - return dpp::sync(this, static_cast(&cluster::threads_get_joined_private_archived), channel_id, before_id, limit); -} - -thread_map cluster::threads_get_private_archived_sync(snowflake channel_id, time_t before_timestamp, uint16_t limit) { - return dpp::sync(this, static_cast(&cluster::threads_get_private_archived), channel_id, before_timestamp, limit); -} - -thread_map cluster::threads_get_public_archived_sync(snowflake channel_id, time_t before_timestamp, uint16_t limit) { - return dpp::sync(this, static_cast(&cluster::threads_get_public_archived), channel_id, before_timestamp, limit); -} - -thread_member cluster::thread_member_get_sync(const snowflake thread_id, const snowflake user_id) { - return dpp::sync(this, static_cast(&cluster::thread_member_get), thread_id, user_id); -} - -thread_member_map cluster::thread_members_get_sync(snowflake thread_id) { - return dpp::sync(this, static_cast(&cluster::thread_members_get), thread_id); -} - -thread cluster::thread_create_in_forum_sync(const std::string& thread_name, snowflake channel_id, const message& msg, auto_archive_duration_t auto_archive_duration, uint16_t rate_limit_per_user, std::vector applied_tags) { - return dpp::sync(this, static_cast, command_completion_event_t)>(&cluster::thread_create_in_forum), thread_name, channel_id, msg, auto_archive_duration, rate_limit_per_user, applied_tags); -} - -thread cluster::thread_create_sync(const std::string& thread_name, snowflake channel_id, uint16_t auto_archive_duration, channel_type thread_type, bool invitable, uint16_t rate_limit_per_user) { - return dpp::sync(this, static_cast(&cluster::thread_create), thread_name, channel_id, auto_archive_duration, thread_type, invitable, rate_limit_per_user); -} - -thread cluster::thread_edit_sync(const thread &t) { - return dpp::sync(this, static_cast(&cluster::thread_edit), t); -} - -thread cluster::thread_create_with_message_sync(const std::string& thread_name, snowflake channel_id, snowflake message_id, uint16_t auto_archive_duration, uint16_t rate_limit_per_user) { - return dpp::sync(this, static_cast(&cluster::thread_create_with_message), thread_name, channel_id, message_id, auto_archive_duration, rate_limit_per_user); -} - -confirmation cluster::thread_member_add_sync(snowflake thread_id, snowflake user_id) { - return dpp::sync(this, static_cast(&cluster::thread_member_add), thread_id, user_id); -} - -confirmation cluster::thread_member_remove_sync(snowflake thread_id, snowflake user_id) { - return dpp::sync(this, static_cast(&cluster::thread_member_remove), thread_id, user_id); -} - -thread cluster::thread_get_sync(snowflake thread_id) { - return dpp::sync(this, static_cast(&cluster::thread_get), thread_id); -} - -user cluster::current_user_edit_sync(const std::string &nickname, const std::string& avatar_blob, const image_type avatar_type, const std::string& banner_blob, const image_type banner_type) { - return dpp::sync(this, static_cast(&cluster::current_user_edit), nickname, avatar_blob, avatar_type, banner_blob, banner_type); -} - -application cluster::current_application_get_sync() { - return dpp::sync(this, static_cast(&cluster::current_application_get)); -} - -user_identified cluster::current_user_get_sync() { - return dpp::sync(this, static_cast(&cluster::current_user_get)); -} - -confirmation cluster::current_user_set_voice_state_sync(snowflake guild_id, snowflake channel_id, bool suppress, time_t request_to_speak_timestamp) { - return dpp::sync(this, static_cast(&cluster::current_user_set_voice_state), guild_id, channel_id, suppress, request_to_speak_timestamp); -} - -voicestate cluster::current_user_get_voice_state_sync(snowflake guild_id) { - return dpp::sync(this, static_cast(&cluster::current_user_get_voice_state), guild_id); -} - -confirmation cluster::user_set_voice_state_sync(snowflake user_id, snowflake guild_id, snowflake channel_id, bool suppress) { - return dpp::sync(this, static_cast(&cluster::user_set_voice_state), user_id, guild_id, channel_id, suppress); -} - -voicestate cluster::user_get_voice_state_sync(snowflake guild_id, snowflake user_id) { - return dpp::sync(this, static_cast(&cluster::user_get_voice_state), guild_id, user_id); -} - -connection_map cluster::current_user_connections_get_sync() { - return dpp::sync(this, static_cast(&cluster::current_user_connections_get)); -} - -guild_map cluster::current_user_get_guilds_sync() { - return dpp::sync(this, static_cast(&cluster::current_user_get_guilds)); -} - -confirmation cluster::current_user_leave_guild_sync(snowflake guild_id) { - return dpp::sync(this, static_cast(&cluster::current_user_leave_guild), guild_id); -} - -user_identified cluster::user_get_sync(snowflake user_id) { - return dpp::sync(this, static_cast(&cluster::user_get), user_id); -} - -user_identified cluster::user_get_cached_sync(snowflake user_id) { - return dpp::sync(this, static_cast(&cluster::user_get_cached), user_id); -} - -voiceregion_map cluster::get_voice_regions_sync() { - return dpp::sync(this, static_cast(&cluster::get_voice_regions)); -} - -voiceregion_map cluster::guild_get_voice_regions_sync(snowflake guild_id) { - return dpp::sync(this, static_cast(&cluster::guild_get_voice_regions), guild_id); -} - -webhook cluster::create_webhook_sync(const class webhook &wh) { - return dpp::sync(this, static_cast(&cluster::create_webhook), wh); -} - -confirmation cluster::delete_webhook_sync(snowflake webhook_id) { - return dpp::sync(this, static_cast(&cluster::delete_webhook), webhook_id); -} - -confirmation cluster::delete_webhook_message_sync(const class webhook &wh, snowflake message_id, snowflake thread_id) { - return dpp::sync(this, static_cast(&cluster::delete_webhook_message), wh, message_id, thread_id); -} - -confirmation cluster::delete_webhook_with_token_sync(snowflake webhook_id, const std::string &token) { - return dpp::sync(this, static_cast(&cluster::delete_webhook_with_token), webhook_id, token); -} - -webhook cluster::edit_webhook_sync(const class webhook& wh) { - return dpp::sync(this, static_cast(&cluster::edit_webhook), wh); -} - -message cluster::edit_webhook_message_sync(const class webhook &wh, const struct message& m, snowflake thread_id) { - return dpp::sync(this, static_cast(&cluster::edit_webhook_message), wh, m, thread_id); -} - -webhook cluster::edit_webhook_with_token_sync(const class webhook& wh) { - return dpp::sync(this, static_cast(&cluster::edit_webhook_with_token), wh); -} - -message cluster::execute_webhook_sync(const class webhook &wh, const struct message& m, bool wait, snowflake thread_id, const std::string& thread_name) { - return dpp::sync(this, static_cast(&cluster::execute_webhook), wh, m, wait, thread_id, thread_name); -} - -webhook_map cluster::get_channel_webhooks_sync(snowflake channel_id) { - return dpp::sync(this, static_cast(&cluster::get_channel_webhooks), channel_id); -} - -webhook_map cluster::get_guild_webhooks_sync(snowflake guild_id) { - return dpp::sync(this, static_cast(&cluster::get_guild_webhooks), guild_id); -} - -webhook cluster::get_webhook_sync(snowflake webhook_id) { - return dpp::sync(this, static_cast(&cluster::get_webhook), webhook_id); -} - -message cluster::get_webhook_message_sync(const class webhook &wh, snowflake message_id, snowflake thread_id) { - return dpp::sync(this, static_cast(&cluster::get_webhook_message), wh, message_id, thread_id); -} - -webhook cluster::get_webhook_with_token_sync(snowflake webhook_id, const std::string &token) { - return dpp::sync(this, static_cast(&cluster::get_webhook_with_token), webhook_id, token); -} - - -}; - -/* End of auto-generated definitions */ diff --git a/src/dpp/dave/session.cpp b/src/dpp/dave/session.cpp index 8fa34409c8..9b3e410fdf 100755 --- a/src/dpp/dave/session.cpp +++ b/src/dpp/dave/session.cpp @@ -247,7 +247,7 @@ bool session::is_recognized_user_id(const ::mlspp::Credential& cred, std::set * Copyright 2021 Craig Edwards and D++ contributors * (https://github.com/brainboxdotcc/DPP/graphs/contributors) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -28,49 +28,59 @@ #include #include #include -#include - -#define PATH_UNCOMPRESSED_JSON "/?v=" DISCORD_API_VERSION "&encoding=json" -#define PATH_COMPRESSED_JSON "/?v=" DISCORD_API_VERSION "&encoding=json&compress=zlib-stream" -#define PATH_UNCOMPRESSED_ETF "/?v=" DISCORD_API_VERSION "&encoding=etf" -#define PATH_COMPRESSED_ETF "/?v=" DISCORD_API_VERSION "&encoding=etf&compress=zlib-stream" -#define DECOMP_BUFFER_SIZE 512 * 1024 +#define PATH_UNCOMPRESSED_JSON "/?v=" DISCORD_API_VERSION "&encoding=json" +#define PATH_COMPRESSED_JSON "/?v=" DISCORD_API_VERSION "&encoding=json&compress=zlib-stream" +#define PATH_UNCOMPRESSED_ETF "/?v=" DISCORD_API_VERSION "&encoding=etf" +#define PATH_COMPRESSED_ETF "/?v=" DISCORD_API_VERSION "&encoding=etf&compress=zlib-stream" #define STRINGIFY(a) STRINGIFY_(a) #define STRINGIFY_(a) #a #ifndef DPP_OS -#define DPP_OS unknown + #define DPP_OS unknown #endif namespace dpp { /** - * @brief This is an opaque class containing zlib library specific structures. - * We define it this way so that the public facing D++ library doesn't require - * the zlib headers be available to build against it. + * @brief Used in IDENTIFY to indicate what a large guild is */ -class zlibcontext { -public: - /** - * @brief Zlib stream struct - */ - z_stream d_stream; -}; +constexpr int LARGE_THRESHOLD = 250; /** - * @brief Stores the most recent ping message on this shard, which we check for to monitor latency + * @brief Resume constructor for websocket client */ -thread_local static std::string last_ping_message; +discord_client::discord_client(discord_client &old, uint64_t sequence, const std::string& session_id) + : websocket_client(old.owner, old.resume_gateway_url, "443", old.compressed ? (old.protocol == ws_json ? PATH_COMPRESSED_JSON : PATH_COMPRESSED_ETF) : (old.protocol == ws_json ? PATH_UNCOMPRESSED_JSON : PATH_UNCOMPRESSED_ETF)), + compressed(old.compressed), + zlib(nullptr), + connect_time(0), + ping_start(0.0), + etf(nullptr), + creator(old.owner), + heartbeat_interval(0), + last_heartbeat(time(nullptr)), + shard_id(old.shard_id), + max_shards(old.max_shards), + last_seq(sequence), + token(old.token), + intents(old.intents), + sessionid(session_id), + resumes(old.resumes), + reconnects(old.reconnects), + websocket_ping(old.websocket_ping), + ready(false), + last_heartbeat_ack(time(nullptr)), + protocol(old.protocol), + resume_gateway_url(old.resume_gateway_url) +{ + start_connecting(); +} discord_client::discord_client(dpp::cluster* _cluster, uint32_t _shard_id, uint32_t _max_shards, const std::string &_token, uint32_t _intents, bool comp, websocket_protocol_t ws_proto) - : websocket_client(_cluster->default_gateway, "443", comp ? (ws_proto == ws_json ? PATH_COMPRESSED_JSON : PATH_COMPRESSED_ETF) : (ws_proto == ws_json ? PATH_UNCOMPRESSED_JSON : PATH_UNCOMPRESSED_ETF)), - terminating(false), - runner(nullptr), + : websocket_client(_cluster, _cluster->default_gateway, "443", comp ? (ws_proto == ws_json ? PATH_COMPRESSED_JSON : PATH_COMPRESSED_ETF) : (ws_proto == ws_json ? PATH_UNCOMPRESSED_JSON : PATH_UNCOMPRESSED_ETF)), compressed(comp), - decomp_buffer(nullptr), zlib(nullptr), - decompressed_total(0), connect_time(0), ping_start(0.0), etf(nullptr), @@ -88,70 +98,37 @@ discord_client::discord_client(dpp::cluster* _cluster, uint32_t _shard_id, uint3 ready(false), last_heartbeat_ack(time(nullptr)), protocol(ws_proto), - resume_gateway_url(_cluster->default_gateway) + resume_gateway_url(_cluster->default_gateway) { - try { - zlib = new zlibcontext(); - etf = new etf_parser(); - } - catch (std::bad_alloc&) { - delete zlib; - delete etf; - /* Clean up and rethrow to caller */ - throw std::bad_alloc(); - } - try { - this->connect(); - } - catch (std::exception&) { - cleanup(); - throw; - } + start_connecting(); } -void discord_client::cleanup() -{ - terminating = true; - if (runner) { - runner->join(); - delete runner; +void discord_client::start_connecting() { + etf = std::make_unique(); + if (compressed) { + zlib = std::make_unique(); } - delete etf; - delete zlib; + websocket_client::connect(); } -discord_client::~discord_client() -{ - cleanup(); -} - -uint64_t discord_client::get_decompressed_bytes_in() +void discord_client::cleanup() { - return decompressed_total; } -void discord_client::setup_zlib() +void discord_client::on_disconnect() { - if (compressed) { - zlib->d_stream.zalloc = (alloc_func)0; - zlib->d_stream.zfree = (free_func)0; - zlib->d_stream.opaque = (voidpf)0; - int error = inflateInit(&(zlib->d_stream)); - if (error != Z_OK) { - throw dpp::connection_exception((exception_error_code)error, "Can't initialise stream compression!"); - } - this->decomp_buffer = new unsigned char[DECOMP_BUFFER_SIZE]; + log(ll_trace, "discord_client::on_disconnect()"); + set_resume_hostname(); + if (sfd != INVALID_SOCKET) { + log(dpp::ll_debug, "Lost connection to websocket on shard " + std::to_string(shard_id) + ", reconnecting..."); } - + ssl_client::close(); + owner->add_reconnect(this->shard_id); } -void discord_client::end_zlib() +uint64_t discord_client::get_decompressed_bytes_in() { - if (compressed) { - inflateEnd(&(zlib->d_stream)); - delete[] this->decomp_buffer; - this->decomp_buffer = nullptr; - } + return zlib ? zlib->decompressed_total : 0; } void discord_client::set_resume_hostname() @@ -159,100 +136,28 @@ void discord_client::set_resume_hostname() hostname = resume_gateway_url; } -void discord_client::thread_run() -{ - utility::set_thread_name(std::string("shard/") + std::to_string(shard_id)); - setup_zlib(); - do { - bool error = false; - ready = false; - message_queue.clear(); - ssl_client::read_loop(); - if (!terminating) { - ssl_client::close(); - end_zlib(); - setup_zlib(); - do { - this->log(ll_debug, "Attempting reconnection of shard " + std::to_string(this->shard_id) + " to wss://" + resume_gateway_url); - error = false; - try { - set_resume_hostname(); - ssl_client::connect(); - websocket_client::connect(); - } - catch (const std::exception &e) { - log(dpp::ll_error, std::string("Error establishing connection, retry in 5 seconds: ") + e.what()); - ssl_client::close(); - std::this_thread::sleep_for(std::chrono::seconds(5)); - error = true; - } - } while (error); - } - } while(!terminating); - if (this->sfd != INVALID_SOCKET) { - /* Send a graceful termination */ - this->log(ll_debug, "Graceful shutdown of shard " + std::to_string(this->shard_id) + " succeeded."); - this->nonblocking = false; - this->send_close_packet(); - ssl_client::close(); - } else { - this->log(ll_debug, "Graceful shutdown of shard " + std::to_string(this->shard_id) + " not possible, socket already closed."); - } - end_zlib(); -} - void discord_client::run() { - this->runner = new std::thread(&discord_client::thread_run, this); - this->thread_id = runner->native_handle(); + ready = false; + message_queue.clear(); + ssl_client::read_loop(); } bool discord_client::handle_frame(const std::string &buffer, ws_opcode opcode) { - std::string& data = (std::string&)buffer; + auto& data = const_cast(buffer); /* gzip compression is a special case */ if (compressed) { /* Check that we have a complete compressed frame */ if ((uint8_t)buffer[buffer.size() - 4] == 0x00 && (uint8_t)buffer[buffer.size() - 3] == 0x00 && (uint8_t)buffer[buffer.size() - 2] == 0xFF && (uint8_t)buffer[buffer.size() - 1] == 0xFF) { - /* Decompress buffer */ - decompressed.clear(); - zlib->d_stream.next_in = (Bytef *)buffer.c_str(); - zlib->d_stream.avail_in = (uInt)buffer.size(); - do { - int have = 0; - zlib->d_stream.next_out = (Bytef*)decomp_buffer; - zlib->d_stream.avail_out = DECOMP_BUFFER_SIZE; - int ret = inflate(&(zlib->d_stream), Z_NO_FLUSH); - have = DECOMP_BUFFER_SIZE - zlib->d_stream.avail_out; - switch (ret) - { - case Z_NEED_DICT: - case Z_STREAM_ERROR: - this->error(err_compression_stream); - this->close(); - return true; - break; - case Z_DATA_ERROR: - this->error(err_compression_data); - this->close(); - return true; - break; - case Z_MEM_ERROR: - this->error(err_compression_memory); - this->close(); - return true; - break; - case Z_OK: - this->decompressed.append((const char*)decomp_buffer, have); - this->decompressed_total += have; - break; - default: - /* Stub */ - break; - } - } while (zlib->d_stream.avail_out == 0); + auto result = zlib->decompress(buffer, decompressed); + if (result != err_no_code_specified) { + this->error(result); + this->close(); + return false; + } data = decompressed; } else { /* No complete compressed frame yet */ @@ -291,6 +196,8 @@ bool discord_client::handle_frame(const std::string &buffer, ws_opcode opcode) break; } + //log(dpp::ll_trace, "R: " + j.dump()); + auto seq = j.find("s"); if (seq != j.end() && !seq->is_null()) { last_seq = seq->get(); @@ -298,86 +205,101 @@ bool discord_client::handle_frame(const std::string &buffer, ws_opcode opcode) auto o = j.find("op"); if (o != j.end() && !o->is_null()) { - uint32_t op = o->get(); + shard_frame_type op = o->get(); switch (op) { - case 9: - /* Reset session state and fall through to 10 */ - op = 10; + case ft_invalid_session: + /* Reset session state and fall through to ft_hello */ + op = ft_hello; log(dpp::ll_debug, "Failed to resume session " + sessionid + ", will reidentify"); this->sessionid.clear(); this->last_seq = 0; - /* No break here, falls through to state 10 to cause a reidentify */ + /* No break here, falls through to state ft_hello to cause a re-identify */ [[fallthrough]]; - case 10: + case ft_hello: { /* Need to check carefully for the existence of this before we try to access it! */ - if (j.find("d") != j.end() && j["d"].find("heartbeat_interval") != j["d"].end() && !j["d"]["heartbeat_interval"].is_null()) { - this->heartbeat_interval = j["d"]["heartbeat_interval"].get(); + auto d = j.find("d"); + if (d != j.end()) { + auto heartbeat = d->find("heartbeat_interval"); + if (heartbeat != d->end() && !heartbeat->is_null()) + this->heartbeat_interval = heartbeat->get(); } - if (last_seq && !sessionid.empty()) { + if (last_seq != 0U && !sessionid.empty()) { /* Resume */ log(dpp::ll_debug, "Resuming session " + sessionid + " with seq=" + std::to_string(last_seq)); json obj = { - { "op", 6 }, - { "d", { - {"token", this->token }, - {"session_id", this->sessionid }, - {"seq", this->last_seq } - } + {"op", ft_resume}, + {"d", { + {"token", this->token}, + {"session_id", this->sessionid}, + {"seq", this->last_seq} + } } }; this->write(jsonobj_to_string(obj), protocol == ws_etf ? OP_BINARY : OP_TEXT); resumes++; } else { /* Full connect */ - while (time(nullptr) < creator->last_identify + 5) { - time_t wait = (creator->last_identify + 5) - time(nullptr); - std::this_thread::sleep_for(std::chrono::seconds(wait)); - } - log(dpp::ll_debug, "Connecting new session..."); - json obj = { - { "op", 2 }, - { - "d", - { - { "token", this->token }, - { "properties", - { - { "os", STRINGIFY(DPP_OS) }, - { "browser", "D++" }, - { "device", "D++" } - } - }, - { "shard", json::array({ shard_id, max_shards }) }, - { "compress", false }, - { "large_threshold", 250 }, - { "intents", this->intents } + auto connect_now = [this]() { + log(dpp::ll_debug, "Connecting new session..."); + json obj = { + {"op", ft_identify}, + {"d", + { + {"token", this->token}, + {"properties", + { + {"os", STRINGIFY(DPP_OS)}, + {"browser", "D++"}, + {"device", "D++"} + } + }, + {"shard", json::array({shard_id, max_shards})}, + {"compress", false}, + {"large_threshold", LARGE_THRESHOLD}, + {"intents", this->intents} + } } - } + }; + this->write(jsonobj_to_string(obj), protocol == ws_etf ? OP_BINARY : OP_TEXT); + this->connect_time = creator->last_identify = time(nullptr); + reconnects++; }; - this->write(jsonobj_to_string(obj), protocol == ws_etf ? OP_BINARY : OP_TEXT); - this->connect_time = creator->last_identify = time(nullptr); - reconnects++; + if (time(nullptr) < creator->last_identify + RECONNECT_INTERVAL) { + owner->start_timer([this, connect_now](timer h) { + owner->stop_timer(h); + connect_now(); + }, (creator->last_identify + RECONNECT_INTERVAL) - time(nullptr)); + } else { + connect_now(); + } } this->last_heartbeat_ack = time(nullptr); websocket_ping = 0; + } break; - case 0: { + case ft_dispatch: { std::string event = j["t"]; handle_event(event, j, data); } break; - case 7: - log(dpp::ll_debug, "Reconnection requested, closing socket " + sessionid); + case ft_reconnect: message_queue.clear(); - throw dpp::connection_exception(err_reconnection, "Remote site requested reconnection"); - break; + throw dpp::connection_exception("Reconnection requested, closing session " + sessionid); /* Heartbeat ack */ - case 11: + case ft_heartbeat_ack: this->last_heartbeat_ack = time(nullptr); websocket_ping = utility::time_f() - ping_start; break; + case ft_heartbeat: + case ft_identify: + case ft_presence: + case ft_voice_state_update: + case ft_resume: + case ft_request_guild_members: + case ft_request_soundboard_sounds: + throw dpp::connection_exception("Received invalid opcode on websocket for session " + sessionid); } } return true; @@ -385,7 +307,7 @@ bool discord_client::handle_frame(const std::string &buffer, ws_opcode opcode) dpp::utility::uptime discord_client::get_uptime() { - return dpp::utility::uptime(time(nullptr) - connect_time); + return {time(nullptr) - connect_time}; } bool discord_client::is_connected() @@ -437,13 +359,14 @@ void discord_client::error(uint32_t errorcode) error = i->second; } log(dpp::ll_warning, "OOF! Error from underlying websocket: " + std::to_string(errorcode) + ": " + error); + this->close(); } void discord_client::log(dpp::loglevel severity, const std::string &msg) const { if (!creator->on_log.empty()) { /* Pass to user if they've hooked the event */ - dpp::log_t logmsg(nullptr, msg); + dpp::log_t logmsg(creator, shard_id, msg); logmsg.severity = severity; logmsg.message = msg; size_t pos{0}; @@ -480,29 +403,8 @@ size_t discord_client::get_queue_size() void discord_client::one_second_timer() { - if (terminating) { - throw dpp::exception("Shard terminating due to cluster shutdown"); - } - websocket_client::one_second_timer(); - /* Every minute, rehash all containers from first shard. - * We can't just get shard with the id 0 because this won't - * work on a clustered environment - */ - auto shards = creator->get_shards(); - auto first_iter = shards.begin(); - if (first_iter != shards.end()) { - dpp::discord_client* first_shard = first_iter->second; - if (first_shard == this) { - creator->tick_timers(); - - if ((time(nullptr) % 60) == 0) { - dpp::garbage_collection(); - } - } - } - /* This all only triggers if we are connected (have completed websocket, and received READY or RESUMED) */ if (this->is_connected()) { @@ -523,9 +425,8 @@ void discord_client::one_second_timer() if (message_queue.size()) { std::string message = message_queue.front(); message_queue.pop_front(); - /* Checking here with .find() saves us having to deserialise the json - * to find pings in our queue. The assumption is that the format of the - * ping isn't going to change. + /* Checking here by string comparison saves us having to deserialise the json + * to find pings in our queue. */ if (!last_ping_message.empty() && message == last_ping_message) { ping_start = utility::time_f(); @@ -541,7 +442,7 @@ void discord_client::one_second_timer() if (this->heartbeat_interval && this->last_seq) { /* Check if we're due to emit a heartbeat */ if (time(nullptr) > last_heartbeat + ((heartbeat_interval / 1000.0) * 0.75)) { - last_ping_message = jsonobj_to_string(json({{"op", 1}, {"d", last_seq}})); + last_ping_message = jsonobj_to_string(json({{"op", ft_heartbeat}, {"d", last_seq}})); queue_message(last_ping_message, true); last_heartbeat = time(nullptr); } @@ -615,7 +516,7 @@ discord_client& discord_client::connect_voice(snowflake guild_id, snowflake chan */ log(ll_debug, "Sending op 4 to join VC, guild " + std::to_string(guild_id) + " channel " + std::to_string(channel_id) + (enable_dave ? " WITH DAVE" : "")); queue_message(jsonobj_to_string(json({ - { "op", 4 }, + { "op", ft_voice_state_update }, { "d", { { "guild_id", std::to_string(guild_id) }, { "channel_id", std::to_string(channel_id) }, @@ -644,7 +545,7 @@ void discord_client::disconnect_voice_internal(snowflake guild_id, bool emit_jso log(ll_debug, "Disconnecting voice, guild: " + std::to_string(guild_id)); if (emit_json) { queue_message(jsonobj_to_string(json({ - { "op", 4 }, + { "op", ft_voice_state_update }, { "d", { { "guild_id", std::to_string(guild_id) }, { "channel_id", json::value_t::null }, @@ -679,11 +580,11 @@ voiceconn* discord_client::get_voice(snowflake guild_id) { voiceconn::voiceconn(discord_client* o, snowflake _channel_id, bool enable_dave) : creator(o), channel_id(_channel_id), voiceclient(nullptr), dave(enable_dave) { } -bool voiceconn::is_ready() { +bool voiceconn::is_ready() const { return (!websocket_hostname.empty() && !session_id.empty() && !token.empty()); } -bool voiceconn::is_active() { +bool voiceconn::is_active() const { return voiceclient != nullptr; } diff --git a/src/dpp/discordvoiceclient.cpp b/src/dpp/discordvoiceclient.cpp index 5b241d1122..31801c62f7 100644 --- a/src/dpp/discordvoiceclient.cpp +++ b/src/dpp/discordvoiceclient.cpp @@ -231,6 +231,8 @@ void discord_voice_client::error(uint32_t errorcode) this->terminating = true; log(dpp::ll_error, "This is a non-recoverable error, giving up on voice connection"); } + + this->close(); } void discord_voice_client::set_user_gain(snowflake user_id, float factor) diff --git a/src/dpp/dispatcher.cpp b/src/dpp/dispatcher.cpp index db8ea2e888..314a1f1efe 100644 --- a/src/dpp/dispatcher.cpp +++ b/src/dpp/dispatcher.cpp @@ -31,9 +31,13 @@ namespace dpp { -event_dispatch_t::event_dispatch_t(discord_client* client, const std::string& raw) : raw_event(raw), from(client) {} +event_dispatch_t::event_dispatch_t(dpp::cluster* creator, uint32_t shard_id, const std::string& raw) : raw_event(raw), shard(shard_id), owner(creator) {} -event_dispatch_t::event_dispatch_t(discord_client* client, std::string&& raw) : raw_event(std::move(raw)), from(client) {} +event_dispatch_t::event_dispatch_t(dpp::cluster* creator, uint32_t shard_id, std::string&& raw) : raw_event(std::move(raw)), shard(shard_id), owner(creator) {} + +discord_client* event_dispatch_t::from() const { + return owner->get_shard(shard); +} const event_dispatch_t& event_dispatch_t::cancel_event() const { cancelled = true; @@ -73,12 +77,12 @@ void message_create_t::send(const std::string& m, command_completion_event_t cal } void message_create_t::send(const message& msg, command_completion_event_t callback) const { - this->from->creator->message_create(std::move(message{msg}.set_channel_id(this->msg.channel_id)), std::move(callback)); + owner->message_create(std::move(message{msg}.set_channel_id(this->msg.channel_id)), std::move(callback)); } void message_create_t::send(message&& msg, command_completion_event_t callback) const { msg.channel_id = this->msg.channel_id; - this->from->creator->message_create(std::move(msg), std::move(callback)); + owner->message_create(std::move(msg), std::move(callback)); } void message_create_t::reply(const std::string& m, bool mention_replied_user, command_completion_event_t callback) const { @@ -94,7 +98,7 @@ void message_create_t::reply(const message& msg, bool mention_replied_user, comm msg_to_send.allowed_mentions.replied_user = mention_replied_user; msg_to_send.allowed_mentions.users.push_back(this->msg.author.id); } - this->from->creator->message_create(std::move(msg_to_send), std::move(callback)); + owner->message_create(std::move(msg_to_send), std::move(callback)); } void message_create_t::reply(message&& msg, bool mention_replied_user, command_completion_event_t callback) const { @@ -104,15 +108,15 @@ void message_create_t::reply(message&& msg, bool mention_replied_user, command_c msg.allowed_mentions.replied_user = mention_replied_user; msg.allowed_mentions.users.push_back(this->msg.author.id); } - this->from->creator->message_create(std::move(msg), std::move(callback)); + owner->message_create(std::move(msg), std::move(callback)); } void interaction_create_t::reply(interaction_response_type t, const message& m, command_completion_event_t callback) const { - from->creator->interaction_response_create(this->command.id, this->command.token, dpp::interaction_response(t, m), std::move(callback)); + owner->interaction_response_create(this->command.id, this->command.token, dpp::interaction_response(t, m), std::move(callback)); } void interaction_create_t::reply(const message& m, command_completion_event_t callback) const { - from->creator->interaction_response_create( + owner->interaction_response_create( this->command.id, this->command.token, dpp::interaction_response(ir_channel_message_with_source, m), @@ -134,7 +138,7 @@ void interaction_create_t::reply(command_completion_event_t callback) const { } void interaction_create_t::dialog(const interaction_modal_response& mr, command_completion_event_t callback) const { - from->creator->interaction_response_create(this->command.id, this->command.token, mr, std::move(callback)); + owner->interaction_response_create(this->command.id, this->command.token, mr, std::move(callback)); } void interaction_create_t::reply(interaction_response_type t, const std::string& mt, command_completion_event_t callback) const { @@ -146,7 +150,7 @@ void interaction_create_t::reply(const std::string& mt, command_completion_event } void interaction_create_t::edit_response(const message& m, command_completion_event_t callback) const { - from->creator->interaction_response_edit(this->command.token, m, std::move(callback)); + owner->interaction_response_edit(this->command.token, m, std::move(callback)); } void interaction_create_t::edit_response(const std::string& mt, command_completion_event_t callback) const { @@ -154,9 +158,9 @@ void interaction_create_t::edit_response(const std::string& mt, command_completi } void interaction_create_t::get_original_response(command_completion_event_t callback) const { - from->creator->post_rest(API_PATH "/webhooks", std::to_string(command.application_id), command.token + "/messages/@original", m_get, "", [creator = this->from->creator, cb = std::move(callback)](json& j, const http_request_completion_t& http) { + owner->post_rest(API_PATH "/webhooks", std::to_string(command.application_id), command.token + "/messages/@original", m_get, "", [owner = this->owner, cb = std::move(callback)](json& j, const http_request_completion_t& http) { if (cb) { - cb(confirmation_callback_t(creator, message().fill_from_json(&j), http)); + cb(confirmation_callback_t(owner, message().fill_from_json(&j), http)); } }); } @@ -172,17 +176,17 @@ void interaction_create_t::edit_original_response(const message& m, command_comp file_mimetypes.push_back(data.mimetype); } - from->creator->post_rest_multipart(API_PATH "/webhooks", std::to_string(command.application_id), command.token + "/messages/@original", m_patch, m.build_json(), [creator = this->from->creator, cb = std::move(callback)](json& j, const http_request_completion_t& http) { + owner->post_rest_multipart(API_PATH "/webhooks", std::to_string(command.application_id), command.token + "/messages/@original", m_patch, m.build_json(), [owner = this->owner, cb = std::move(callback)](json& j, const http_request_completion_t& http) { if (cb) { - cb(confirmation_callback_t(creator, message().fill_from_json(&j), http)); + cb(confirmation_callback_t(owner, message().fill_from_json(&j), http)); } }, m.file_data); } void interaction_create_t::delete_original_response(command_completion_event_t callback) const { - from->creator->post_rest(API_PATH "/webhooks", std::to_string(command.application_id), command.token + "/messages/@original", m_delete, "", [creator = this->from->creator, cb = std::move(callback)](const json &, const http_request_completion_t& http) { + owner->post_rest(API_PATH "/webhooks", std::to_string(command.application_id), command.token + "/messages/@original", m_delete, "", [owner = this->owner, cb = std::move(callback)](const json &, const http_request_completion_t& http) { if (cb) { - cb(confirmation_callback_t(creator, confirmation(), http)); + cb(confirmation_callback_t(owner, confirmation(), http)); } }); } @@ -267,11 +271,11 @@ command_value interaction_create_t::get_parameter(const std::string& name) const return {}; } -voice_receive_t::voice_receive_t(discord_client* client, const std::string& raw, discord_voice_client* vc, snowflake _user_id, const uint8_t* pcm, size_t length) : event_dispatch_t(client, raw), voice_client(vc), user_id(_user_id) { +voice_receive_t::voice_receive_t(dpp::cluster* creator, uint32_t shard_id, const std::string& raw, discord_voice_client* vc, snowflake _user_id, const uint8_t* pcm, size_t length) : event_dispatch_t(owner, shard_id, std::move(raw)), voice_client(vc), user_id(_user_id) { reassign(vc, _user_id, pcm, length); } -voice_receive_t::voice_receive_t(discord_client* client, std::string&& raw, discord_voice_client* vc, snowflake _user_id, const uint8_t* pcm, size_t length) : event_dispatch_t(client, std::move(raw)), voice_client(vc), user_id(_user_id) { +voice_receive_t::voice_receive_t(dpp::cluster* creator, uint32_t shard_id, std::string&& raw, discord_voice_client* vc, snowflake _user_id, const uint8_t* pcm, size_t length) : event_dispatch_t(owner, shard_id, std::move(raw)), voice_client(vc), user_id(_user_id) { reassign(vc, _user_id, pcm, length); } diff --git a/src/dpp/dns.cpp b/src/dpp/dns.cpp index 42aed3ce8f..df1d733624 100644 --- a/src/dpp/dns.cpp +++ b/src/dpp/dns.cpp @@ -54,8 +54,7 @@ socket dns_cache_entry::make_connecting_socket() const { return ::socket(addr.ai_family, addr.ai_socktype, addr.ai_protocol); } -const dns_cache_entry* resolve_hostname(const std::string& hostname, const std::string& port) -{ +const dns_cache_entry *resolve_hostname(const std::string &hostname, const std::string &port) { addrinfo hints, *addrs; dns_cache_t::const_iterator iter; time_t now = time(nullptr); @@ -71,7 +70,7 @@ const dns_cache_entry* resolve_hostname(const std::string& hostname, const std:: exists = true; if (now < iter->second->expire_timestamp) { /* there is a cached entry that is still valid, return it */ - return iter->second; + return iter->second.get(); } } } @@ -83,7 +82,6 @@ const dns_cache_entry* resolve_hostname(const std::string& hostname, const std:: std::unique_lock dns_cache_lock(dns_cache_mutex); iter = dns_cache.find(hostname); if (iter != dns_cache.end()) { /* re-validate iter */ - delete iter->second; dns_cache.erase(iter); } } @@ -109,7 +107,7 @@ const dns_cache_entry* resolve_hostname(const std::string& hostname, const std:: { /* Update cache, requires unique lock */ std::unique_lock dns_cache_lock(dns_cache_mutex); - dns_cache_entry* cache_entry = new dns_cache_entry(); + auto cache_entry = std::make_unique(); for (struct addrinfo* rp = addrs; rp != nullptr; rp = rp->ai_next) { /* Discord only support ipv4, so iterate over any ipv6 results */ @@ -128,11 +126,13 @@ const dns_cache_entry* resolve_hostname(const std::string& hostname, const std:: } cache_entry->expire_timestamp = now + one_hour; - dns_cache[hostname] = cache_entry; + auto r = dns_cache.emplace(hostname, std::move(cache_entry)); /* Now we're done with this horrible struct, free it and return */ freeaddrinfo(addrs); - return cache_entry; + + /* Return either the existing entry, or the newly inserted entry */ + return r.first->second.get(); } } diff --git a/src/dpp/events/automod_rule_create.cpp b/src/dpp/events/automod_rule_create.cpp index b31d5b9f5d..0a1bdab273 100644 --- a/src/dpp/events/automod_rule_create.cpp +++ b/src/dpp/events/automod_rule_create.cpp @@ -39,9 +39,11 @@ namespace dpp::events { void automod_rule_create::handle(discord_client* client, json &j, const std::string &raw) { if (!client->creator->on_automod_rule_create.empty()) { json& d = j["d"]; - automod_rule_create_t arc(client, raw); + automod_rule_create_t arc(client->owner, client->shard_id, raw); arc.created = automod_rule().fill_from_json(&d); - client->creator->on_automod_rule_create.call(arc); + client->creator->queue_work(0, [c = client->creator, arc]() { + c->on_automod_rule_create.call(arc); + }); } } diff --git a/src/dpp/events/automod_rule_delete.cpp b/src/dpp/events/automod_rule_delete.cpp index d8a0702390..59ffc57655 100644 --- a/src/dpp/events/automod_rule_delete.cpp +++ b/src/dpp/events/automod_rule_delete.cpp @@ -37,9 +37,11 @@ namespace dpp::events { void automod_rule_delete::handle(discord_client* client, json &j, const std::string &raw) { if (!client->creator->on_automod_rule_create.empty()) { json& d = j["d"]; - automod_rule_delete_t ard(client, raw); + automod_rule_delete_t ard(client->owner, client->shard_id, raw); ard.deleted = automod_rule().fill_from_json(&d); - client->creator->on_automod_rule_delete.call(ard); + client->creator->queue_work(0, [c = client->creator, ard]() { + c->on_automod_rule_delete.call(ard); + }); } } diff --git a/src/dpp/events/automod_rule_execute.cpp b/src/dpp/events/automod_rule_execute.cpp index 4b4d6e4a16..c2699d228b 100644 --- a/src/dpp/events/automod_rule_execute.cpp +++ b/src/dpp/events/automod_rule_execute.cpp @@ -37,7 +37,7 @@ namespace dpp::events { void automod_rule_execute::handle(discord_client* client, json &j, const std::string &raw) { if (!client->creator->on_automod_rule_execute.empty()) { json& d = j["d"]; - automod_rule_execute_t are(client, raw); + automod_rule_execute_t are(client->owner, client->shard_id, raw); are.guild_id = snowflake_not_null(&d, "guild_id"); are.action = dpp::automod_action().fill_from_json(&(d["action"])); are.rule_id = snowflake_not_null(&d, "rule_id"); @@ -49,7 +49,9 @@ void automod_rule_execute::handle(discord_client* client, json &j, const std::st are.content = string_not_null(&d, "content"); are.matched_keyword = string_not_null(&d, "matched_keyword"); are.matched_content = string_not_null(&d, "matched_content"); - client->creator->on_automod_rule_execute.call(are); + client->creator->queue_work(0, [c = client->creator, are]() { + c->on_automod_rule_execute.call(are); + }); } } diff --git a/src/dpp/events/automod_rule_update.cpp b/src/dpp/events/automod_rule_update.cpp index 1e78541473..c14a968bf7 100644 --- a/src/dpp/events/automod_rule_update.cpp +++ b/src/dpp/events/automod_rule_update.cpp @@ -37,9 +37,11 @@ namespace dpp::events { void automod_rule_update::handle(discord_client* client, json &j, const std::string &raw) { if (!client->creator->on_automod_rule_update.empty()) { json& d = j["d"]; - automod_rule_update_t aru(client, raw); + automod_rule_update_t aru(client->owner, client->shard_id, raw); aru.updated = automod_rule().fill_from_json(&d); - client->creator->on_automod_rule_update.call(aru); + client->creator->queue_work(0, [c = client->creator, aru]() { + c->on_automod_rule_update.call(aru); + }); } } diff --git a/src/dpp/events/channel_create.cpp b/src/dpp/events/channel_create.cpp index b252432c71..ae6bc3d1b0 100644 --- a/src/dpp/events/channel_create.cpp +++ b/src/dpp/events/channel_create.cpp @@ -67,10 +67,12 @@ void channel_create::handle(discord_client* client, json &j, const std::string & } } if (!client->creator->on_channel_create.empty()) { - dpp::channel_create_t cc(client, raw); + dpp::channel_create_t cc(client->owner, client->shard_id, raw); cc.created = c; cc.creating_guild = g; - client->creator->on_channel_create.call(cc); + client->creator->queue_work(1, [c = client->creator, cc]() { + c->on_channel_create.call(cc); + }); } } diff --git a/src/dpp/events/channel_delete.cpp b/src/dpp/events/channel_delete.cpp index 3009ff3f4a..84898b2eb0 100644 --- a/src/dpp/events/channel_delete.cpp +++ b/src/dpp/events/channel_delete.cpp @@ -48,10 +48,12 @@ void channel_delete::handle(discord_client* client, json &j, const std::string & get_channel_cache()->remove(find_channel(c.id)); } if (!client->creator->on_channel_delete.empty()) { - channel_delete_t cd(client, raw); + channel_delete_t cd(client->owner, client->shard_id, raw); cd.deleted = c; cd.deleting_guild = g; - client->creator->on_channel_delete.call(cd); + client->creator->queue_work(1, [c = client->creator, cd]() { + c->on_channel_delete.call(cd); + }); } } diff --git a/src/dpp/events/channel_pins_update.cpp b/src/dpp/events/channel_pins_update.cpp index 29ec7c2595..e3fe01cb57 100644 --- a/src/dpp/events/channel_pins_update.cpp +++ b/src/dpp/events/channel_pins_update.cpp @@ -39,12 +39,13 @@ void channel_pins_update::handle(discord_client* client, json &j, const std::str if (!client->creator->on_channel_pins_update.empty()) { json& d = j["d"]; - dpp::channel_pins_update_t cpu(client, raw); + dpp::channel_pins_update_t cpu(client->owner, client->shard_id, raw); cpu.pin_channel = dpp::find_channel(snowflake_not_null(&d, "channel_id")); cpu.pin_guild = dpp::find_guild(snowflake_not_null(&d, "guild_id")); cpu.timestamp = ts_not_null(&d, "last_pin_timestamp"); - - client->creator->on_channel_pins_update.call(cpu); + client->creator->queue_work(0, [c = client->creator, cpu]() { + c->on_channel_pins_update.call(cpu); + }); } } diff --git a/src/dpp/events/channel_update.cpp b/src/dpp/events/channel_update.cpp index 33ecd580c0..5d7a9d8276 100644 --- a/src/dpp/events/channel_update.cpp +++ b/src/dpp/events/channel_update.cpp @@ -50,10 +50,12 @@ void channel_update::handle(discord_client* client, json &j, const std::string & } } if (!client->creator->on_channel_update.empty()) { - dpp::channel_update_t cu(client, raw); + dpp::channel_update_t cu(client->owner, client->shard_id, raw); cu.updated = c; cu.updating_guild = dpp::find_guild(c->guild_id); - client->creator->on_channel_update.call(cu); + client->creator->queue_work(1, [c = client->creator, cu]() { + c->on_channel_update.call(cu); + }); } } diff --git a/src/dpp/events/entitlement_create.cpp b/src/dpp/events/entitlement_create.cpp index d2ed6b71cc..59f0687877 100644 --- a/src/dpp/events/entitlement_create.cpp +++ b/src/dpp/events/entitlement_create.cpp @@ -38,10 +38,12 @@ void entitlement_create::handle(discord_client* client, json &j, const std::stri json& d = j["d"]; ent.fill_from_json(&d); - dpp::entitlement_create_t entitlement_event(client, raw); + dpp::entitlement_create_t entitlement_event(client->owner, client->shard_id, raw); entitlement_event.created = ent; - client->creator->on_entitlement_create.call(entitlement_event); + client->creator->queue_work(0, [c = client->creator, entitlement_event]() { + c->on_entitlement_create.call(entitlement_event); + }); } } diff --git a/src/dpp/events/entitlement_delete.cpp b/src/dpp/events/entitlement_delete.cpp index 79f6f61f34..2315de963e 100644 --- a/src/dpp/events/entitlement_delete.cpp +++ b/src/dpp/events/entitlement_delete.cpp @@ -38,10 +38,12 @@ void entitlement_delete::handle(discord_client* client, json &j, const std::stri json& d = j["d"]; ent.fill_from_json(&d); - dpp::entitlement_delete_t entitlement_event(client, raw); + dpp::entitlement_delete_t entitlement_event(client->owner, client->shard_id, raw); entitlement_event.deleted = ent; - client->creator->on_entitlement_delete.call(entitlement_event); + client->creator->queue_work(0, [c = client->creator, entitlement_event]() { + c->on_entitlement_delete.call(entitlement_event); + }); } } diff --git a/src/dpp/events/entitlement_update.cpp b/src/dpp/events/entitlement_update.cpp index 648a9310d1..34979ade78 100644 --- a/src/dpp/events/entitlement_update.cpp +++ b/src/dpp/events/entitlement_update.cpp @@ -38,10 +38,12 @@ void entitlement_update::handle(discord_client* client, json &j, const std::stri json& d = j["d"]; ent.fill_from_json(&d); - dpp::entitlement_update_t entitlement_event(client, raw); + dpp::entitlement_update_t entitlement_event(client->owner, client->shard_id, raw); entitlement_event.updating_entitlement = ent; - client->creator->on_entitlement_update.call(entitlement_event); + client->creator->queue_work(0, [c = client->creator, entitlement_event]() { + c->on_entitlement_update.call(entitlement_event); + }); } } diff --git a/src/dpp/events/guild_audit_log_entry_create.cpp b/src/dpp/events/guild_audit_log_entry_create.cpp index 131f5e4b9e..de689d7a79 100644 --- a/src/dpp/events/guild_audit_log_entry_create.cpp +++ b/src/dpp/events/guild_audit_log_entry_create.cpp @@ -35,9 +35,11 @@ namespace dpp::events { void guild_audit_log_entry_create::handle(discord_client* client, json &j, const std::string &raw) { json& d = j["d"]; if (!client->creator->on_guild_audit_log_entry_create.empty()) { - dpp::guild_audit_log_entry_create_t ec(client, raw); + dpp::guild_audit_log_entry_create_t ec(client->owner, client->shard_id, raw); ec.entry.fill_from_json(&d); - client->creator->on_guild_audit_log_entry_create.call(ec); + client->creator->queue_work(2, [c = client->creator, ec]() { + c->on_guild_audit_log_entry_create.call(ec); + }); } } diff --git a/src/dpp/events/guild_ban_add.cpp b/src/dpp/events/guild_ban_add.cpp index 646656c996..cbec907aca 100644 --- a/src/dpp/events/guild_ban_add.cpp +++ b/src/dpp/events/guild_ban_add.cpp @@ -41,10 +41,12 @@ namespace dpp::events { void guild_ban_add::handle(discord_client* client, json &j, const std::string &raw) { if (!client->creator->on_guild_ban_add.empty()) { json &d = j["d"]; - dpp::guild_ban_add_t gba(client, raw); + dpp::guild_ban_add_t gba(client->owner, client->shard_id, raw); gba.banning_guild = dpp::find_guild(snowflake_not_null(&d, "guild_id")); gba.banned = dpp::user().fill_from_json(&(d["user"])); - client->creator->on_guild_ban_add.call(gba); + client->creator->queue_work(1, [c = client->creator, gba]() { + c->on_guild_ban_add.call(gba); + }); } } diff --git a/src/dpp/events/guild_ban_remove.cpp b/src/dpp/events/guild_ban_remove.cpp index 9d0d54d05b..f8b5b451c2 100644 --- a/src/dpp/events/guild_ban_remove.cpp +++ b/src/dpp/events/guild_ban_remove.cpp @@ -41,10 +41,12 @@ namespace dpp::events { void guild_ban_remove::handle(discord_client* client, json &j, const std::string &raw) { if (!client->creator->on_guild_ban_remove.empty()) { json &d = j["d"]; - dpp::guild_ban_remove_t gbr(client, raw); + dpp::guild_ban_remove_t gbr(client->owner, client->shard_id, raw); gbr.unbanning_guild = dpp::find_guild(snowflake_not_null(&d, "guild_id")); gbr.unbanned = dpp::user().fill_from_json(&(d["user"])); - client->creator->on_guild_ban_remove.call(gbr); + client->creator->queue_work(1, [c = client->creator, gbr]() { + c->on_guild_ban_remove.call(gbr); + }); } } diff --git a/src/dpp/events/guild_create.cpp b/src/dpp/events/guild_create.cpp index 8e4ac63b18..e80746a899 100644 --- a/src/dpp/events/guild_create.cpp +++ b/src/dpp/events/guild_create.cpp @@ -21,6 +21,7 @@ ************************************************************************************/ #include #include +#include #include #include #include @@ -136,7 +137,7 @@ void guild_create::handle(discord_client* client, json &j, const std::string &ra dpp::get_guild_cache()->store(g); if (is_new_guild && g->id && (client->intents & dpp::i_guild_members)) { if (client->creator->cache_policy.user_policy == cp_aggressive) { - json chunk_req = json({{"op", 8}, {"d", {{"guild_id",std::to_string(g->id)},{"query",""},{"limit",0}}}}); + json chunk_req = json({{"op", ft_request_guild_members}, {"d", {{"guild_id",std::to_string(g->id)},{"query",""},{"limit",0}}}}); if (client->intents & dpp::i_guild_presences) { chunk_req["d"]["presences"] = true; } @@ -146,7 +147,7 @@ void guild_create::handle(discord_client* client, json &j, const std::string &ra } if (!client->creator->on_guild_create.empty()) { - dpp::guild_create_t gc(client, raw); + dpp::guild_create_t gc(client->owner, client->shard_id, raw); gc.created = g; /* Fill presences if there are any */ @@ -203,7 +204,9 @@ void guild_create::handle(discord_client* client, json &j, const std::string &ra } } - client->creator->on_guild_create.call(gc); + client->creator->queue_work(0, [c = client->creator, gc]() { + c->on_guild_create.call(gc); + }); } } diff --git a/src/dpp/events/guild_delete.cpp b/src/dpp/events/guild_delete.cpp index d8e01119ed..1711a0cb2c 100644 --- a/src/dpp/events/guild_delete.cpp +++ b/src/dpp/events/guild_delete.cpp @@ -87,10 +87,12 @@ void guild_delete::handle(discord_client* client, json &j, const std::string &ra } if (!client->creator->on_guild_delete.empty()) { - dpp::guild_delete_t gd(client, raw); + dpp::guild_delete_t gd(client->owner, client->shard_id, raw); gd.deleted = guild_del; gd.guild_id = guild_del.id; - client->creator->on_guild_delete.call(gd); + client->creator->queue_work(0, [c = client->creator, gd]() { + c->on_guild_delete.call(gd); + }); } } diff --git a/src/dpp/events/guild_emojis_update.cpp b/src/dpp/events/guild_emojis_update.cpp index 9d4a6676bc..5ba2895bdc 100644 --- a/src/dpp/events/guild_emojis_update.cpp +++ b/src/dpp/events/guild_emojis_update.cpp @@ -71,10 +71,12 @@ void guild_emojis_update::handle(discord_client* client, json &j, const std::str } } if (!client->creator->on_guild_emojis_update.empty()) { - dpp::guild_emojis_update_t geu(client, raw); + dpp::guild_emojis_update_t geu(client->owner, client->shard_id, raw); geu.emojis = emojis; geu.updating_guild = g; - client->creator->on_guild_emojis_update.call(geu); + client->creator->queue_work(1, [c = client->creator, geu]() { + c->on_guild_emojis_update.call(geu); + }); } } diff --git a/src/dpp/events/guild_integrations_update.cpp b/src/dpp/events/guild_integrations_update.cpp index 72346c2ee6..45c187fbd2 100644 --- a/src/dpp/events/guild_integrations_update.cpp +++ b/src/dpp/events/guild_integrations_update.cpp @@ -39,9 +39,11 @@ namespace dpp::events { void guild_integrations_update::handle(class discord_client* client, json &j, const std::string &raw) { if (!client->creator->on_guild_integrations_update.empty()) { json& d = j["d"]; - dpp::guild_integrations_update_t giu(client, raw); + dpp::guild_integrations_update_t giu(client->owner, client->shard_id, raw); giu.updating_guild = dpp::find_guild(snowflake_not_null(&d, "guild_id")); - client->creator->on_guild_integrations_update.call(giu); + client->creator->queue_work(1, [c = client->creator, giu]() { + c->on_guild_integrations_update.call(giu); + }); } } diff --git a/src/dpp/events/guild_join_request_delete.cpp b/src/dpp/events/guild_join_request_delete.cpp index f349a5756a..6fd613210a 100644 --- a/src/dpp/events/guild_join_request_delete.cpp +++ b/src/dpp/events/guild_join_request_delete.cpp @@ -38,10 +38,12 @@ namespace dpp::events { void guild_join_request_delete::handle(class discord_client* client, json &j, const std::string &raw) { if (!client->creator->on_guild_join_request_delete.empty()) { json& d = j["d"]; - dpp::guild_join_request_delete_t grd(client, raw); + dpp::guild_join_request_delete_t grd(client->owner, client->shard_id, raw); grd.user_id = snowflake_not_null(&d, "user_id"); grd.guild_id = snowflake_not_null(&d, "guild_id"); - client->creator->on_guild_join_request_delete.call(grd); + client->creator->queue_work(1, [c = client->creator, grd]() { + c->on_guild_join_request_delete.call(grd); + }); } } diff --git a/src/dpp/events/guild_member_add.cpp b/src/dpp/events/guild_member_add.cpp index d253174f42..bb2e0b9c2d 100644 --- a/src/dpp/events/guild_member_add.cpp +++ b/src/dpp/events/guild_member_add.cpp @@ -40,14 +40,16 @@ void guild_member_add::handle(discord_client* client, json &j, const std::string json d = j["d"]; dpp::snowflake guild_id = snowflake_not_null(&d, "guild_id"); dpp::guild* g = dpp::find_guild(guild_id); - dpp::guild_member_add_t gmr(client, raw); + dpp::guild_member_add_t gmr(client->owner, client->shard_id, raw); if (client->creator->cache_policy.user_policy == dpp::cp_none) { dpp::guild_member gm; gm.fill_from_json(&d, guild_id, snowflake_not_null(&(d["user"]), "id")); gmr.added = gm; if (!client->creator->on_guild_member_add.empty()) { gmr.adding_guild = g; - client->creator->on_guild_member_add.call(gmr); + client->creator->queue_work(1, [c = client->creator, gmr]() { + c->on_guild_member_add.call(gmr); + }); } } else { dpp::user* u = dpp::find_user(snowflake_not_null(&(d["user"]), "id")); @@ -69,7 +71,9 @@ void guild_member_add::handle(discord_client* client, json &j, const std::string } if (!client->creator->on_guild_member_add.empty()) { gmr.adding_guild = g; - client->creator->on_guild_member_add.call(gmr); + client->creator->queue_work(1, [c = client->creator, gmr]() { + c->on_guild_member_add.call(gmr); + }); } } } diff --git a/src/dpp/events/guild_member_remove.cpp b/src/dpp/events/guild_member_remove.cpp index 9ac3e5c8bc..2d20d547d9 100644 --- a/src/dpp/events/guild_member_remove.cpp +++ b/src/dpp/events/guild_member_remove.cpp @@ -39,13 +39,15 @@ namespace dpp::events { void guild_member_remove::handle(discord_client* client, json &j, const std::string &raw) { json d = j["d"]; - dpp::guild_member_remove_t gmr(client, raw); + dpp::guild_member_remove_t gmr(client->owner, client->shard_id, raw); gmr.removed.fill_from_json(&(d["user"])); gmr.guild_id = snowflake_not_null(&d, "guild_id"); gmr.removing_guild = dpp::find_guild(gmr.guild_id); if (!client->creator->on_guild_member_remove.empty()) { - client->creator->on_guild_member_remove.call(gmr); + client->creator->queue_work(1, [c = client->creator, gmr]() { + c->on_guild_member_remove.call(gmr); + }); } if (client->creator->cache_policy.user_policy != dpp::cp_none && gmr.removing_guild) { diff --git a/src/dpp/events/guild_member_update.cpp b/src/dpp/events/guild_member_update.cpp index cca7422f3a..92e007ccdc 100644 --- a/src/dpp/events/guild_member_update.cpp +++ b/src/dpp/events/guild_member_update.cpp @@ -21,11 +21,8 @@ ************************************************************************************/ #include #include -#include -#include #include - namespace dpp::events { @@ -43,15 +40,17 @@ void guild_member_update::handle(discord_client* client, json &j, const std::str if (client->creator->cache_policy.user_policy == dpp::cp_none) { dpp::user u; u.fill_from_json(&(d["user"])); - dpp::guild_member_update_t gmu(client, raw); + dpp::guild_member_update_t gmu(client->owner, client->shard_id, raw); gmu.updating_guild = g; if (!client->creator->on_guild_member_update.empty()) { guild_member m; auto& user = d; // d contains roles and other member stuff already m.fill_from_json(&user, guild_id, u.id); gmu.updated = m; + client->creator->queue_work(1, [c = client->creator, gmu]() { + c->on_guild_member_update.call(gmu); + }); } - client->creator->on_guild_member_update.call(gmu); } else { dpp::user* u = dpp::find_user(from_string(d["user"]["id"].get())); if (u) { @@ -63,10 +62,12 @@ void guild_member_update::handle(discord_client* client, json &j, const std::str } if (!client->creator->on_guild_member_update.empty()) { - dpp::guild_member_update_t gmu(client, raw); + dpp::guild_member_update_t gmu(client->owner, client->shard_id, raw); gmu.updating_guild = g; gmu.updated = m; - client->creator->on_guild_member_update.call(gmu); + client->creator->queue_work(0, [c = client->creator, gmu]() { + c->on_guild_member_update.call(gmu); + }); } } } diff --git a/src/dpp/events/guild_members_chunk.cpp b/src/dpp/events/guild_members_chunk.cpp index a5c4be9f8b..6c589efc0b 100644 --- a/src/dpp/events/guild_members_chunk.cpp +++ b/src/dpp/events/guild_members_chunk.cpp @@ -64,10 +64,12 @@ void guild_members_chunk::handle(discord_client* client, json &j, const std::str } } if (!client->creator->on_guild_members_chunk.empty()) { - dpp::guild_members_chunk_t gmc(client, raw); + dpp::guild_members_chunk_t gmc(client->owner, client->shard_id, raw); gmc.adding = g; gmc.members = &um; - client->creator->on_guild_members_chunk.call(gmc); + client->creator->queue_work(1, [c = client->creator, gmc]() { + c->on_guild_members_chunk.call(gmc); + }); } } diff --git a/src/dpp/events/guild_role_create.cpp b/src/dpp/events/guild_role_create.cpp index a3780da3ea..591f6bc1be 100644 --- a/src/dpp/events/guild_role_create.cpp +++ b/src/dpp/events/guild_role_create.cpp @@ -46,10 +46,12 @@ void guild_role_create::handle(discord_client* client, json &j, const std::strin dpp::role r; r.fill_from_json(guild_id, &role); if (!client->creator->on_guild_role_create.empty()) { - dpp::guild_role_create_t grc(client, raw); + dpp::guild_role_create_t grc(client->owner, client->shard_id, raw); grc.creating_guild = g; grc.created = &r; - client->creator->on_guild_role_create.call(grc); + client->creator->queue_work(1, [c = client->creator, grc]() { + c->on_guild_role_create.call(grc); + }); } } else { json &role = d["role"]; @@ -63,10 +65,12 @@ void guild_role_create::handle(discord_client* client, json &j, const std::strin g->roles.push_back(r->id); } if (!client->creator->on_guild_role_create.empty()) { - dpp::guild_role_create_t grc(client, raw); + dpp::guild_role_create_t grc(client->owner, client->shard_id, raw); grc.creating_guild = g; grc.created = r; - client->creator->on_guild_role_create.call(grc); + client->creator->queue_work(1, [c = client->creator, grc]() { + c->on_guild_role_create.call(grc); + }); } } } diff --git a/src/dpp/events/guild_role_delete.cpp b/src/dpp/events/guild_role_delete.cpp index a32628ead0..df01744096 100644 --- a/src/dpp/events/guild_role_delete.cpp +++ b/src/dpp/events/guild_role_delete.cpp @@ -44,20 +44,24 @@ void guild_role_delete::handle(discord_client* client, json &j, const std::strin dpp::guild* g = dpp::find_guild(guild_id); if (client->creator->cache_policy.role_policy == dpp::cp_none) { if (!client->creator->on_guild_role_delete.empty()) { - dpp::guild_role_delete_t grd(client, raw); + dpp::guild_role_delete_t grd(client->owner, client->shard_id, raw); grd.deleting_guild = g; grd.role_id = role_id; grd.deleted = nullptr; - client->creator->on_guild_role_delete.call(grd); + client->creator->queue_work(1, [c = client->creator, grd]() { + c->on_guild_role_delete.call(grd); + }); } } else { dpp::role *r = dpp::find_role(role_id); if (!client->creator->on_guild_role_delete.empty()) { - dpp::guild_role_delete_t grd(client, raw); + dpp::guild_role_delete_t grd(client->owner, client->shard_id, raw); grd.deleting_guild = g; grd.deleted = r; grd.role_id = role_id; - client->creator->on_guild_role_delete.call(grd); + client->creator->queue_work(1, [c = client->creator, grd]() { + c->on_guild_role_delete.call(grd); + }); } if (r) { if (g) { diff --git a/src/dpp/events/guild_role_update.cpp b/src/dpp/events/guild_role_update.cpp index 83a348e271..ba819511cf 100644 --- a/src/dpp/events/guild_role_update.cpp +++ b/src/dpp/events/guild_role_update.cpp @@ -44,10 +44,12 @@ void guild_role_update::handle(discord_client* client, json &j, const std::strin dpp::role r; r.fill_from_json(guild_id, &d); if (!client->creator->on_guild_role_update.empty()) { - dpp::guild_role_update_t gru(client, raw); + dpp::guild_role_update_t gru(client->owner, client->shard_id, raw); gru.updating_guild = g; gru.updated = &r; - client->creator->on_guild_role_update.call(gru); + client->creator->queue_work(1, [c = client->creator, gru]() { + c->on_guild_role_update.call(gru); + }); } } else { json& role = d["role"]; @@ -55,10 +57,12 @@ void guild_role_update::handle(discord_client* client, json &j, const std::strin if (r) { r->fill_from_json(g->id, &role); if (!client->creator->on_guild_role_update.empty()) { - dpp::guild_role_update_t gru(client, raw); + dpp::guild_role_update_t gru(client->owner, client->shard_id, raw); gru.updating_guild = g; gru.updated = r; - client->creator->on_guild_role_update.call(gru); + client->creator->queue_work(1, [c = client->creator, gru]() { + c->on_guild_role_update.call(gru); + }); } } } diff --git a/src/dpp/events/guild_scheduled_event_create.cpp b/src/dpp/events/guild_scheduled_event_create.cpp index 15414756a9..34b0e2cf07 100644 --- a/src/dpp/events/guild_scheduled_event_create.cpp +++ b/src/dpp/events/guild_scheduled_event_create.cpp @@ -39,9 +39,11 @@ namespace dpp::events { void guild_scheduled_event_create::handle(discord_client* client, json &j, const std::string &raw) { json& d = j["d"]; if (!client->creator->on_guild_scheduled_event_create.empty()) { - dpp::guild_scheduled_event_create_t ec(client, raw); + dpp::guild_scheduled_event_create_t ec(client->owner, client->shard_id, raw); ec.created.fill_from_json(&d); - client->creator->on_guild_scheduled_event_create.call(ec); + client->creator->queue_work(1, [c = client->creator, ec]() { + c->on_guild_scheduled_event_create.call(ec); + }); } } diff --git a/src/dpp/events/guild_scheduled_event_delete.cpp b/src/dpp/events/guild_scheduled_event_delete.cpp index 4552743d86..991e1f1efe 100644 --- a/src/dpp/events/guild_scheduled_event_delete.cpp +++ b/src/dpp/events/guild_scheduled_event_delete.cpp @@ -40,9 +40,11 @@ namespace dpp::events { void guild_scheduled_event_delete::handle(discord_client* client, json &j, const std::string &raw) { json& d = j["d"]; if (!client->creator->on_guild_scheduled_event_delete.empty()) { - dpp::guild_scheduled_event_delete_t ed(client, raw); + dpp::guild_scheduled_event_delete_t ed(client->owner, client->shard_id, raw); ed.deleted.fill_from_json(&d); - client->creator->on_guild_scheduled_event_delete.call(ed); + client->creator->queue_work(1, [c = client->creator, ed]() { + c->on_guild_scheduled_event_delete.call(ed); + }); } } diff --git a/src/dpp/events/guild_scheduled_event_update.cpp b/src/dpp/events/guild_scheduled_event_update.cpp index 7f297879a7..7f1085853b 100644 --- a/src/dpp/events/guild_scheduled_event_update.cpp +++ b/src/dpp/events/guild_scheduled_event_update.cpp @@ -40,9 +40,11 @@ namespace dpp::events { void guild_scheduled_event_update::handle(discord_client* client, json &j, const std::string &raw) { json& d = j["d"]; if (!client->creator->on_guild_scheduled_event_update.empty()) { - dpp::guild_scheduled_event_update_t eu(client, raw); + dpp::guild_scheduled_event_update_t eu(client->owner, client->shard_id, raw); eu.updated.fill_from_json(&d); - client->creator->on_guild_scheduled_event_update.call(eu); + client->creator->queue_work(1, [c = client->creator, eu]() { + c->on_guild_scheduled_event_update.call(eu); + }); } } diff --git a/src/dpp/events/guild_scheduled_event_user_add.cpp b/src/dpp/events/guild_scheduled_event_user_add.cpp index deecd3ff3f..c984b4f261 100644 --- a/src/dpp/events/guild_scheduled_event_user_add.cpp +++ b/src/dpp/events/guild_scheduled_event_user_add.cpp @@ -39,11 +39,13 @@ namespace dpp::events { void guild_scheduled_event_user_add::handle(discord_client* client, json &j, const std::string &raw) { json& d = j["d"]; if (!client->creator->on_guild_scheduled_event_user_add.empty()) { - dpp::guild_scheduled_event_user_add_t eua(client, raw); + dpp::guild_scheduled_event_user_add_t eua(client->owner, client->shard_id, raw); eua.guild_id = snowflake_not_null(&d, "guild_id"); eua.user_id = snowflake_not_null(&d, "user_id"); eua.event_id = snowflake_not_null(&d, "guild_scheduled_event_id"); - client->creator->on_guild_scheduled_event_user_add.call(eua); + client->creator->queue_work(1, [c = client->creator, eua]() { + c->on_guild_scheduled_event_user_add.call(eua); + }); } } diff --git a/src/dpp/events/guild_scheduled_event_user_remove.cpp b/src/dpp/events/guild_scheduled_event_user_remove.cpp index 64e4d31106..2cb1e48c54 100644 --- a/src/dpp/events/guild_scheduled_event_user_remove.cpp +++ b/src/dpp/events/guild_scheduled_event_user_remove.cpp @@ -39,11 +39,13 @@ namespace dpp::events { void guild_scheduled_event_user_remove::handle(discord_client* client, json &j, const std::string &raw) { json& d = j["d"]; if (!client->creator->on_guild_scheduled_event_user_remove.empty()) { - dpp::guild_scheduled_event_user_remove_t eur(client, raw); + dpp::guild_scheduled_event_user_remove_t eur(client->owner, client->shard_id, raw); eur.guild_id = snowflake_not_null(&d, "guild_id"); eur.user_id = snowflake_not_null(&d, "user_id"); eur.event_id = snowflake_not_null(&d, "guild_scheduled_event_id"); - client->creator->on_guild_scheduled_event_user_remove.call(eur); + client->creator->queue_work(1, [c = client->creator, eur]() { + c->on_guild_scheduled_event_user_remove.call(eur); + }); } } diff --git a/src/dpp/events/guild_stickers_update.cpp b/src/dpp/events/guild_stickers_update.cpp index d6b4e16237..c0c8773755 100644 --- a/src/dpp/events/guild_stickers_update.cpp +++ b/src/dpp/events/guild_stickers_update.cpp @@ -42,14 +42,16 @@ void guild_stickers_update::handle(discord_client* client, json &j, const std::s if (!client->creator->on_guild_stickers_update.empty()) { dpp::snowflake guild_id = snowflake_not_null(&d, "guild_id"); dpp::guild* g = dpp::find_guild(guild_id); - dpp::guild_stickers_update_t gsu(client, raw); + dpp::guild_stickers_update_t gsu(client->owner, client->shard_id, raw); for (auto & sticker : d["stickers"]) { dpp::sticker s; s.fill_from_json(&sticker); gsu.stickers.emplace_back(s); } gsu.updating_guild = g; - client->creator->on_guild_stickers_update.call(gsu); + client->creator->queue_work(1, [c = client->creator, gsu]() { + c->on_guild_stickers_update.call(gsu); + }); } } diff --git a/src/dpp/events/guild_update.cpp b/src/dpp/events/guild_update.cpp index 4f96fa698e..75dbabec1b 100644 --- a/src/dpp/events/guild_update.cpp +++ b/src/dpp/events/guild_update.cpp @@ -65,9 +65,11 @@ void guild_update::handle(discord_client* client, json &j, const std::string &ra } } if (!client->creator->on_guild_update.empty()) { - dpp::guild_update_t gu(client, raw); + dpp::guild_update_t gu(client->owner, client->shard_id, raw); gu.updated = g; - client->creator->on_guild_update.call(gu); + client->creator->queue_work(1, [c = client->creator, gu]() { + c->on_guild_update.call(gu); + }); } } diff --git a/src/dpp/events/integration_create.cpp b/src/dpp/events/integration_create.cpp index 13b475ab9d..81669856aa 100644 --- a/src/dpp/events/integration_create.cpp +++ b/src/dpp/events/integration_create.cpp @@ -40,9 +40,11 @@ namespace dpp::events { void integration_create::handle(discord_client* client, json &j, const std::string &raw) { if (!client->creator->on_integration_create.empty()) { json& d = j["d"]; - dpp::integration_create_t ic(client, raw); + dpp::integration_create_t ic(client->owner, client->shard_id, raw); ic.created_integration = dpp::integration().fill_from_json(&d); - client->creator->on_integration_create.call(ic); + client->creator->queue_work(1, [c = client->creator, ic]() { + c->on_integration_create.call(ic); + }); } } diff --git a/src/dpp/events/integration_delete.cpp b/src/dpp/events/integration_delete.cpp index 8dce94a072..a3fcd06857 100644 --- a/src/dpp/events/integration_delete.cpp +++ b/src/dpp/events/integration_delete.cpp @@ -39,9 +39,11 @@ namespace dpp::events { void integration_delete::handle(discord_client* client, json &j, const std::string &raw) { if (!client->creator->on_integration_delete.empty()) { json& d = j["d"]; - dpp::integration_delete_t id(client, raw); + dpp::integration_delete_t id(client->owner, client->shard_id, raw); id.deleted_integration = dpp::integration().fill_from_json(&d); - client->creator->on_integration_delete.call(id); + client->creator->queue_work(1, [c = client->creator, id]() { + c->on_integration_delete.call(id); + }); } } diff --git a/src/dpp/events/integration_update.cpp b/src/dpp/events/integration_update.cpp index aad1ab66c8..7201a2554e 100644 --- a/src/dpp/events/integration_update.cpp +++ b/src/dpp/events/integration_update.cpp @@ -39,9 +39,11 @@ namespace dpp::events { void integration_update::handle(discord_client* client, json &j, const std::string &raw) { if (!client->creator->on_integration_update.empty()) { json& d = j["d"]; - dpp::integration_update_t iu(client, raw); + dpp::integration_update_t iu(client->owner, client->shard_id, raw); iu.updated_integration = dpp::integration().fill_from_json(&d); - client->creator->on_integration_update.call(iu); + client->creator->queue_work(1, [c = client->creator, iu]() { + c->on_integration_update.call(iu); + }); } } diff --git a/src/dpp/events/interaction_create.cpp b/src/dpp/events/interaction_create.cpp index 37919206ce..da39a4e018 100644 --- a/src/dpp/events/interaction_create.cpp +++ b/src/dpp/events/interaction_create.cpp @@ -99,73 +99,89 @@ void interaction_create::handle(discord_client* client, json &j, const std::stri if (cmd_data.type == ctxm_message && !client->creator->on_message_context_menu.empty()) { if (i.resolved.messages.size()) { /* Message right-click context menu */ - message_context_menu_t mcm(client, raw); + message_context_menu_t mcm(client->owner, client->shard_id, raw); mcm.command = i; mcm.set_message(i.resolved.messages.begin()->second); - client->creator->on_message_context_menu.call(mcm); + client->creator->queue_work(1, [c = client->creator, mcm]() { + c->on_message_context_menu.call(mcm); + }); } } else if (cmd_data.type == ctxm_user && !client->creator->on_user_context_menu.empty()) { if (i.resolved.users.size()) { /* User right-click context menu */ - user_context_menu_t ucm(client, raw); + user_context_menu_t ucm(client->owner, client->shard_id, raw); ucm.command = i; ucm.set_user(i.resolved.users.begin()->second); - client->creator->on_user_context_menu.call(ucm); + client->creator->queue_work(1, [c = client->creator, ucm]() { + c->on_user_context_menu.call(ucm); + }); } } else if (cmd_data.type == ctxm_chat_input && !client->creator->on_slashcommand.empty()) { - dpp::slashcommand_t sc(client, raw); + dpp::slashcommand_t sc(client->owner, client->shard_id, raw); sc.command = i; - client->creator->on_slashcommand.call(sc); + client->creator->queue_work(1, [c = client->creator, sc]() { + c->on_slashcommand.call(sc); + }); } if (!client->creator->on_interaction_create.empty()) { /* Standard chat input. Note that for backwards compatibility, context menu * events still find their way here. At some point in the future, receiving * ctxm_user and ctxm_message inputs to this event will be depreciated. */ - dpp::interaction_create_t ic(client, raw); + dpp::interaction_create_t ic(client->owner, client->shard_id, raw); ic.command = i; - client->creator->on_interaction_create.call(ic); + client->creator->queue_work(1, [c = client->creator, ic]() { + c->on_interaction_create.call(ic); + }); } } else if (i.type == it_modal_submit) { if (!client->creator->on_form_submit.empty()) { - dpp::form_submit_t fs(client, raw); + dpp::form_submit_t fs(client->owner, client->shard_id, raw); fs.custom_id = string_not_null(&(d["data"]), "custom_id"); fs.command = i; for (auto & c : d["data"]["components"]) { fs.components.push_back(dpp::component().fill_from_json(&c)); } - client->creator->on_form_submit.call(fs); + client->creator->queue_work(1, [c = client->creator, fs]() { + c->on_form_submit.call(fs); + }); } } else if (i.type == it_autocomplete) { // "data":{"id":"903319628816728104","name":"blep","options":[{"focused":true,"name":"animal","type":3,"value":"a"}],"type":1} if (!client->creator->on_autocomplete.empty()) { - dpp::autocomplete_t ac(client, raw); + dpp::autocomplete_t ac(client->owner, client->shard_id, raw); ac.id = snowflake_not_null(&(d["data"]), "id"); ac.name = string_not_null(&(d["data"]), "name"); fill_options(d["data"]["options"], ac.options); ac.command = i; - client->creator->on_autocomplete.call(ac); + client->creator->queue_work(1, [c = client->creator, ac]() { + c->on_autocomplete.call(ac); + }); } } else if (i.type == it_component_button) { dpp::component_interaction bi = std::get(i.data); if (bi.component_type == cot_button) { if (!client->creator->on_button_click.empty()) { - dpp::button_click_t ic(client, raw); + dpp::button_click_t ic(client->owner, client->shard_id, raw); ic.command = i; ic.custom_id = bi.custom_id; ic.component_type = bi.component_type; - client->creator->on_button_click.call(ic); + client->creator->queue_work(1, [c = client->creator, ic]() { + c->on_button_click.call(ic); + }); } } else if (bi.component_type == cot_selectmenu || bi.component_type == cot_user_selectmenu || bi.component_type == cot_role_selectmenu || bi.component_type == cot_mentionable_selectmenu || bi.component_type == cot_channel_selectmenu) { if (!client->creator->on_select_click.empty()) { - dpp::select_click_t ic(client, raw); + dpp::select_click_t ic(client->owner, client->shard_id, raw); ic.command = i; ic.custom_id = bi.custom_id; ic.component_type = bi.component_type; ic.values = bi.values; - client->creator->on_select_click.call(ic); + client->creator->queue_work(1, [c = client->creator, ic]() { + c->on_select_click.call(ic); + }); } } } diff --git a/src/dpp/events/invite_create.cpp b/src/dpp/events/invite_create.cpp index 626ac857e8..b8af9a5eab 100644 --- a/src/dpp/events/invite_create.cpp +++ b/src/dpp/events/invite_create.cpp @@ -39,9 +39,11 @@ namespace dpp::events { void invite_create::handle(discord_client* client, json &j, const std::string &raw) { if (!client->creator->on_invite_create.empty()) { json& d = j["d"]; - dpp::invite_create_t ci(client, raw); + dpp::invite_create_t ci(client->owner, client->shard_id, raw); ci.created_invite = dpp::invite().fill_from_json(&d); - client->creator->on_invite_create.call(ci); + client->creator->queue_work(1, [c = client->creator, ci]() { + c->on_invite_create.call(ci); + }); } } diff --git a/src/dpp/events/invite_delete.cpp b/src/dpp/events/invite_delete.cpp index bd121f6105..746bb4c4c7 100644 --- a/src/dpp/events/invite_delete.cpp +++ b/src/dpp/events/invite_delete.cpp @@ -39,9 +39,11 @@ namespace dpp::events { void invite_delete::handle(discord_client* client, json &j, const std::string &raw) { if (!client->creator->on_invite_delete.empty()) { json& d = j["d"]; - dpp::invite_delete_t cd(client, raw); + dpp::invite_delete_t cd(client->owner, client->shard_id, raw); cd.deleted_invite = dpp::invite().fill_from_json(&d); - client->creator->on_invite_delete.call(cd); + client->creator->queue_work(1, [c = client->creator, cd]() { + c->on_invite_delete.call(cd); + }); } } diff --git a/src/dpp/events/logger.cpp b/src/dpp/events/logger.cpp index 5a9dcf5dd5..d505acae53 100644 --- a/src/dpp/events/logger.cpp +++ b/src/dpp/events/logger.cpp @@ -37,7 +37,7 @@ namespace dpp::events { */ void logger::handle(discord_client* client, json &j, const std::string &raw) { if (!client->creator->on_log.empty()) { - dpp::log_t logmsg(client, raw); + dpp::log_t logmsg(client->owner, client->shard_id, raw); logmsg.severity = (dpp::loglevel)from_string(raw.substr(0, raw.find(';'))); logmsg.message = raw.substr(raw.find(';') + 1, raw.length()); client->creator->on_log.call(logmsg); diff --git a/src/dpp/events/message_create.cpp b/src/dpp/events/message_create.cpp index a1ce729b7b..39ad89adf1 100644 --- a/src/dpp/events/message_create.cpp +++ b/src/dpp/events/message_create.cpp @@ -22,7 +22,6 @@ #include #include #include -#include #include @@ -39,11 +38,13 @@ namespace dpp::events { void message_create::handle(discord_client* client, json &j, const std::string &raw) { if (!client->creator->on_message_create.empty()) { - json d = j["d"]; - dpp::message_create_t msg(client, raw); - msg.msg.fill_from_json(&d, client->creator->cache_policy); - msg.msg.owner = client->creator; - client->creator->on_message_create.call(msg); + client->creator->queue_work(1, [shard_id = client->shard_id, c = client->creator, js = j, raw]() { + json d = js["d"]; + dpp::message_create_t msg(c, shard_id, raw); + msg.msg = message(c).fill_from_json(&d, c->cache_policy); + msg.msg.owner = c; + c->on_message_create.call(msg); + }); } } diff --git a/src/dpp/events/message_delete.cpp b/src/dpp/events/message_delete.cpp index 60f0e97d81..bffd907f20 100644 --- a/src/dpp/events/message_delete.cpp +++ b/src/dpp/events/message_delete.cpp @@ -39,11 +39,13 @@ namespace dpp::events { void message_delete::handle(discord_client* client, json &j, const std::string &raw) { if (!client->creator->on_message_delete.empty()) { json d = j["d"]; - dpp::message_delete_t msg(client, raw); + dpp::message_delete_t msg(client->owner, client->shard_id, raw); msg.id = snowflake_not_null(&d, "id"); msg.guild_id = snowflake_not_null(&d, "guild_id"); msg.channel_id = snowflake_not_null(&d, "channel_id"); - client->creator->on_message_delete.call(msg); + client->creator->queue_work(1, [c = client->creator, msg]() { + c->on_message_delete.call(msg); + }); } } diff --git a/src/dpp/events/message_delete_bulk.cpp b/src/dpp/events/message_delete_bulk.cpp index 394d408fb2..4ac923c900 100644 --- a/src/dpp/events/message_delete_bulk.cpp +++ b/src/dpp/events/message_delete_bulk.cpp @@ -37,14 +37,16 @@ namespace dpp::events { void message_delete_bulk::handle(discord_client* client, json &j, const std::string &raw) { if (!client->creator->on_message_delete_bulk.empty()) { json& d = j["d"]; - dpp::message_delete_bulk_t msg(client, raw); + dpp::message_delete_bulk_t msg(client->owner, client->shard_id, raw); msg.deleting_guild = dpp::find_guild(snowflake_not_null(&d, "guild_id")); msg.deleting_channel = dpp::find_channel(snowflake_not_null(&d, "channel_id")); msg.deleting_user = dpp::find_user(snowflake_not_null(&d, "user_id")); for (auto& m : d["ids"]) { msg.deleted.push_back(from_string(m.get())); } - client->creator->on_message_delete_bulk.call(msg); + client->creator->queue_work(1, [c = client->creator, msg]() { + c->on_message_delete_bulk.call(msg); + }); } } diff --git a/src/dpp/events/message_poll_vote_add.cpp b/src/dpp/events/message_poll_vote_add.cpp index 319d809e9c..3b50d287d5 100644 --- a/src/dpp/events/message_poll_vote_add.cpp +++ b/src/dpp/events/message_poll_vote_add.cpp @@ -40,13 +40,15 @@ void message_poll_vote_add::handle(discord_client* client, json &j, const std::s if (!client->creator->on_message_poll_vote_add.empty()) { json d = j["d"]; - dpp::message_poll_vote_add_t vote(client, raw); + dpp::message_poll_vote_add_t vote(client->owner, client->shard_id, raw); vote.user_id = snowflake_not_null(&j, "user_id"); vote.message_id = snowflake_not_null(&j, "message_id"); vote.channel_id = snowflake_not_null(&j, "channel_id"); vote.guild_id = snowflake_not_null(&j, "guild_id"); vote.answer_id = int32_not_null(&j, "answer_id"); - client->creator->on_message_poll_vote_add.call(vote); + client->creator->queue_work(1, [c = client->creator, vote]() { + c->on_message_poll_vote_add.call(vote); + }); } } diff --git a/src/dpp/events/message_poll_vote_remove.cpp b/src/dpp/events/message_poll_vote_remove.cpp index 55c243e767..feccb4cc00 100644 --- a/src/dpp/events/message_poll_vote_remove.cpp +++ b/src/dpp/events/message_poll_vote_remove.cpp @@ -40,13 +40,15 @@ void message_poll_vote_remove::handle(discord_client* client, json &j, const std if (!client->creator->on_message_poll_vote_add.empty()) { json d = j["d"]; - dpp::message_poll_vote_remove_t vote(client, raw); + dpp::message_poll_vote_remove_t vote(client->owner, client->shard_id, raw); vote.user_id = snowflake_not_null(&j, "user_id"); vote.message_id = snowflake_not_null(&j, "message_id"); vote.channel_id = snowflake_not_null(&j, "channel_id"); vote.guild_id = snowflake_not_null(&j, "guild_id"); vote.answer_id = int32_not_null(&j, "answer_id"); - client->creator->on_message_poll_vote_remove.call(vote); + client->creator->queue_work(1, [c = client->creator, vote]() { + c->on_message_poll_vote_remove.call(vote); + }); } } diff --git a/src/dpp/events/message_reaction_add.cpp b/src/dpp/events/message_reaction_add.cpp index 88a2d05608..392834bf39 100644 --- a/src/dpp/events/message_reaction_add.cpp +++ b/src/dpp/events/message_reaction_add.cpp @@ -39,7 +39,7 @@ namespace dpp::events { void message_reaction_add::handle(discord_client* client, json &j, const std::string &raw) { if (!client->creator->on_message_reaction_add.empty()) { json &d = j["d"]; - dpp::message_reaction_add_t mra(client, raw); + dpp::message_reaction_add_t mra(client->owner, client->shard_id, raw); dpp::snowflake guild_id = snowflake_not_null(&d, "guild_id"); mra.reacting_guild = dpp::find_guild(guild_id); mra.reacting_user = dpp::user().fill_from_json(&(d["member"]["user"])); @@ -50,7 +50,9 @@ void message_reaction_add::handle(discord_client* client, json &j, const std::st mra.message_author_id = snowflake_not_null(&d, "message_author_id"); mra.reacting_emoji = dpp::emoji().fill_from_json(&(d["emoji"])); if (mra.channel_id && mra.message_id) { - client->creator->on_message_reaction_add.call(mra); + client->creator->queue_work(1, [c = client->creator, mra]() { + c->on_message_reaction_add.call(mra); + }); } } } diff --git a/src/dpp/events/message_reaction_remove.cpp b/src/dpp/events/message_reaction_remove.cpp index 36b367c9cb..25ffa9c852 100644 --- a/src/dpp/events/message_reaction_remove.cpp +++ b/src/dpp/events/message_reaction_remove.cpp @@ -39,7 +39,7 @@ namespace dpp::events { void message_reaction_remove::handle(discord_client* client, json &j, const std::string &raw) { if (!client->creator->on_message_reaction_remove.empty()) { json &d = j["d"]; - dpp::message_reaction_remove_t mrr(client, raw); + dpp::message_reaction_remove_t mrr(client->owner, client->shard_id, raw); dpp::snowflake guild_id = snowflake_not_null(&d, "guild_id"); mrr.reacting_guild = dpp::find_guild(guild_id); mrr.reacting_user_id = snowflake_not_null(&d, "user_id"); @@ -48,7 +48,9 @@ void message_reaction_remove::handle(discord_client* client, json &j, const std: mrr.message_id = snowflake_not_null(&d, "message_id"); mrr.reacting_emoji = dpp::emoji().fill_from_json(&(d["emoji"])); if (mrr.channel_id && mrr.message_id) { - client->creator->on_message_reaction_remove.call(mrr); + client->creator->queue_work(1, [c = client->creator, mrr]() { + c->on_message_reaction_remove.call(mrr); + }); } } } diff --git a/src/dpp/events/message_reaction_remove_all.cpp b/src/dpp/events/message_reaction_remove_all.cpp index 71fa31b873..dca6cce8d5 100644 --- a/src/dpp/events/message_reaction_remove_all.cpp +++ b/src/dpp/events/message_reaction_remove_all.cpp @@ -39,13 +39,15 @@ namespace dpp::events { void message_reaction_remove_all::handle(discord_client* client, json &j, const std::string &raw) { if (!client->creator->on_message_reaction_remove_all.empty()) { json &d = j["d"]; - dpp::message_reaction_remove_all_t mrra(client, raw); + dpp::message_reaction_remove_all_t mrra(client->owner, client->shard_id, raw); mrra.reacting_guild = dpp::find_guild(snowflake_not_null(&d, "guild_id")); mrra.channel_id = snowflake_not_null(&d, "channel_id"); mrra.reacting_channel = dpp::find_channel(mrra.channel_id); mrra.message_id = snowflake_not_null(&d, "message_id"); if (mrra.channel_id && mrra.message_id) { - client->creator->on_message_reaction_remove_all.call(mrra); + client->creator->queue_work(1, [c = client->creator, mrra]() { + c->on_message_reaction_remove_all.call(mrra); + }); } } } diff --git a/src/dpp/events/message_reaction_remove_emoji.cpp b/src/dpp/events/message_reaction_remove_emoji.cpp index 97002b4992..ceebccfaac 100644 --- a/src/dpp/events/message_reaction_remove_emoji.cpp +++ b/src/dpp/events/message_reaction_remove_emoji.cpp @@ -39,14 +39,16 @@ namespace dpp::events { void message_reaction_remove_emoji::handle(discord_client* client, json &j, const std::string &raw) { if (!client->creator->on_message_reaction_remove_emoji.empty()) { json &d = j["d"]; - dpp::message_reaction_remove_emoji_t mrre(client, raw); + dpp::message_reaction_remove_emoji_t mrre(client->owner, client->shard_id, raw); mrre.reacting_guild = dpp::find_guild(snowflake_not_null(&d, "guild_id")); mrre.channel_id = snowflake_not_null(&d, "channel_id"); mrre.reacting_channel = dpp::find_channel(mrre.channel_id); mrre.message_id = snowflake_not_null(&d, "message_id"); mrre.reacting_emoji = dpp::emoji().fill_from_json(&(d["emoji"])); if (mrre.channel_id && mrre.message_id) { - client->creator->on_message_reaction_remove_emoji.call(mrre); + client->creator->queue_work(1, [c = client->creator, mrre]() { + c->on_message_reaction_remove_emoji.call(mrre); + }); } } diff --git a/src/dpp/events/message_update.cpp b/src/dpp/events/message_update.cpp index 4b6490a1f2..545ebd78df 100644 --- a/src/dpp/events/message_update.cpp +++ b/src/dpp/events/message_update.cpp @@ -39,11 +39,13 @@ namespace dpp::events { void message_update::handle(discord_client* client, json &j, const std::string &raw) { if (!client->creator->on_message_update.empty()) { json d = j["d"]; - dpp::message_update_t msg(client, raw); + dpp::message_update_t msg(client->owner, client->shard_id, raw); dpp::message m(client->creator); m.fill_from_json(&d); msg.msg = m; - client->creator->on_message_update.call(msg); + client->creator->queue_work(1, [c = client->creator, msg]() { + c->on_message_update.call(msg); + }); } } diff --git a/src/dpp/events/presence_update.cpp b/src/dpp/events/presence_update.cpp index 4238efb378..aac6f37e63 100644 --- a/src/dpp/events/presence_update.cpp +++ b/src/dpp/events/presence_update.cpp @@ -38,9 +38,11 @@ namespace dpp::events { void presence_update::handle(discord_client* client, json &j, const std::string &raw) { if (!client->creator->on_presence_update.empty()) { json& d = j["d"]; - dpp::presence_update_t pu(client, raw); + dpp::presence_update_t pu(client->owner, client->shard_id, raw); pu.rich_presence = dpp::presence().fill_from_json(&d); - client->creator->on_presence_update.call(pu); + client->creator->queue_work(1, [c = client->creator, pu]() { + c->on_presence_update.call(pu); + }); } } diff --git a/src/dpp/events/ready.cpp b/src/dpp/events/ready.cpp index 7990293d98..765f9a9daa 100644 --- a/src/dpp/events/ready.cpp +++ b/src/dpp/events/ready.cpp @@ -68,14 +68,16 @@ void ready::handle(discord_client* client, json &j, const std::string &raw) { } if (!client->creator->on_ready.empty()) { - dpp::ready_t r(client, raw); + dpp::ready_t r(client->owner, client->shard_id, raw); r.session_id = client->sessionid; r.shard_id = client->shard_id; for (const auto& guild : j["d"]["guilds"]) { r.guilds.emplace_back(snowflake_not_null(&guild, "id")); } r.guild_count = r.guilds.size(); - client->creator->on_ready.call(r); + client->creator->queue_work(1, [c = client->creator, r]() { + c->on_ready.call(r); + }); } } diff --git a/src/dpp/events/resumed.cpp b/src/dpp/events/resumed.cpp index b70db335ef..706c45e2dd 100644 --- a/src/dpp/events/resumed.cpp +++ b/src/dpp/events/resumed.cpp @@ -41,10 +41,12 @@ void resumed::handle(discord_client* client, json &j, const std::string &raw) { client->ready = true; if (!client->creator->on_resumed.empty()) { - dpp::resumed_t r(client, raw); + dpp::resumed_t r(client->owner, client->shard_id, raw); r.session_id = client->sessionid; r.shard_id = client->shard_id; - client->creator->on_resumed.call(r); + client->creator->queue_work(1, [c = client->creator, r]() { + c->on_resumed.call(r); + }); } } diff --git a/src/dpp/events/stage_instance_create.cpp b/src/dpp/events/stage_instance_create.cpp index fbbc3099e5..db42c0d1a7 100644 --- a/src/dpp/events/stage_instance_create.cpp +++ b/src/dpp/events/stage_instance_create.cpp @@ -39,9 +39,11 @@ namespace dpp::events { void stage_instance_create::handle(discord_client* client, json &j, const std::string &raw) { if (!client->creator->on_stage_instance_create.empty()) { json& d = j["d"]; - dpp::stage_instance_create_t sic(client, raw); + dpp::stage_instance_create_t sic(client->owner, client->shard_id, raw); sic.created.fill_from_json(&d); - client->creator->on_stage_instance_create.call(sic); + client->creator->queue_work(1, [c = client->creator, sic]() { + c->on_stage_instance_create.call(sic); + }); } } diff --git a/src/dpp/events/stage_instance_delete.cpp b/src/dpp/events/stage_instance_delete.cpp index 54d8459dd5..d639b866e5 100644 --- a/src/dpp/events/stage_instance_delete.cpp +++ b/src/dpp/events/stage_instance_delete.cpp @@ -37,9 +37,11 @@ namespace dpp::events { void stage_instance_delete::handle(discord_client* client, json &j, const std::string &raw) { if (!client->creator->on_stage_instance_delete.empty()) { json& d = j["d"]; - dpp::stage_instance_delete_t sid(client, raw); + dpp::stage_instance_delete_t sid(client->owner, client->shard_id, raw); sid.deleted.fill_from_json(&d); - client->creator->on_stage_instance_delete.call(sid); + client->creator->queue_work(1, [c = client->creator, sid]() { + c->on_stage_instance_delete.call(sid); + }); } } diff --git a/src/dpp/events/stage_instance_update.cpp b/src/dpp/events/stage_instance_update.cpp index 8b2188f998..d445356008 100644 --- a/src/dpp/events/stage_instance_update.cpp +++ b/src/dpp/events/stage_instance_update.cpp @@ -39,9 +39,11 @@ namespace dpp::events { void stage_instance_update::handle(discord_client* client, json &j, const std::string &raw) { if (!client->creator->on_stage_instance_update.empty()) { json& d = j["d"]; - dpp::stage_instance_update_t siu(client, raw); + dpp::stage_instance_update_t siu(client->owner, client->shard_id, raw); siu.updated.fill_from_json(&d); - client->creator->on_stage_instance_update.call(siu); + client->creator->queue_work(1, [c = client->creator, siu]() { + c->on_stage_instance_update.call(siu); + }); } } diff --git a/src/dpp/events/thread_create.cpp b/src/dpp/events/thread_create.cpp index 7e1993a6ee..7049ad6e52 100644 --- a/src/dpp/events/thread_create.cpp +++ b/src/dpp/events/thread_create.cpp @@ -39,10 +39,12 @@ void thread_create::handle(discord_client* client, json& j, const std::string& r g->threads.push_back(t.id); } if (!client->creator->on_thread_create.empty()) { - dpp::thread_create_t tc(client, raw); + dpp::thread_create_t tc(client->owner, client->shard_id, raw); tc.created = t; tc.creating_guild = g; - client->creator->on_thread_create.call(tc); + client->creator->queue_work(1, [c = client->creator, tc]() { + c->on_thread_create.call(tc); + }); } } }; diff --git a/src/dpp/events/thread_delete.cpp b/src/dpp/events/thread_delete.cpp index b4bfc45710..ad4512e2e3 100644 --- a/src/dpp/events/thread_delete.cpp +++ b/src/dpp/events/thread_delete.cpp @@ -39,10 +39,12 @@ void thread_delete::handle(discord_client* client, json& j, const std::string& r g->threads.erase(std::remove(g->threads.begin(), g->threads.end(), t.id), g->threads.end()); } if (!client->creator->on_thread_delete.empty()) { - dpp::thread_delete_t td(client, raw); + dpp::thread_delete_t td(client->owner, client->shard_id, raw); td.deleted = t; td.deleting_guild = g; - client->creator->on_thread_delete.call(td); + client->creator->queue_work(1, [c = client->creator, td]() { + c->on_thread_delete.call(td); + }); } } }; diff --git a/src/dpp/events/thread_list_sync.cpp b/src/dpp/events/thread_list_sync.cpp index 3f850f7950..d0019deb2c 100644 --- a/src/dpp/events/thread_list_sync.cpp +++ b/src/dpp/events/thread_list_sync.cpp @@ -41,7 +41,7 @@ void thread_list_sync::handle(discord_client* client, json& j, const std::string } } if (!client->creator->on_thread_list_sync.empty()) { - dpp::thread_list_sync_t tls(client, raw); + dpp::thread_list_sync_t tls(client->owner, client->shard_id, raw); if (d.find("threads") != d.end()) { for (auto& t : d["threads"]) { tls.threads.push_back(thread().fill_from_json(&t)); @@ -52,7 +52,9 @@ void thread_list_sync::handle(discord_client* client, json& j, const std::string tls.members.push_back(thread_member().fill_from_json(&tm)); } } - client->creator->on_thread_list_sync.call(tls); + client->creator->queue_work(1, [c = client->creator, tls]() { + c->on_thread_list_sync.call(tls); + }); } } }; diff --git a/src/dpp/events/thread_member_update.cpp b/src/dpp/events/thread_member_update.cpp index e4407b425b..61c421afb4 100644 --- a/src/dpp/events/thread_member_update.cpp +++ b/src/dpp/events/thread_member_update.cpp @@ -32,9 +32,11 @@ namespace dpp::events { void thread_member_update::handle(discord_client* client, json& j, const std::string& raw) { if (!client->creator->on_thread_member_update.empty()) { json& d = j["d"]; - dpp::thread_member_update_t tm(client, raw); + dpp::thread_member_update_t tm(client->owner, client->shard_id, raw); tm.updated = thread_member().fill_from_json(&d); - client->creator->on_thread_member_update.call(tm); + client->creator->queue_work(1, [c = client->creator, tm]() { + c->on_thread_member_update.call(tm); + }); } } }; diff --git a/src/dpp/events/thread_members_update.cpp b/src/dpp/events/thread_members_update.cpp index 90d2fa5cb1..d4e14b7676 100644 --- a/src/dpp/events/thread_members_update.cpp +++ b/src/dpp/events/thread_members_update.cpp @@ -34,7 +34,7 @@ void thread_members_update::handle(discord_client* client, json& j, const std::s dpp::guild* g = dpp::find_guild(snowflake_not_null(&d, "guild_id")); if (!client->creator->on_thread_members_update.empty()) { - dpp::thread_members_update_t tms(client, raw); + dpp::thread_members_update_t tms(client->owner, client->shard_id, raw); tms.updating_guild = g; set_snowflake_not_null(&d, "id", tms.thread_id); set_int8_not_null(&d, "member_count", tms.member_count); @@ -52,7 +52,9 @@ void thread_members_update::handle(discord_client* client, json& j, const std::s client->creator->log(dpp::ll_error, std::string("thread_members_update: {}") + e.what()); } } - client->creator->on_thread_members_update.call(tms); + client->creator->queue_work(1, [c = client->creator, tms]() { + c->on_thread_members_update.call(tms); + }); } } }; diff --git a/src/dpp/events/thread_update.cpp b/src/dpp/events/thread_update.cpp index 7d92c9fd4e..9600ef36f5 100644 --- a/src/dpp/events/thread_update.cpp +++ b/src/dpp/events/thread_update.cpp @@ -35,10 +35,12 @@ void thread_update::handle(discord_client* client, json& j, const std::string& r t.fill_from_json(&d); dpp::guild* g = dpp::find_guild(t.guild_id); if (!client->creator->on_thread_update.empty()) { - dpp::thread_update_t tu(client, raw); + dpp::thread_update_t tu(client->owner, client->shard_id, raw); tu.updated = t; tu.updating_guild = g; - client->creator->on_thread_update.call(tu); + client->creator->queue_work(1, [c = client->creator, tu]() { + c->on_thread_update.call(tu); + }); } } }; diff --git a/src/dpp/events/typing_start.cpp b/src/dpp/events/typing_start.cpp index b5334c9f54..bab90053ce 100644 --- a/src/dpp/events/typing_start.cpp +++ b/src/dpp/events/typing_start.cpp @@ -37,13 +37,15 @@ namespace dpp::events { void typing_start::handle(discord_client* client, json &j, const std::string &raw) { if (!client->creator->on_typing_start.empty()) { json& d = j["d"]; - dpp::typing_start_t ts(client, raw); + dpp::typing_start_t ts(client->owner, client->shard_id, raw); ts.typing_guild = dpp::find_guild(snowflake_not_null(&d, "guild_id")); ts.typing_channel = dpp::find_channel(snowflake_not_null(&d, "channel_id")); ts.user_id = snowflake_not_null(&d, "user_id"); ts.typing_user = dpp::find_user(ts.user_id); ts.timestamp = ts_not_null(&d, "timestamp"); - client->creator->on_typing_start.call(ts); + client->creator->queue_work(1, [c = client->creator, ts]() { + c->on_typing_start.call(ts); + }); } } diff --git a/src/dpp/events/user_update.cpp b/src/dpp/events/user_update.cpp index b954b55c27..05aee1b63d 100644 --- a/src/dpp/events/user_update.cpp +++ b/src/dpp/events/user_update.cpp @@ -47,17 +47,21 @@ void user_update::handle(discord_client* client, json &j, const std::string &raw u->fill_from_json(&d); } if (!client->creator->on_user_update.empty()) { - dpp::user_update_t uu(client, raw); + dpp::user_update_t uu(client->owner, client->shard_id, raw); uu.updated = *u; - client->creator->on_user_update.call(uu); + client->creator->queue_work(1, [c = client->creator, uu]() { + c->on_user_update.call(uu); + }); } } else { if (!client->creator->on_user_update.empty()) { dpp::user u; u.fill_from_json(&d); - dpp::user_update_t uu(client, raw); + dpp::user_update_t uu(client->owner, client->shard_id, raw); uu.updated = u; - client->creator->on_user_update.call(uu); + client->creator->queue_work(1, [c = client->creator, uu]() { + c->on_user_update.call(uu); + }); } } } diff --git a/src/dpp/events/voice_server_update.cpp b/src/dpp/events/voice_server_update.cpp index 14cf9f77b8..489a013fdb 100644 --- a/src/dpp/events/voice_server_update.cpp +++ b/src/dpp/events/voice_server_update.cpp @@ -39,7 +39,7 @@ namespace dpp::events { void voice_server_update::handle(discord_client* client, json &j, const std::string &raw) { json &d = j["d"]; - dpp::voice_server_update_t vsu(client, raw); + dpp::voice_server_update_t vsu(client->owner, client->shard_id, raw); vsu.guild_id = snowflake_not_null(&d, "guild_id"); vsu.token = string_not_null(&d, "token"); vsu.endpoint = string_not_null(&d, "endpoint"); @@ -60,7 +60,9 @@ void voice_server_update::handle(discord_client* client, json &j, const std::str } if (!client->creator->on_voice_server_update.empty()) { - client->creator->on_voice_server_update.call(vsu); + client->creator->queue_work(1, [c = client->creator, vsu]() { + c->on_voice_server_update.call(vsu); + }); } } diff --git a/src/dpp/events/voice_state_update.cpp b/src/dpp/events/voice_state_update.cpp index 7be7b18515..30c81eed59 100644 --- a/src/dpp/events/voice_state_update.cpp +++ b/src/dpp/events/voice_state_update.cpp @@ -40,7 +40,7 @@ namespace dpp::events { void voice_state_update::handle(discord_client* client, json &j, const std::string &raw) { json& d = j["d"]; - dpp::voice_state_update_t vsu(client, raw); + dpp::voice_state_update_t vsu(client->owner, client->shard_id, raw); vsu.state = dpp::voicestate().fill_from_json(&d); vsu.state.shard = client; @@ -84,7 +84,9 @@ void voice_state_update::handle(discord_client* client, json &j, const std::stri } if (!client->creator->on_voice_state_update.empty()) { - client->creator->on_voice_state_update.call(vsu); + client->creator->queue_work(1, [c = client->creator, vsu]() { + c->on_voice_state_update.call(vsu); + }); } } diff --git a/src/dpp/events/webhooks_update.cpp b/src/dpp/events/webhooks_update.cpp index 4f676eeade..1750cdb2fd 100644 --- a/src/dpp/events/webhooks_update.cpp +++ b/src/dpp/events/webhooks_update.cpp @@ -38,10 +38,12 @@ namespace dpp::events { void webhooks_update::handle(discord_client* client, json &j, const std::string &raw) { if (!client->creator->on_webhooks_update.empty()) { json& d = j["d"]; - dpp::webhooks_update_t wu(client, raw); + dpp::webhooks_update_t wu(client->owner, client->shard_id, raw); wu.webhook_guild = dpp::find_guild(snowflake_not_null(&d, "guild_id")); wu.webhook_channel = dpp::find_channel(snowflake_not_null(&d, "channel_id")); - client->creator->on_webhooks_update.call(wu); + client->creator->queue_work(1, [c = client->creator, wu]() { + c->on_webhooks_update.call(wu); + }); } } diff --git a/src/dpp/httpsclient.cpp b/src/dpp/httpsclient.cpp index 8c7cff3258..7ab9f5fdac 100644 --- a/src/dpp/httpsclient.cpp +++ b/src/dpp/httpsclient.cpp @@ -30,9 +30,8 @@ namespace dpp { -https_client::https_client(const std::string &hostname, uint16_t port, const std::string &urlpath, const std::string &verb, const std::string &req_body, const http_headers& extra_headers, bool plaintext_connection, uint16_t request_timeout, const std::string &protocol) - : ssl_client(hostname, std::to_string(port), plaintext_connection, false), - state(HTTPS_HEADERS), +https_client::https_client(cluster* creator, const std::string &hostname, uint16_t port, const std::string &urlpath, const std::string &verb, const std::string &req_body, const http_headers& extra_headers, bool plaintext_connection, uint16_t request_timeout, const std::string &protocol, https_client_completion_event done) + : ssl_client(creator, hostname, std::to_string(port), plaintext_connection, false), request_type(verb), path(urlpath), request_body(req_body), @@ -40,11 +39,11 @@ https_client::https_client(const std::string &hostname, uint16_t port, const st request_headers(extra_headers), status(0), http_protocol(protocol), - timeout(request_timeout), - timed_out(false) + timeout(time(nullptr) + request_timeout), + timed_out(false), + completed(done), + state(HTTPS_HEADERS) { - nonblocking = false; - timeout = time(nullptr) + request_timeout; https_client::connect(); } @@ -157,6 +156,10 @@ bool https_client::handle_buffer(std::string &buffer) switch (state) { case HTTPS_HEADERS: if (buffer.find("\r\n\r\n") != std::string::npos) { + + /* Add 10 seconds to retrieve body */ + timeout += 10; + /* Got all headers, proceed to new state */ std::string unparsed = buffer; @@ -192,10 +195,6 @@ bool https_client::handle_buffer(std::string &buffer) } else { content_length = ULLONG_MAX; } - auto it_conn = response_headers.find("connection"); - if (it_conn != response_headers.end() && it_conn->second == "close") { - keepalive = false; - } chunked = false; auto it_txenc = response_headers.find("transfer-encoding"); if (it_txenc != response_headers.end()) { @@ -215,14 +214,16 @@ bool https_client::handle_buffer(std::string &buffer) state_changed = true; continue; } + if (!buffer.empty()) { + /* Got a bit of body content in the same read as the headers */ + continue; + } return true; } else { /* Non-HTTP-like response with invalid headers. Go no further. */ - keepalive = false; return false; } } else { - keepalive = false; return false; } @@ -248,6 +249,10 @@ bool https_client::handle_buffer(std::string &buffer) case HTTPS_CHUNK_TRAILER: if (buffer.length() >= 2 && buffer.substr(0, 2) == "\r\n") { if (state == HTTPS_CHUNK_LAST) { + if (completed) { + completed(this); + completed = {}; + } state = HTTPS_DONE; this->close(); return false; @@ -283,12 +288,20 @@ bool https_client::handle_buffer(std::string &buffer) body += buffer; buffer.clear(); if (content_length == ULLONG_MAX || body.length() >= content_length) { + if (completed) { + completed(this); + completed = {}; + } state = HTTPS_DONE; this->close(); return false; } break; case HTTPS_DONE: + if (completed) { + completed(this); + completed = {}; + } this->close(); return false; break; @@ -311,19 +324,31 @@ http_state https_client::get_state() { } void https_client::one_second_timer() { - if ((this->sfd == SOCKET_ERROR || time(nullptr) >= timeout) && this->state != HTTPS_DONE) { - /* if and only if response is timed out */ - if (this->sfd != SOCKET_ERROR) { - timed_out = true; - } - keepalive = false; + if (!tcp_connect_done && time(nullptr) >= timeout) { + timed_out = true; + this->close(); + } else if (tcp_connect_done && !connected && time(nullptr) >= timeout && this->state != HTTPS_DONE) { + this->close(); + timed_out = true; + } else if (time(nullptr) >= timeout && this->state != HTTPS_DONE) { this->close(); + timed_out = true; } } void https_client::close() { if (state != HTTPS_DONE) { - state = HTTPS_DONE; + if (completed) { + completed(this); + completed = {}; + } + } + state = HTTPS_DONE; + ssl_client::close(); +} + +https_client::~https_client() { + if (sfd != INVALID_SOCKET) { ssl_client::close(); } } diff --git a/src/dpp/message.cpp b/src/dpp/message.cpp index 76c14495c4..9afa80009a 100644 --- a/src/dpp/message.cpp +++ b/src/dpp/message.cpp @@ -663,8 +663,9 @@ embed::embed() : timestamp(0) { } message::message() : managed(0), channel_id(0), guild_id(0), sent(0), edited(0), webhook_id(0), interaction_metadata{}, - owner(nullptr), type(mt_default), flags(0), pinned(false), tts(false), mention_everyone(false) + type(mt_default), flags(0), pinned(false), tts(false), mention_everyone(false) { + owner = nullptr; message_reference.channel_id = 0; message_reference.guild_id = 0; message_reference.message_id = 0; @@ -1059,8 +1060,10 @@ attachment::attachment(struct message* o, json *j) : attachment(o) { void attachment::download(http_completion_event callback) const { /* Download attachment if there is one attached to this object */ - if (owner == nullptr || owner->owner == nullptr) { - throw dpp::logic_exception(err_no_owning_message, "attachment has no owning message/cluster"); + if (owner == nullptr) { + throw dpp::logic_exception(err_no_owning_message, "attachment has no owning message"); + } else if (owner->owner == nullptr) { + throw dpp::logic_exception(err_no_owning_message, "attachment has no owning cluster"); } if (callback && this->id && !this->url.empty()) { owner->owner->request(this->url, dpp::m_get, callback); diff --git a/src/dpp/presence.cpp b/src/dpp/presence.cpp index 7b40dc9a0d..2fff07f31e 100644 --- a/src/dpp/presence.cpp +++ b/src/dpp/presence.cpp @@ -21,6 +21,7 @@ ************************************************************************************/ #include #include +#include #include namespace dpp { @@ -250,7 +251,7 @@ json presence::to_json_impl(bool with_id) const { }; json j({ - {"op", 3}, + {"op", ft_presence}, {"d", { { "status", status_name_mapping[status()] }, diff --git a/src/dpp/queues.cpp b/src/dpp/queues.cpp index 96aa1d5288..80da28786e 100644 --- a/src/dpp/queues.cpp +++ b/src/dpp/queues.cpp @@ -20,19 +20,70 @@ * ************************************************************************************/ #include -#ifdef _WIN32 -/* Central point for forcing inclusion of winsock library for all socket code */ -#include -#pragma comment(lib,"ws2_32") -#endif #include #include #include +#ifdef _WIN32 + #include +#endif namespace dpp { +/** + * @brief List of possible request verbs. + * + * This MUST MATCH the size of the dpp::http_method enum! + */ +constexpr std::array request_verb { + "GET", + "POST", + "PUT", + "PATCH", + "DELETE" +}; + +namespace +{ + +/** + * @brief Comparator for sorting a request container + */ +struct compare_request { + /** + * @brief Less_than comparator for sorting + * @param lhs Left-hand side + * @param rhs Right-hand side + * @return Whether lhs comes before rhs in strict ordering + */ + bool operator()(const std::unique_ptr& lhs, const std::unique_ptr& rhs) const noexcept { + return std::less{}(lhs->endpoint, rhs->endpoint); + }; + + /** + * @brief Less_than comparator for sorting + * @param lhs Left-hand side + * @param rhs Right-hand side + * @return Whether lhs comes before rhs in strict ordering + */ + bool operator()(const std::unique_ptr& lhs, std::string_view rhs) const noexcept { + return std::less{}(lhs->endpoint, rhs); + }; + + /** + * @brief Less_than comparator for sorting + * @param lhs Left-hand side + * @param rhs Right-hand side + * @return Whether lhs comes before rhs in strict ordering + */ + bool operator()(std::string_view lhs, const std::unique_ptr& rhs) const noexcept { + return std::less{}(lhs, rhs->endpoint); + }; +}; + +} + http_request::http_request(const std::string &_endpoint, const std::string &_parameters, http_completion_event completion, const std::string &_postdata, http_method _method, const std::string &audit_reason, const std::string &filename, const std::string &filecontent, const std::string &filemimetype, const std::string &http_protocol) - : complete_handler(completion), completed(false), non_discord(false), endpoint(_endpoint), parameters(_parameters), postdata(_postdata), method(_method), reason(audit_reason), mimetype("application/json"), waiting(false), protocol(http_protocol), request_timeout(5) + : complete_handler(completion), completed(false), non_discord(false), endpoint(_endpoint), parameters(_parameters), postdata(_postdata), method(_method), reason(audit_reason), mimetype("application/json"), waiting(false), protocol(http_protocol) { if (!filename.empty()) { file_name.push_back(filename); @@ -46,21 +97,20 @@ http_request::http_request(const std::string &_endpoint, const std::string &_par } http_request::http_request(const std::string &_endpoint, const std::string &_parameters, http_completion_event completion, const std::string &_postdata, http_method method, const std::string &audit_reason, const std::vector &filename, const std::vector &filecontent, const std::vector &filemimetypes, const std::string &http_protocol) - : complete_handler(completion), completed(false), non_discord(false), endpoint(_endpoint), parameters(_parameters), postdata(_postdata), method(method), reason(audit_reason), file_name(filename), file_content(filecontent), file_mimetypes(filemimetypes), mimetype("application/json"), waiting(false), protocol(http_protocol), request_timeout(5) + : complete_handler(completion), completed(false), non_discord(false), endpoint(_endpoint), parameters(_parameters), postdata(_postdata), method(method), reason(audit_reason), file_name(filename), file_content(filecontent), file_mimetypes(filemimetypes), mimetype("application/json"), waiting(false), protocol(http_protocol) { } -http_request::http_request(const std::string &_url, http_completion_event completion, http_method _method, const std::string &_postdata, const std::string &_mimetype, const std::multimap &_headers, const std::string &http_protocol, time_t _request_timeout) - : complete_handler(completion), completed(false), non_discord(true), endpoint(_url), postdata(_postdata), method(_method), mimetype(_mimetype), req_headers(_headers), waiting(false), protocol(http_protocol), request_timeout(_request_timeout) +http_request::http_request(const std::string &_url, http_completion_event completion, http_method _method, const std::string &_postdata, const std::string &_mimetype, const std::multimap &_headers, const std::string &http_protocol) + : complete_handler(completion), completed(false), non_discord(true), endpoint(_url), postdata(_postdata), method(_method), mimetype(_mimetype), req_headers(_headers), waiting(false), protocol(http_protocol) { } -http_request::~http_request() = default; +http_request::~http_request() = default; void http_request::complete(const http_request_completion_t &c) { - /* Call completion handler only if the request has been completed */ - if (is_completed() && complete_handler) { + if (complete_handler) { complete_handler(c); } } @@ -103,8 +153,13 @@ bool http_request::is_completed() return completed; } +https_client* http_request::get_client() const +{ + return cli.get(); +} + /* Execute a HTTP request */ -http_request_completion_t http_request::run(cluster* owner) { +http_request_completion_t http_request::run(request_concurrency_queue* processor, cluster* owner) { http_request_completion_t rv; double start = dpp::utility::time_f(); @@ -156,14 +211,6 @@ http_request_completion_t http_request::run(cluster* owner) { } } - constexpr std::array request_verb { - "GET", - "POST", - "PUT", - "PATCH", - "DELETE" - }; - multipart_content multipart; if (non_discord) { multipart = { postdata, mimetype }; @@ -175,270 +222,203 @@ http_request_completion_t http_request::run(cluster* owner) { } http_connect_info hci = https_client::get_host_info(_host); try { - https_client cli(hci.hostname, hci.port, _url, request_verb[method], multipart.body, headers, !hci.is_ssl, owner->request_timeout, protocol); - rv.latency = dpp::utility::time_f() - start; - if (cli.timed_out) { - rv.error = h_connection; - owner->log(ll_error, "HTTP(S) error on " + hci.scheme + " connection to " + hci.hostname + ":" + std::to_string(hci.port) + ": Timed out while waiting for the response"); - } else if (cli.get_status() < 100) { - rv.error = h_connection; - owner->log(ll_error, "HTTP(S) error on " + hci.scheme + " connection to " + hci.hostname + ":" + std::to_string(hci.port) + ": Malformed HTTP response"); - } else { - populate_result(_url, owner, rv, cli); - } + cli = std::make_unique( + owner, + hci.hostname, + hci.port, + _url, + request_verb[method], + multipart.body, + headers, + !hci.is_ssl, + owner->request_timeout, + protocol, + [processor, rv, hci, this, owner, start, _url](https_client* client) { + http_request_completion_t result{rv}; + result.latency = dpp::utility::time_f() - start; + if (client->timed_out) { + result.error = h_connection; + owner->log(ll_error, "HTTP(S) error on " + hci.scheme + " connection to " + request_verb[method] + " " + hci.hostname + ":" + std::to_string(hci.port) + _url + ": Timed out while waiting for the response"); + } else if (client->get_status() < 100) { + result.error = h_connection; + owner->log(ll_error, "HTTP(S) error on " + hci.scheme + " connection to " + request_verb[method] + " " + hci.hostname + ":" + std::to_string(hci.port) + _url + ": Malformed HTTP response"); + } + populate_result(_url, owner, result, *client); + /* Set completion flag */ + + bucket_t newbucket; + newbucket.limit = result.ratelimit_limit; + newbucket.remaining = result.ratelimit_remaining; + newbucket.reset_after = result.ratelimit_reset_after; + newbucket.retry_after = result.ratelimit_retry_after; + newbucket.timestamp = time(nullptr); + processor->requests->globally_ratelimited = rv.ratelimit_global; + if (processor->requests->globally_ratelimited) { + /* We are globally rate limited - user up to shenanigans */ + processor->requests->globally_limited_until = (newbucket.retry_after ? newbucket.retry_after : newbucket.reset_after) + newbucket.timestamp; + } + processor->buckets[this->endpoint] = newbucket; + + /* Transfer it to completed requests */ + owner->queue_work(0, [owner, this, result, hci, _url]() { + try { + complete(result); + } + catch (const std::exception& e) { + owner->log(ll_error, "Uncaught exception thrown in HTTPS callback for " + std::string(request_verb[method]) + " " + hci.hostname + ":" + std::to_string(hci.port) + _url + ": " + std::string(e.what())); + } + catch (...) { + owner->log(ll_error, "Uncaught exception thrown in HTTPS callback for " + std::string(request_verb[method]) + " " + hci.hostname + ":" + std::to_string(hci.port) + _url + ": "); + } + completed = true; + }); + } + ); } catch (const std::exception& e) { owner->log(ll_error, "HTTP(S) error on " + hci.scheme + " connection to " + hci.hostname + ":" + std::to_string(hci.port) + ": " + std::string(e.what())); rv.error = h_connection; } - - /* Set completion flag */ - completed = true; return rv; } -request_queue::request_queue(class cluster* owner, uint32_t request_threads) : creator(owner), terminating(false), globally_ratelimited(false), globally_limited_for(0), in_thread_pool_size(request_threads) +request_queue::request_queue(class cluster* owner, uint32_t request_concurrency) : creator(owner), terminating(false), globally_ratelimited(false), globally_limited_until(0), in_queue_pool_size(request_concurrency) { - for (uint32_t in_alloc = 0; in_alloc < in_thread_pool_size; ++in_alloc) { - requests_in.push_back(std::make_unique(owner, this, in_alloc)); + /* Create request_concurrency timer instances */ + for (uint32_t in_alloc = 0; in_alloc < in_queue_pool_size; ++in_alloc) { + requests_in.push_back(std::make_unique(owner, this, in_alloc)); } - out_thread = new std::thread(&request_queue::out_loop, this); -} - -request_queue& request_queue::add_request_threads(uint32_t request_threads) -{ - for (uint32_t in_alloc_ex = 0; in_alloc_ex < request_threads; ++in_alloc_ex) { - requests_in.push_back(std::make_unique(creator, this, in_alloc_ex + in_thread_pool_size)); - } - in_thread_pool_size += request_threads; - return *this; } -uint32_t request_queue::get_request_thread_count() const +uint32_t request_queue::get_request_queue_count() const { - return in_thread_pool_size; + return in_queue_pool_size; } -in_thread::in_thread(class cluster* owner, class request_queue* req_q, uint32_t index) : terminating(false), requests(req_q), creator(owner) +request_concurrency_queue::request_concurrency_queue(class cluster* owner, class request_queue* req_q, uint32_t index) : in_index(index), terminating(false), requests(req_q), creator(owner) { - this->in_thr = new std::thread(&in_thread::in_loop, this, index); + in_timer = creator->start_timer([this](auto timer_handle) { + tick_and_deliver_requests(in_index); + /* Clear pending removals in the removals queue */ + if (time(nullptr) % 90 == 0) { + std::scoped_lock lock1{in_mutex}; + for (auto it = removals.cbegin(); it != removals.cend();) { + if ((*it)->is_completed()) { + it = removals.erase(it); + } else { + ++it; + } + } + } + }, 1); } -in_thread::~in_thread() +request_concurrency_queue::~request_concurrency_queue() { terminate(); - in_thr->join(); - delete in_thr; + creator->stop_timer(in_timer); } -void in_thread::terminate() +void request_concurrency_queue::terminate() { terminating.store(true, std::memory_order_relaxed); - in_ready.notify_one(); } request_queue::~request_queue() { terminating.store(true, std::memory_order_relaxed); - out_ready.notify_one(); for (auto& in_thr : requests_in) { - in_thr->terminate(); // signal all of them here, otherwise they will all join 1 by 1 and it will take forever + /* Note: We don't need to set the atomic to make timers quit, this is purely + * to prevent additional requests going into the queue while it is being destructed + * from other threads, + */ + in_thr->terminate(); } - out_thread->join(); - delete out_thread; } -namespace +void request_concurrency_queue::tick_and_deliver_requests(uint32_t index) { + if (terminating) { + return; + } -/** - * @brief Comparator for sorting a request container - */ -struct compare_request { - /** - * @brief Less_than comparator for sorting - * @param lhs Left-hand side - * @param rhs Right-hand side - * @return Whether lhs comes before rhs in strict ordering - */ - bool operator()(const std::unique_ptr& lhs, const std::unique_ptr& rhs) const noexcept { - return std::less{}(lhs->endpoint, rhs->endpoint); - }; - - /** - * @brief Less_than comparator for sorting - * @param lhs Left-hand side - * @param rhs Right-hand side - * @return Whether lhs comes before rhs in strict ordering - */ - bool operator()(const std::unique_ptr& lhs, std::string_view rhs) const noexcept { - return std::less{}(lhs->endpoint, rhs); - }; - - /** - * @brief Less_than comparator for sorting - * @param lhs Left-hand side - * @param rhs Right-hand side - * @return Whether lhs comes before rhs in strict ordering - */ - bool operator()(std::string_view lhs, const std::unique_ptr& rhs) const noexcept { - return std::less{}(lhs, rhs->endpoint); - }; -}; - -} - -void in_thread::in_loop(uint32_t index) -{ - utility::set_thread_name(std::string("http_req/") + std::to_string(index)); - while (!terminating.load(std::memory_order_relaxed)) { - std::mutex mtx; - std::unique_lock lock{ mtx }; - in_ready.wait_for(lock, std::chrono::seconds(1)); - /* New request to be sent! */ - - if (!requests->globally_ratelimited) { + if (!requests->globally_ratelimited) { - std::vector requests_view; - { - /* Gather all the requests first within a mutex */ - std::shared_lock lock(in_mutex); - if (requests_in.empty()) { - /* Nothing to copy, wait again */ - continue; - } - requests_view.reserve(requests_in.size()); - std::transform(requests_in.begin(), requests_in.end(), std::back_inserter(requests_view), [](const std::unique_ptr &r) { - return r.get(); - }); + std::vector requests_view; + { + /* Gather all the requests first within a mutex */ + std::shared_lock lock(in_mutex); + if (requests_in.empty()) { + /* Nothing to copy, check again when we call the timer in a second */ + return; } + requests_view.reserve(requests_in.size()); + std::transform(requests_in.begin(), requests_in.end(), std::back_inserter(requests_view), [](const std::unique_ptr &r) { + return r.get(); + }); + } - for (auto& request_view : requests_view) { - const std::string &key = request_view->endpoint; - http_request_completion_t rv; - auto currbucket = buckets.find(key); - - if (currbucket != buckets.end()) { - /* There's a bucket for this request. Check its status. If the bucket says to wait, - * skip all requests in this bucket till its ok. - */ - if (currbucket->second.remaining < 1) { - uint64_t wait = (currbucket->second.retry_after ? currbucket->second.retry_after : currbucket->second.reset_after); - if ((uint64_t)time(nullptr) > currbucket->second.timestamp + wait) { - /* Time has passed, we can process this bucket again. send its request. */ - rv = request_view->run(creator); - } else { - if (!request_view->waiting) { - request_view->waiting = true; - } - /* Time not up yet, wait more */ - break; - } + for (auto& request_view : requests_view) { + const std::string &key = request_view->endpoint; + http_request_completion_t rv; + auto currbucket = buckets.find(key); + + if (currbucket != buckets.end()) { + /* There's a bucket for this request. Check its status. If the bucket says to wait, + * skip all requests until the timer value indicates the rate limit won't be hit + */ + if (currbucket->second.remaining < 1) { + uint64_t wait = (currbucket->second.retry_after ? currbucket->second.retry_after : currbucket->second.reset_after); + if ((uint64_t)time(nullptr) > currbucket->second.timestamp + wait) { + /* Time has passed, we can process this bucket again. send its request. */ + request_view->run(this, creator); } else { - /* There's limit remaining, we can just run the request */ - rv = request_view->run(creator); - } - } else { - /* No bucket for this endpoint yet. Just send it, and make one from its reply */ - rv = request_view->run(creator); - } - - bucket_t newbucket; - newbucket.limit = rv.ratelimit_limit; - newbucket.remaining = rv.ratelimit_remaining; - newbucket.reset_after = rv.ratelimit_reset_after; - newbucket.retry_after = rv.ratelimit_retry_after; - newbucket.timestamp = time(nullptr); - requests->globally_ratelimited = rv.ratelimit_global; - if (requests->globally_ratelimited) { - requests->globally_limited_for = (newbucket.retry_after ? newbucket.retry_after : newbucket.reset_after); - } - buckets[request_view->endpoint] = newbucket; - - /* Remove the request from the incoming requests to transfer it to completed requests */ - std::unique_ptr request; - { - /* Find the owned pointer in requests_in */ - std::scoped_lock lock1{in_mutex}; - - auto [begin, end] = std::equal_range(requests_in.begin(), requests_in.end(), key, compare_request{}); - for (auto it = begin; it != end; ++it) { - if (it->get() == request_view) { - /* Grab and remove */ - request = std::move(*it); - requests_in.erase(it); - break; + if (!request_view->waiting) { + request_view->waiting = true; } + /* Time not up yet, wait more */ + break; } + } else { + /* We aren't at the limit, so we can just run the request */ + request_view->run(this, creator); } - /* Make a new entry in the completion list and notify */ - auto hrc = std::make_unique(); - *hrc = rv; - { - std::scoped_lock lock1(requests->out_mutex); - requests->responses_out.push({std::move(request), std::move(hrc)}); - } - requests->out_ready.notify_one(); - } - - } else { - if (requests->globally_limited_for > 0) { - std::this_thread::sleep_for(std::chrono::seconds(requests->globally_limited_for)); - requests->globally_limited_for = 0; + } else { + /* No bucket for this endpoint yet. Just send it, and make one from its reply */ + request_view->run(this, creator); } - requests->globally_ratelimited = false; - in_ready.notify_one(); - } - } -} -bool request_queue::queued_deleting_request::operator<(const queued_deleting_request& other) const noexcept { - return time_to_delete < other.time_to_delete; -} - -bool request_queue::queued_deleting_request::operator<(time_t time) const noexcept { - return time_to_delete < time; -} - - -void request_queue::out_loop() -{ - utility::set_thread_name("req_callback"); - while (!terminating.load(std::memory_order_relaxed)) { - - std::mutex mtx; - std::unique_lock lock{ mtx }; - out_ready.wait_for(lock, std::chrono::seconds(1)); - time_t now = time(nullptr); + /* Remove from inbound requests */ + std::unique_ptr rq; + { + /* Find the owned pointer in requests_in */ + std::scoped_lock lock1{in_mutex}; - /* A request has been completed! */ - completed_request queue_head = {}; - { - std::scoped_lock lock1(out_mutex); - if (responses_out.size()) { - queue_head = std::move(responses_out.front()); - responses_out.pop(); + const std::string &key = request_view->endpoint; + auto [begin, end] = std::equal_range(requests_in.begin(), requests_in.end(), key, compare_request{}); + for (auto it = begin; it != end; ++it) { + if (it->get() == request_view) { + /* Grab and remove */ + rq = std::move(*it); + removals.push_back(std::move(rq)); + requests_in.erase(it); + break; + } + } } } - if (queue_head.request && queue_head.response) { - queue_head.request->complete(*queue_head.response); - /* Queue deletions for 60 seconds from now */ - auto when = now + 60; - auto where = std::lower_bound(responses_to_delete.begin(), responses_to_delete.end(), when); - responses_to_delete.insert(where, {when, std::move(queue_head)}); - } - - /* Check for deletable items every second regardless of select status */ - auto end = std::lower_bound(responses_to_delete.begin(), responses_to_delete.end(), now); - if (end != responses_to_delete.begin()) { - responses_to_delete.erase(responses_to_delete.begin(), end); + } else { + /* If we are globally rate limited, do nothing until we are not */ + if (time(nullptr) > requests->globally_limited_until) { + requests->globally_limited_until = 0; + requests->globally_ratelimited = false; } } } /* Post a http_request into the queue */ -void in_thread::post_request(std::unique_ptr req) +void request_concurrency_queue::post_request(std::unique_ptr req) { { std::scoped_lock lock(in_mutex); @@ -446,17 +426,21 @@ void in_thread::post_request(std::unique_ptr req) auto where = std::lower_bound(requests_in.begin(), requests_in.end(), req->endpoint, compare_request{}); requests_in.emplace(where, std::move(req)); } - in_ready.notify_one(); + /* Immediately trigger requests in this queue */ + tick_and_deliver_requests(in_index); } -/* Simple hash function for hashing urls into thread pool values, - * ensuring that the same url always ends up on the same thread, +/* @brief Simple hash function for hashing urls into request pool values, + * ensuring that the same url always ends up in the same queue, * which means that it will be part of the same ratelimit bucket. * I did consider std::hash for this, but std::hash returned even * numbers for absolutely every string i passed it on g++ 10.0, * so this was a no-no. There are also much bigger more complex * hash functions that claim to be really fast, but this is * readable and small and fits the requirement exactly. + * + * @param s String to hash + * @return Hash value */ inline uint32_t hash(const char *s) { @@ -468,15 +452,24 @@ inline uint32_t hash(const char *s) } /* Post a http_request into a request queue */ -request_queue& request_queue::post_request(std::unique_ptr req) -{ - requests_in[hash(req->endpoint.c_str()) % in_thread_pool_size]->post_request(std::move(req)); +request_queue& request_queue::post_request(std::unique_ptr req) { + if (!terminating) { + requests_in[hash(req->endpoint.c_str()) % in_queue_pool_size]->post_request(std::move(req)); + } return *this; } -bool request_queue::is_globally_ratelimited() const -{ +bool request_queue::is_globally_ratelimited() const { return this->globally_ratelimited; } +size_t request_queue::get_active_request_count() const { + size_t total{}; + for (auto& pool : requests_in) { + std::scoped_lock lock(pool->in_mutex); + total += pool->requests_in.size(); + } + return total; +} + } diff --git a/src/dpp/socketengine.cpp b/src/dpp/socketengine.cpp new file mode 100644 index 0000000000..762dbcf9e5 --- /dev/null +++ b/src/dpp/socketengine.cpp @@ -0,0 +1,120 @@ +/************************************************************************************ + * + * 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. + * + ************************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace dpp { + +bool socket_engine_base::register_socket(const socket_events &e) { + std::unique_lock lock(fds_mutex); + auto i = fds.find(e.fd); + if (e.fd != INVALID_SOCKET && i == fds.end()) { + fds.emplace(e.fd, std::make_unique(e)); + return true; + } + if (e.fd != INVALID_SOCKET && i != fds.end()) { + remove_socket(e.fd); + fds.erase(i); + fds.emplace(e.fd, std::make_unique(e)); + return true; + } + return false; +} + +bool socket_engine_base::update_socket(const socket_events &e) { + std::unique_lock lock(fds_mutex); + if (e.fd != INVALID_SOCKET && fds.find(e.fd) != fds.end()) { + auto iter = fds.find(e.fd); + *(iter->second) = e; + return true; + } + return false; +} + +socket_engine_base::socket_engine_base(cluster* creator) : owner(creator) { +#ifndef _WIN32 + set_signal_handler(SIGALRM); + set_signal_handler(SIGXFSZ); + set_signal_handler(SIGCHLD); + signal(SIGHUP, SIG_IGN); + signal(SIGPIPE, SIG_IGN); +#else + // Set up winsock. + WSADATA wsadata; + if (WSAStartup(MAKEWORD(2, 2), &wsadata)) { + throw dpp::connection_exception(err_connect_failure, "WSAStartup failure"); + } +#endif +} + +time_t last_time = time(nullptr); + +socket_events* socket_engine_base::get_fd(dpp::socket fd) { + std::unique_lock lock(fds_mutex); + auto iter = fds.find(fd); + if (iter == fds.end()) { + return nullptr; + } + return iter->second.get(); +} + +void socket_engine_base::prune() { + if (time(nullptr) != last_time) { + try { + owner->tick_timers(); + } catch (const std::exception& e) { + owner->log(dpp::ll_error, "Uncaught exception in tick_timers: " + std::string(e.what())); + } + + if ((time(nullptr) % 60) == 0) { + /* Every minute, rehash all cache containers. + * We do this from the socket engine now, not from + * shard 0, so no need to run shards to have timers! + */ + dpp::garbage_collection(); + } + + last_time = time(nullptr); + } +} + +bool socket_engine_base::delete_socket(dpp::socket fd) { + std::unique_lock lock(fds_mutex); + auto iter = fds.find(fd); + if (iter == fds.end() || ((iter->second->flags & WANT_DELETION) != 0L)) { + return false; + } + iter->second->flags |= WANT_DELETION; + return true; +} + +bool socket_engine_base::remove_socket(dpp::socket fd) { + return true; +} + +} diff --git a/src/dpp/socketengines/epoll.cpp b/src/dpp/socketengines/epoll.cpp new file mode 100644 index 0000000000..ed601878a8 --- /dev/null +++ b/src/dpp/socketengines/epoll.cpp @@ -0,0 +1,190 @@ +/************************************************************************************ + * + * 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. + * + ************************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace dpp { + +int modify_event(int epoll_handle, socket_events* eh, int new_events) { + if (new_events != eh->flags) { + struct epoll_event new_ev{}; + new_ev.events = EPOLLET; + if ((new_events & WANT_READ) != 0) { + new_ev.events |= EPOLLIN; + } + if ((new_events & WANT_WRITE) != 0) { + new_ev.events |= EPOLLOUT; + } + if ((new_events & WANT_ERROR) != 0) { + new_ev.events |= EPOLLERR; + } + new_ev.data.fd = eh->fd; + epoll_ctl(epoll_handle, EPOLL_CTL_MOD, eh->fd, &new_ev); + } + return new_events; +} + +struct DPP_EXPORT socket_engine_epoll : public socket_engine_base { + + int epoll_handle{INVALID_SOCKET}; + static constexpr size_t MAX_EVENTS = 65536; + std::array events; + + socket_engine_epoll(const socket_engine_epoll&) = delete; + socket_engine_epoll(socket_engine_epoll&&) = delete; + socket_engine_epoll& operator=(const socket_engine_epoll&) = delete; + socket_engine_epoll& operator=(socket_engine_epoll&&) = delete; + + explicit socket_engine_epoll(cluster* creator) : socket_engine_base(creator), epoll_handle(epoll_create(MAX_EVENTS)) { + if (epoll_handle == -1) { + throw dpp::connection_exception("Failed to initialise epoll()"); + } + } + + ~socket_engine_epoll() override { + if (epoll_handle != INVALID_SOCKET) { + close(epoll_handle); + } + } + + void process_events() final { + const int sleep_length = 1000; + int i = epoll_wait(epoll_handle, events.data(), MAX_EVENTS, sleep_length); + + for (int j = 0; j < i; j++) { + epoll_event ev = events[j]; + + const int fd = ev.data.fd; + auto eh = get_fd(fd); + if (eh == nullptr || fd == INVALID_SOCKET) { + continue; + } + + if ((eh->flags & WANT_DELETION) == 0L) try { + + if ((ev.events & EPOLLHUP) != 0U) { + if (eh->on_error) { + eh->on_error(fd, *eh, EPIPE); + } + continue; + } + + if ((ev.events & EPOLLERR) != 0U) { + socklen_t codesize = sizeof(int); + int errcode{}; + if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &errcode, &codesize) < 0) { + errcode = errno; + } + if (eh->on_error) { + eh->on_error(fd, *eh, errcode); + } + continue; + } + + if ((ev.events & EPOLLOUT) != 0U) { + /* Should we have a flag to allow keeping WANT_WRITE? Maybe like WANT_WRITE_ONCE or GREEDY_WANT_WRITE, eh */ + eh->flags = modify_event(epoll_handle, eh, eh->flags & ~WANT_WRITE); + if (eh->on_write) { + eh->on_write(fd, *eh); + } + } + + if ((ev.events & EPOLLIN) != 0U) { + if (eh->on_read) { + eh->on_read(fd, *eh); + } + } + + } catch (const std::exception& e) { + owner->log(ll_trace, "Socket loop exception: " + std::string(e.what())); + eh->on_error(fd, *eh, 0); + } + + if ((eh->flags & WANT_DELETION) != 0L) { + remove_socket(fd); + fds.erase(fd); + } + } + prune(); + } + + bool register_socket(const socket_events& e) final { + bool r = socket_engine_base::register_socket(e); + if (r) { + struct epoll_event ev{}; + ev.events = EPOLLET; + if ((e.flags & WANT_READ) != 0) { + ev.events |= EPOLLIN; + } + if ((e.flags & WANT_WRITE) != 0) { + ev.events |= EPOLLOUT; + } + if ((e.flags & WANT_ERROR) != 0) { + ev.events |= EPOLLERR; + } + ev.data.fd = e.fd; + return epoll_ctl(epoll_handle, EPOLL_CTL_ADD, e.fd, &ev) >= 0; + } + return r; + } + + bool update_socket(const socket_events& e) final { + bool r = socket_engine_base::update_socket(e); + if (r) { + struct epoll_event ev{}; + ev.events = EPOLLET; + if ((e.flags & WANT_READ) != 0) { + ev.events |= EPOLLIN; + } + if ((e.flags & WANT_WRITE) != 0) { + ev.events |= EPOLLOUT; + } + if ((e.flags & WANT_ERROR) != 0) { + ev.events |= EPOLLERR; + } + ev.data.fd = e.fd; + return epoll_ctl(epoll_handle, EPOLL_CTL_MOD, e.fd, &ev) >= 0; + } + return r; + } + +protected: + + bool remove_socket(dpp::socket fd) final { + struct epoll_event ev{}; + epoll_ctl(epoll_handle, EPOLL_CTL_DEL, fd, &ev); + return true; + } +}; + +DPP_EXPORT std::unique_ptr create_socket_engine(cluster *creator) { + return std::make_unique(creator); +} + +}; diff --git a/src/dpp/socketengines/kqueue-facade.h b/src/dpp/socketengines/kqueue-facade.h new file mode 100644 index 0000000000..f11c62c0d2 --- /dev/null +++ b/src/dpp/socketengines/kqueue-facade.h @@ -0,0 +1,69 @@ +/************************************************************************************ + * + * 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. + * + ************************************************************************************/ +#pragma once + +/** + * Include actual kqueue + */ +#include +#include + +#if defined __NetBSD__ && __NetBSD_Version__ <= 999001400 + #define CAST_TYPE intptr_t +#else + #define CAST_TYPE void* +#endif + +#ifndef EV_ONESHOT + +#include + +/** + * This is a facade for kqueue(), a freebsd-only event mechanism. + * It is not documented here and only exists so that when editing the file on Linux + * the linter does not go nuts. For actual documentation of kqueue, + * see the relevant man pages. + */ + +int kqueue(); + +int kevent(int kq, const struct kevent *changelist, int number_changes, struct kevent *event_list, std::size_t number_events, const struct timespec *timeout); + +#define EV_SET(kev, ident, filter, flags, fflags, data, udata) + +struct kevent { + uintptr_t ident; /* identifier for this event */ + short filter; /* filter for event */ + uint16_t flags; /* action flags for kqueue */ + uint32_t fflags; /* filter flag value */ + intptr_t data; /* filter data value */ + void *udata; /* opaque user data identifier */ +}; + +#define EV_ADD 0x0001 +#define EV_DELETE 0x0002 +#define EV_ONESHOT 0x0010 +#define EV_EOF 0x8000 +#define EV_ERROR 0x4000 +#define EVFILT_READ (-1) +#define EVFILT_WRITE (-2) + +#endif diff --git a/src/dpp/socketengines/kqueue.cpp b/src/dpp/socketengines/kqueue.cpp new file mode 100644 index 0000000000..eef50da65e --- /dev/null +++ b/src/dpp/socketengines/kqueue.cpp @@ -0,0 +1,155 @@ +/************************************************************************************ + * + * 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. + * + ************************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "kqueue-facade.h" + +namespace dpp { + +struct DPP_EXPORT socket_engine_kqueue : public socket_engine_base { + + static constexpr size_t MAX_SOCKET_VALUE = 65536; + + int kqueue_handle{INVALID_SOCKET}; + std::array ke_list; + + socket_engine_kqueue(const socket_engine_kqueue&) = delete; + socket_engine_kqueue(socket_engine_kqueue&&) = delete; + socket_engine_kqueue& operator=(const socket_engine_kqueue&) = delete; + socket_engine_kqueue& operator=(socket_engine_kqueue&&) = delete; + + explicit socket_engine_kqueue(cluster* creator) : socket_engine_base(creator), kqueue_handle(kqueue()) { + if (kqueue_handle == -1) { + throw dpp::connection_exception("Failed to initialise kqueue()"); + } + } + + ~socket_engine_kqueue() override { + if (kqueue_handle != INVALID_SOCKET) { + close(kqueue_handle); + } + } + + void process_events() final { + struct timespec ts{}; + ts.tv_sec = 1; + + int i = kevent(kqueue_handle, nullptr, 0, ke_list.data(), static_cast(ke_list.size()), &ts); + if (i < 0) { + return; + } + + for (int j = 0; j < i; j++) { + const struct kevent& kev = ke_list[j]; + auto eh = get_fd(kev.ident); + if (eh == nullptr) { + continue; + } + + if ((eh->flags & WANT_DELETION) == 0L) try { + + const short filter = kev.filter; + if (kev.flags & EV_EOF || kev.flags & EV_ERROR) { + if (eh->on_error) { + eh->on_error(kev.ident, *eh, kev.fflags); + } + continue; + } + if (filter == EVFILT_WRITE) { + const int bits_to_clr = WANT_WRITE; + eh->flags &= ~bits_to_clr; + if (eh->on_write) { + eh->on_write(kev.ident, *eh); + } + } + else if (filter == EVFILT_READ) { + if (eh->on_read) { + eh->on_read(kev.ident, *eh); + } + } + + } catch (const std::exception& e) { + owner->log(ll_trace, "Socket loop exception: " + std::string(e.what())); + eh->on_error(kev.ident, *eh, 0); + } + + + if ((eh->flags & WANT_DELETION) != 0L) { + remove_socket(kev.ident); + fds.erase(kev.ident); + } + } + prune(); + } + + bool set_events(const socket_events& e) { + struct kevent ke{}; + if ((e.flags & WANT_READ) != 0) { + EV_SET(&ke, e.fd, EVFILT_READ, EV_ADD, 0, 0, nullptr); + kevent(kqueue_handle, &ke, 1, nullptr, 0, nullptr); + } + if ((e.flags & WANT_WRITE) != 0) { + EV_SET(&ke, e.fd, EVFILT_WRITE, EV_ADD | EV_ONESHOT, 0, 0, nullptr); + kevent(kqueue_handle, &ke, 1, nullptr, 0, nullptr); + } + return true; + } + + bool register_socket(const socket_events& e) final { + if (socket_engine_base::register_socket(e)) { + return set_events(e); + } + return false; + } + + bool update_socket(const socket_events& e) final { + if (socket_engine_base::update_socket(e)) { + return set_events(e); + } + return false; + } + +protected: + + bool remove_socket(dpp::socket fd) final { + struct kevent ke{}; + EV_SET(&ke, fd, EVFILT_WRITE, EV_DELETE, 0, 0, nullptr); + kevent(kqueue_handle, &ke, 1, nullptr, 0, nullptr); + EV_SET(&ke, fd, EVFILT_READ, EV_DELETE, 0, 0, nullptr); + kevent(kqueue_handle, &ke, 1, nullptr, 0, nullptr); + return true; + } +}; + +DPP_EXPORT std::unique_ptr create_socket_engine(cluster *creator) { + return std::make_unique(creator); +} + +}; + diff --git a/src/dpp/socketengines/poll.cpp b/src/dpp/socketengines/poll.cpp new file mode 100644 index 0000000000..beb168ed9e --- /dev/null +++ b/src/dpp/socketengines/poll.cpp @@ -0,0 +1,204 @@ +/************************************************************************************ + * + * 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. + * + ************************************************************************************/ + +#include +#include +#include +#ifdef _WIN32 +/* Windows-specific sockets includes */ + #include + #include + #include + /* Windows doesn't have standard poll(), it has WSAPoll. + * It's the same thing with different symbol names. + * Microsoft gotta be different. + */ + #define poll(fds, nfds, timeout) WSAPoll(fds, nfds, timeout) + #define pollfd WSAPOLLFD + /* Windows sockets library */ + #pragma comment(lib, "ws2_32") +#else +/* Anything other than Windows (e.g. sane OSes) */ + #include + #include + #include +#endif +#include + +namespace dpp { + +struct DPP_EXPORT socket_engine_poll : public socket_engine_base { + + /* We store the pollfds as a vector. This means that insertion, deletion and updating + * are comparatively slow O(n), but these operations don't happen too often. Obtaining the + * list to pass to poll, which can happen several times a second, is as simple as + * calling poll_set.data() and is O(1). We don't expect mind-blowing performance with poll() + * anyway. + */ + std::vector poll_set; + pollfd out_set[FD_SETSIZE]{0}; + std::shared_mutex poll_set_mutex; + + void process_events() final { + const int poll_delay = 1000; + + prune(); + { + std::shared_lock lock(poll_set_mutex); + if (poll_set.empty()) { + /* On many platforms, it is not possible to wait on an empty set */ + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + return; + } else { + if (poll_set.size() > FD_SETSIZE) { + throw dpp::connection_exception("poll() does not support more than FD_SETSIZE active sockets at once!"); + } + /** + * We must make a copy of the poll_set, because it would cause thread locking/contention + * issues if we had it locked for read during poll/iteration of the returned set. + */ + std::copy(poll_set.begin(), poll_set.end(), out_set); + } + } + + int i = poll(out_set, static_cast(poll_set.size()), poll_delay); + int processed = 0; + + for (size_t index = 0; index < poll_set.size() && processed < i; index++) { + const int fd = out_set[index].fd; + const short revents = out_set[index].revents; + + if (revents > 0) { + processed++; + } + + socket_events *eh = get_fd(fd); + if (eh == nullptr || eh->flags & WANT_DELETION) { + continue; + } + + if ((eh->flags & WANT_DELETION) == 0L) try { + + if ((revents & POLLHUP) != 0) { + eh->on_error(fd, *eh, 0); + continue; + } + + if ((revents & POLLERR) != 0) { + socklen_t codesize = sizeof(int); + int errcode{}; + if (getsockopt(fd, SOL_SOCKET, SO_ERROR, (char *) &errcode, &codesize) < 0) { + errcode = errno; + } + eh->on_error(fd, *eh, errcode); + continue; + } + + if ((revents & POLLIN) != 0) { + eh->on_read(fd, *eh); + } + + if ((revents & POLLOUT) != 0) { + eh->flags &= ~WANT_WRITE; + update_socket(*eh); + eh->on_write(fd, *eh); + } + + } catch (const std::exception &e) { + eh->on_error(fd, *eh, 0); + } + + if ((eh->flags & WANT_DELETION) != 0L) { + remove_socket(fd); + std::unique_lock lock(fds_mutex); + fds.erase(fd); + } + } + } + +#if _WIN32 + ~socket_engine_poll() override { + WSACleanup(); + } +#endif + + bool register_socket(const socket_events& e) final { + bool r = socket_engine_base::register_socket(e); + if (r) { + std::unique_lock lock(poll_set_mutex); + pollfd fd_info{}; + fd_info.fd = e.fd; + fd_info.events = 0; + if ((e.flags & WANT_READ) != 0) { + fd_info.events |= POLLIN; + } + if ((e.flags & WANT_WRITE) != 0) { + fd_info.events |= POLLOUT; + } + poll_set.push_back(fd_info); + } + return r; + } + + bool update_socket(const socket_events& e) final { + bool r = socket_engine_base::update_socket(e); + if (r) { + std::unique_lock lock(poll_set_mutex); + /* We know this will succeed */ + for (pollfd& fd_info : poll_set) { + if (fd_info.fd != e.fd) { + continue; + } + fd_info.events = 0; + if ((e.flags & WANT_READ) != 0) { + fd_info.events |= POLLIN; + } + if ((e.flags & WANT_WRITE) != 0) { + fd_info.events |= POLLOUT; + } + break; + } + } + return r; + } + + explicit socket_engine_poll(cluster* creator) : socket_engine_base(creator) { }; + +protected: + + bool remove_socket(dpp::socket fd) final { + std::unique_lock lock(poll_set_mutex); + for (auto i = poll_set.begin(); i != poll_set.end(); ++i) { + if (i->fd == fd) { + poll_set.erase(i); + return true; + } + } + return false; + } +}; + +DPP_EXPORT std::unique_ptr create_socket_engine(cluster* creator) { + return std::make_unique(creator); +} + +}; + diff --git a/src/dpp/sslclient.cpp b/src/dpp/sslclient.cpp index 7cdf92150e..c7c9a15c02 100644 --- a/src/dpp/sslclient.cpp +++ b/src/dpp/sslclient.cpp @@ -26,17 +26,8 @@ #include #include #include - /* Windows doesn't have standard poll(), it has WSAPoll. - * It's the same thing with different symbol names. - * Microsoft gotta be different. - */ - #define poll(fds, nfds, timeout) WSAPoll(fds, nfds, timeout) - #define pollfd WSAPOLLFD - /* Windows sockets library */ - #pragma comment(lib, "ws2_32") #else - /* Anyting other than Windows (e.g. sane OSes) */ - #include + /* Anything other than Windows (e.g. sane OSes) */ #include #include #endif @@ -60,17 +51,20 @@ #include #include #include +#include #include #include -#include #include #include +#include /* Maximum allowed time in milliseconds for socket read/write timeouts and connect() */ constexpr uint16_t SOCKET_OP_TIMEOUT{5000}; namespace dpp { +uint64_t last_unique_id{1}; + /** * @brief This is an opaque class containing openssl library specific structures. * We define it this way so that the public facing D++ library doesn't require @@ -81,7 +75,7 @@ class openssl_connection { /** * @brief OpenSSL session */ - SSL* ssl; + SSL* ssl{nullptr}; }; /** @@ -108,20 +102,6 @@ class openssl_context_deleter { */ thread_local std::unique_ptr openssl_context; -/** - * @brief Keepalive sessions, per-thread - */ -thread_local std::unordered_map keepalives; - -/* You'd think that we would get better performance with a bigger buffer, but SSL frames are 16k each. - * SSL_read in non-blocking mode will only read 16k at a time. There's no point in a bigger buffer as - * it'd go unused. - */ -constexpr uint16_t DPP_BUFSIZE{16 * 1024}; - -/* Represents a failed socket system call, e.g. connect() failure */ -constexpr int ERROR_STATUS{-1}; - bool close_socket(dpp::socket sfd) { /* close_socket on an error socket is a non-op */ @@ -165,14 +145,10 @@ bool set_nonblocking(dpp::socket sockfd, bool non_blocking) * @param addr address to connect to * @param addrlen address length * @param timeout_ms timeout in milliseconds - * @return int -1 on error, 0 on succcess just like POSIX connect() + * @return int -1 on error, 0 on success just like POSIX connect() * @throw dpp::connection_exception on failure */ -int connect_with_timeout(dpp::socket sockfd, const struct sockaddr *addr, socklen_t addrlen, unsigned int timeout_ms) { -#ifdef __APPLE__ - /* Unreliable on OSX right now */ - return (::connect(sockfd, addr, addrlen)); -#else +int start_connecting(dpp::socket sockfd, const struct sockaddr *addr, socklen_t addrlen, unsigned int timeout_ms) { if (!set_nonblocking(sockfd, true)) { throw dpp::connection_exception(err_nonblocking_failure, "Can't switch socket to non-blocking mode!"); } @@ -192,30 +168,7 @@ int connect_with_timeout(dpp::socket sockfd, const struct sockaddr *addr, sockle if (rc == -1 && err != EWOULDBLOCK && err != EINPROGRESS) { throw connection_exception(err_connect_failure, strerror(errno)); } - - /* Set a deadline timestamp 'timeout' ms from now */ - double deadline = utility::time_f() + (timeout_ms / 1000.0); - - do { - if (utility::time_f() >= deadline) { - throw connection_exception(err_connection_timed_out, "Connection timed out"); - } - pollfd pfd = {}; - pfd.fd = sockfd; - pfd.events = POLLOUT; - const int r = ::poll(&pfd, 1, 10); - if (r > 0 && pfd.revents & POLLOUT) { - rc = 0; - } else if (r != 0 || pfd.revents & POLLERR) { - throw connection_exception(err_connection_timed_out, strerror(errno)); - } - } while (rc == -1); - - if (!set_nonblocking(sockfd, false)) { - throw connection_exception(err_nonblocking_failure, "Can't switch socket to blocking mode!"); - } - return rc; -#endif + return 0; } #ifndef _WIN32 @@ -230,71 +183,33 @@ void set_signal_handler(int signal) } #endif -ssl_client::ssl_client(const std::string &_hostname, const std::string &_port, bool plaintext_downgrade, bool reuse) : - nonblocking(false), +uint64_t ssl_client::get_unique_id() const { + return unique_id; +} + +ssl_client::ssl_client(cluster* creator, const std::string &_hostname, const std::string &_port, bool plaintext_downgrade, bool reuse) : sfd(INVALID_SOCKET), ssl(nullptr), last_tick(time(nullptr)), + start(time(nullptr)), hostname(_hostname), port(_port), bytes_out(0), bytes_in(0), plaintext(plaintext_downgrade), - make_new(true), - keepalive(reuse) + timer_handle(0), + unique_id(last_unique_id++), + keepalive(reuse), + owner(creator) { -#ifndef WIN32 - set_signal_handler(SIGALRM); - set_signal_handler(SIGXFSZ); - set_signal_handler(SIGCHLD); - signal(SIGHUP, SIG_IGN); - signal(SIGPIPE, SIG_IGN); -#else - // Set up winsock. - WSADATA wsadata; - if (WSAStartup(MAKEWORD(2, 2), &wsadata)) { - throw dpp::connection_exception(err_connect_failure, "WSAStartup failure"); - } -#endif - if (keepalive) { - std::string identifier((!plaintext ? "ssl://" : "tcp://") + hostname + ":" + port); - auto iter = keepalives.find(identifier); - if (iter != keepalives.end()) { - /* Found a keepalive connection, check it is still connected/valid via poll() for error */ - pollfd pfd = {}; - pfd.fd = iter->second.sfd; - pfd.events = POLLOUT; - int r = ::poll(&pfd, 1, 1); - if (time(nullptr) > (iter->second.created + 60) || r < 0 || pfd.revents & POLLERR) { - make_new = true; - /* This connection is dead, free its resources and make a new one */ - if (iter->second.ssl->ssl) { - SSL_free(iter->second.ssl->ssl); - iter->second.ssl->ssl = nullptr; - } - close_socket(iter->second.sfd); - iter->second.sfd = INVALID_SOCKET; - delete iter->second.ssl; - } else { - /* Connection is good, lets use it */ - this->sfd = iter->second.sfd; - this->ssl = iter->second.ssl; - make_new = false; - } - /* We don't keep in-flight connections in the keepalives list */ - keepalives.erase(iter); - } - - } - if (make_new) { - if (plaintext) { - ssl = nullptr; - } else { - ssl = new openssl_connection(); - } + if (plaintext) { + ssl = nullptr; + } else { + ssl = new openssl_connection(); } try { - this->connect(); + //this->connect(); + ssl_client::connect(); } catch (std::exception&) { cleanup(); @@ -305,98 +220,25 @@ ssl_client::ssl_client(const std::string &_hostname, const std::string &_port, b /* SSL Client constructor throws std::runtime_error if it can't connect to the host */ void ssl_client::connect() { - /* Initial connection is done in blocking mode. There is a timeout on it. */ - nonblocking = false; - - if (make_new) { - /* Resolve hostname to IP */ - int err = 0; - const dns_cache_entry* addr = resolve_hostname(hostname, port); - sfd = addr->make_connecting_socket(); - address_t destination = addr->get_connecting_address(from_string(this->port, std::dec)); - if (sfd == ERROR_STATUS) { - err = errno; - } else if (connect_with_timeout(sfd, destination.get_socket_address(), destination.size(), SOCKET_OP_TIMEOUT) != 0) { - close_socket(sfd); - sfd = ERROR_STATUS; - } - - /* Check if none of the IPs yielded a valid connection */ - if (sfd == ERROR_STATUS) { - throw dpp::connection_exception(err_connect_failure, strerror(err)); - } - - if (!plaintext) { - /* Each thread needs a context, but we don't need to make a new one for each connection */ - if (!openssl_context) { - /* We're good to go - hand the fd over to openssl */ - const SSL_METHOD *method = TLS_client_method(); /* Create new client-method instance */ - - /* Create SSL context */ - openssl_context.reset(SSL_CTX_new(method)); - if (!openssl_context) { - throw dpp::connection_exception(err_ssl_context, "Failed to create SSL client context!"); - } - - /* Do not allow SSL 3.0, TLS 1.0 or 1.1 - * https://www.packetlabs.net/posts/tls-1-1-no-longer-secure/ - */ - if (!SSL_CTX_set_min_proto_version(openssl_context.get(), TLS1_2_VERSION)) { - throw dpp::connection_exception(err_ssl_version, "Failed to set minimum SSL version!"); - } - } - - /* Create SSL session */ - ssl->ssl = SSL_new(openssl_context.get()); - if (ssl->ssl == nullptr) { - throw dpp::connection_exception(err_ssl_new, "SSL_new failed!"); - } - - SSL_set_fd(ssl->ssl, (int)sfd); - - /* Server name identification (SNI) */ - SSL_set_tlsext_host_name(ssl->ssl, hostname.c_str()); - -#ifndef _WIN32 - /* On Linux, we can set socket timeouts so that SSL_connect eventually gives up */ - timeval tv; - tv.tv_sec = 0; - tv.tv_usec = SOCKET_OP_TIMEOUT * 1000; - setsockopt(sfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); - setsockopt(sfd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)); -#endif - if (SSL_connect(ssl->ssl) != 1) { - throw dpp::connection_exception(err_ssl_connect, "SSL_connect error"); - } - - this->cipher = SSL_get_cipher(ssl->ssl); - } + /* Resolve hostname to IP */ + int err = 0; + const dns_cache_entry* addr = resolve_hostname(hostname, port); + sfd = addr->make_connecting_socket(); + address_t destination = addr->get_connecting_address(from_string(this->port, std::dec)); + if (sfd == ERROR_STATUS) { + err = errno; + } else { + start_connecting(sfd, destination.get_socket_address(), destination.size(), SOCKET_OP_TIMEOUT); + } + /* Check if valid connection started */ + if (sfd == ERROR_STATUS) { + throw dpp::connection_exception(err_connect_failure, strerror(err)); } } void ssl_client::socket_write(const std::string_view data) { - /* If we are in nonblocking mode, append to the buffer, - * otherwise just use SSL_write directly. The only time we - * use SSL_write directly is during connection before the - * ReadLoop is called, which allows for guaranteed simple - * lock-step delivery e.g. for HTTP header negotiation - */ - if (nonblocking) { - obuffer += data; - return; - } - - const int data_length = static_cast(data.length()); - if (plaintext) { - if (sfd == INVALID_SOCKET || ::send(sfd, data.data(), data_length, 0) != data_length) { - throw dpp::connection_exception(err_write, "write() failed"); - } - } else { - if (SSL_write(ssl->ssl, data.data(), data_length) != data_length) { - throw dpp::connection_exception(err_ssl_write, "SSL_write() failed"); - } - } + obuffer += data; } void ssl_client::one_second_timer() @@ -411,208 +253,300 @@ void ssl_client::log(dpp::loglevel severity, const std::string &msg) const { } -void ssl_client::read_loop() +void ssl_client::complete_handshake(const socket_events* ev) { - /* The read loop is non-blocking using poll(). This method - * cannot read while it is waiting for write, or write while it is - * waiting for read. This is a limitation of the openssl libraries, - * as SSL is sent and received in low level ~16k frames which must - * be synchronised and ordered correctly. Attempting to send while - * we need another frame or receive while we are due to send a frame - * would cause the protocol to break. - */ - int r = 0, sockets = 1; - size_t client_to_server_length = 0, client_to_server_offset = 0; - bool read_blocked_on_write = false, write_blocked_on_read = false, read_blocked = false; - pollfd pfd[2] = {}; - char client_to_server_buffer[DPP_BUFSIZE], server_to_client_buffer[DPP_BUFSIZE]; + if (!ssl || !ssl->ssl) { + return; + } + auto status = SSL_do_handshake(ssl->ssl); + if (status != 1) { + auto code = SSL_get_error(ssl->ssl, status); + switch (code) { + case SSL_ERROR_NONE: { + connected = true; + socket_events se{*ev}; + se.flags = dpp::WANT_READ | dpp::WANT_WRITE | dpp::WANT_ERROR; + owner->socketengine->update_socket(se); + break; + } + case SSL_ERROR_WANT_WRITE: { + socket_events se{*ev}; + se.flags = dpp::WANT_READ | dpp::WANT_WRITE | dpp::WANT_ERROR; + owner->socketengine->update_socket(se); + break; + } + case SSL_ERROR_WANT_READ: { + socket_events se{*ev}; + se.flags = dpp::WANT_READ | dpp::WANT_ERROR; + owner->socketengine->update_socket(se); + break; + } + default: { + throw dpp::connection_exception(err_ssl_connect, "SSL_do_handshake error: " + std::to_string(status) +";" + std::to_string(code)); + } + } + } else { + socket_events se{*ev}; + se.flags = dpp::WANT_WRITE | dpp::WANT_READ | dpp::WANT_ERROR; + owner->socketengine->update_socket(se); + connected = true; + this->cipher = SSL_get_cipher(ssl->ssl); + } - try { +} + +void ssl_client::on_read(socket fd, const struct socket_events& ev) { + + if (sfd == INVALID_SOCKET) { + return; + } - if (sfd == INVALID_SOCKET) { - throw dpp::connection_exception(err_invalid_socket, "Invalid file descriptor in read_loop()"); + if (plaintext && connected) { + int r = (int) ::recv(sfd, server_to_client_buffer, DPP_BUFSIZE, 0); + if (r <= 0) { + this->close(); + return; } - - /* Make the socket nonblocking */ - if (!set_nonblocking(sfd, true)) { - throw dpp::connection_exception(err_nonblocking_failure, "Can't switch socket to non-blocking mode!"); + buffer.append(server_to_client_buffer, r); + if (!this->handle_buffer(buffer)) { + this->close(); + return; } - nonblocking = true; - - /* Loop until there is a socket error */ - while(true) { + bytes_in += r; + } else if (!plaintext && connected && ssl && ssl->ssl) { + int r = SSL_read(ssl->ssl, server_to_client_buffer, DPP_BUFSIZE); + int e = SSL_get_error(ssl->ssl,r); + + switch (e) { + case SSL_ERROR_NONE: + /* Data received, add it to the buffer */ + if (r > 0) { + buffer.append(server_to_client_buffer, r); - if (last_tick != time(nullptr)) { - this->one_second_timer(); - last_tick = time(nullptr); + if (!this->handle_buffer(buffer)) { + this->close(); + return; + } else { + socket_events se{ev}; + se.flags = WANT_READ | WANT_WRITE | WANT_ERROR; + owner->socketengine->update_socket(se); + } + bytes_in += r; + } + break; + case SSL_ERROR_ZERO_RETURN: + /* End of data */ + SSL_shutdown(ssl->ssl); + return; + case SSL_ERROR_WANT_READ: { + socket_events se{ev}; + se.flags = WANT_READ | WANT_ERROR; + owner->socketengine->update_socket(se); + break; } - - sockets = 1; - pfd[0].fd = sfd; - pfd[0].events = POLLIN; - pfd[1].events = 0; - - if (custom_readable_fd && custom_readable_fd() >= 0) { - int cfd = (int)custom_readable_fd(); - pfd[1].fd = cfd; - pfd[1].events = POLLIN; - sockets = 2; + /* We get a WANT_WRITE if we're trying to rehandshake, and we block on a write during that rehandshake. + * We need to wait on the socket to be writeable but initiate the read when it is + */ + case SSL_ERROR_WANT_WRITE: { + socket_events se{ev}; + se.flags = WANT_READ | WANT_WRITE | WANT_ERROR; + owner->socketengine->update_socket(se); + break; } - if (custom_writeable_fd && custom_writeable_fd() >= 0) { - int cfd = (int)custom_writeable_fd(); - pfd[1].fd = cfd; - pfd[1].events |= POLLOUT; - sockets = 2; + case SSL_ERROR_SYSCALL: { + if (errno != 0) { + this->close(); + } + break; } - - if (sfd == -1) { - throw dpp::connection_exception(err_invalid_socket, "File descriptor invalidated, connection died"); + default: { + this->close(); + return; } + } + } - /* If we're waiting for a read on the socket don't try to write to the server */ - if (client_to_server_length || obuffer.length() || read_blocked_on_write) { - pfd[0].events |= POLLOUT; - } + if (!connected && !plaintext) { + complete_handshake(&ev); + } - const int64_t now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); - int poll_time = 1000 - (now % 1000); - poll_time = poll_time > 400 ? 1000 : poll_time + poll_time / 3 + 1; - r = ::poll(pfd, sockets, now / 1000 == (int64_t)last_tick ? poll_time : 0); + if (connected && ssl && ssl->ssl && (!obuffer.empty() || SSL_want_write(ssl->ssl))) { + socket_events se{ev}; + se.flags = WANT_READ | WANT_WRITE | WANT_ERROR; + owner->socketengine->update_socket(se); + } +} - if (r == 0) { - continue; - } +void ssl_client::on_write(socket fd, const struct socket_events& e) { + + if (sfd == INVALID_SOCKET) { + return; + } - if (custom_writeable_fd && custom_writeable_fd() >= 0 && pfd[1].revents & POLLOUT) { - custom_writeable_ready(); + if (!tcp_connect_done) { + tcp_connect_done = true; + } + if (!connected && plaintext) { + /* Plaintext sockets connect immediately on first write event */ + connected = true; + } else if (!connected) { + /* SSL handshake and session setup */ + + /* Each thread needs a context, but we don't need to make a new one for each connection */ + if (!openssl_context) { + /* We're good to go - hand the fd over to openssl */ + const SSL_METHOD *method = TLS_client_method(); /* Create new client-method instance */ + + /* Create SSL context */ + openssl_context.reset(SSL_CTX_new(method)); + if (!openssl_context) { + throw dpp::connection_exception(err_ssl_context, "Failed to create SSL client context!"); } - if (custom_readable_fd && custom_readable_fd() >= 0 && pfd[1].revents & POLLIN) { - custom_readable_ready(); + + /* Do not allow SSL 3.0, TLS 1.0 or 1.1 + * https://www.packetlabs.net/posts/tls-1-1-no-longer-secure/ + */ + if (!SSL_CTX_set_min_proto_version(openssl_context.get(), TLS1_2_VERSION)) { + throw dpp::connection_exception(err_ssl_version, "Failed to set minimum SSL version!"); } - if ((pfd[0].revents & POLLERR) || (pfd[0].revents & POLLNVAL) || sfd == INVALID_SOCKET) { - throw dpp::connection_exception(err_socket_error, strerror(errno)); + } + if (ssl != nullptr && ssl->ssl == nullptr) { + /* Create SSL session */ + std::lock_guard lock(ssl_mutex); + ssl->ssl = SSL_new(openssl_context.get()); + if (ssl->ssl == nullptr) { + throw dpp::connection_exception(err_ssl_new, "SSL_new failed!"); } - /* Now check if there's data to read */ - if (((pfd[0].revents & POLLIN) && !write_blocked_on_read) || (read_blocked_on_write && (pfd[0].revents & POLLOUT))) { - if (plaintext) { - read_blocked_on_write = false; - read_blocked = false; - r = (int) ::recv(sfd, server_to_client_buffer, DPP_BUFSIZE, 0); + SSL_set_fd(ssl->ssl, (int) sfd); + SSL_set_connect_state(ssl->ssl); - if (r <= 0) { - /* error or EOF */ - return; - } - - buffer.append(server_to_client_buffer, r); - if (!this->handle_buffer(buffer)) { - return; - } - bytes_in += r; - } else { - do { - read_blocked_on_write = false; - read_blocked = false; - - r = SSL_read(ssl->ssl,server_to_client_buffer,DPP_BUFSIZE); - int e = SSL_get_error(ssl->ssl,r); - - switch (e) { - case SSL_ERROR_NONE: - /* Data received, add it to the buffer */ - if (r > 0) { - buffer.append(server_to_client_buffer, r); - if (!this->handle_buffer(buffer)) { - return; - } - bytes_in += r; - } - break; - case SSL_ERROR_ZERO_RETURN: - /* End of data */ - SSL_shutdown(ssl->ssl); - return; - break; - case SSL_ERROR_WANT_READ: - read_blocked = true; - break; - - /* We get a WANT_WRITE if we're trying to rehandshake and we block on a write during that rehandshake. - * We need to wait on the socket to be writeable but reinitiate the read when it is - */ - case SSL_ERROR_WANT_WRITE: - read_blocked_on_write = true; - break; - default: - return; - break; - } - - /* We need a check for read_blocked here because SSL_pending() doesn't work properly during the - * handshake. This check prevents a busy-wait loop around SSL_read() - */ - } while (SSL_pending(ssl->ssl) && !read_blocked); - } - } - - /* Check for input on the sendq */ - if (obuffer.length() && client_to_server_length == 0) { - memcpy(&client_to_server_buffer, obuffer.data(), obuffer.length() > DPP_BUFSIZE ? DPP_BUFSIZE : obuffer.length()); - client_to_server_length = obuffer.length() > DPP_BUFSIZE ? DPP_BUFSIZE : obuffer.length(); - obuffer = obuffer.substr(client_to_server_length, obuffer.length()); - client_to_server_offset = 0; - } + /* Server name identification (SNI) */ + SSL_set_tlsext_host_name(ssl->ssl, hostname.c_str()); + } - /* If the socket is writeable... */ - if (((pfd[0].revents & POLLOUT) && client_to_server_length) || (write_blocked_on_read && (pfd[0].revents & POLLIN))) { - write_blocked_on_read = false; - /* Try to write */ + /* If this completes, we fall straight through into if (connected) */ + complete_handshake(&e); + } - if (plaintext) { - r = (int) ::send(sfd, client_to_server_buffer + client_to_server_offset, (int)client_to_server_length, 0); + if (connected) { + if (obuffer.length() && client_to_server_length == 0) { + memcpy(&client_to_server_buffer, obuffer.data(), obuffer.length() > DPP_BUFSIZE ? DPP_BUFSIZE : obuffer.length()); + client_to_server_length = obuffer.length() > DPP_BUFSIZE ? DPP_BUFSIZE : obuffer.length(); + obuffer = obuffer.substr(client_to_server_length, obuffer.length()); + client_to_server_offset = 0; + } - if (r < 0) { - /* Write error */ - return; - } + if (plaintext) { + int r = (int) ::send(sfd, client_to_server_buffer + client_to_server_offset, (int)client_to_server_length, 0); + if (r < 0) { + /* Write error */ + this->close(); + return; + } + client_to_server_length -= r; + client_to_server_offset += r; + bytes_out += r; + if (client_to_server_length > 0) { + socket_events se{e}; + se.flags = WANT_READ | WANT_WRITE | WANT_ERROR; + owner->socketengine->update_socket(se); + } + } else if (ssl && ssl->ssl) { + int r = SSL_write(ssl->ssl, client_to_server_buffer + client_to_server_offset, (int)client_to_server_length); + int err = SSL_get_error(ssl->ssl, r); + switch (err) { + /* We wrote something */ + case SSL_ERROR_NONE: client_to_server_length -= r; client_to_server_offset += r; bytes_out += r; - } else { - r = SSL_write(ssl->ssl, client_to_server_buffer + client_to_server_offset, (int)client_to_server_length); - - switch(SSL_get_error(ssl->ssl,r)) { - /* We wrote something */ - case SSL_ERROR_NONE: - client_to_server_length -= r; - client_to_server_offset += r; - bytes_out += r; - break; - - /* We would have blocked */ - case SSL_ERROR_WANT_WRITE: - break; - - /* We get a WANT_READ if we're trying to rehandshake and we block onwrite during the current connection. - * We need to wait on the socket to be readable but reinitiate our write when it is - */ - case SSL_ERROR_WANT_READ: - write_blocked_on_read = true; - break; - - /* Some other error */ - default: - return; - break; + break; + /* We would have blocked */ + case SSL_ERROR_WANT_READ: { + socket_events se{e}; + se.flags = WANT_READ | WANT_ERROR; + owner->socketengine->update_socket(se); + break; + } + case SSL_ERROR_WANT_WRITE: { + socket_events se{e}; + se.flags = WANT_READ | WANT_WRITE | WANT_ERROR; + owner->socketengine->update_socket(se); + break; + } + case SSL_ERROR_SYSCALL: { + if (errno != 0) { + this->close(); } + break; + } + /* Some other error */ + default: { + return; } } } } - catch (const std::exception &e) { - log(ll_warning, std::string("Read loop ended: ") + e.what()); +} + +void ssl_client::on_error(socket fd, const struct socket_events&, int error_code) { + this->close(); +} + +void ssl_client::read_loop() +{ + auto setup_events = [this]() { + dpp::socket_events events( + sfd, + WANT_READ | WANT_WRITE | WANT_ERROR, + [this](socket fd, const struct socket_events &e) { + if (this->sfd == INVALID_SOCKET) { + close_socket(fd); + owner->socketengine->delete_socket(fd); + return; + } + on_read(fd, e); + }, + [this](socket fd, const struct socket_events &e) { + if (this->sfd == INVALID_SOCKET) { + close_socket(fd); + owner->socketengine->delete_socket(fd); + return; + } + on_write(fd, e); + }, + [this](socket fd, const struct socket_events &e, int error_code) { on_error(fd, e, error_code); } + ); + owner->socketengine->register_socket(events); + }; + setup_events(); + if (!timer_handle) { + timer_handle = owner->start_timer([this, setup_events](auto handle) { + one_second_timer(); + if (!tcp_connect_done && time(nullptr) > start + 2 && connect_retries < MAX_RETRIES && sfd != INVALID_SOCKET) { + /* Retry failed connect(). This can happen even in the best situation with bullet-proof hosting. + * Previously with blocking connect() there was some leniency in this, but now we have to do this + * ourselves. + * + * Retry up to 3 times, 2 seconds between retries. After this, give up and let timeout code + * take the wheel (will likely end with an exception). + */ + log(ll_trace, "connect() retry #" + std::to_string(connect_retries + 1)); + close_socket(sfd); + owner->socketengine->delete_socket(sfd); + try { + ssl_client::connect(); + } + catch (const std::exception& e) { + log(ll_trace, "connect() exception: " + std::string(e.what())); + } + setup_events(); + start = time(nullptr) + 2; + connect_retries++; + } + }, 1); } } @@ -633,25 +567,23 @@ bool ssl_client::handle_buffer(std::string &buffer) void ssl_client::close() { - if (keepalive && this->sfd != INVALID_SOCKET) { - std::string identifier((!plaintext ? "ssl://" : "tcp://") + hostname + ":" + port); - auto iter = keepalives.find(identifier); - if (iter == keepalives.end()) { - keepalive_cache_t kc; - kc.created = time(nullptr); - kc.sfd = this->sfd; - kc.ssl = this->ssl; - keepalives.emplace(identifier, kc); + if (!plaintext) { + std::lock_guard lock(ssl_mutex); + if (ssl != nullptr && ssl->ssl != nullptr) { + SSL_free(ssl->ssl); + ssl->ssl = nullptr; } - return; } - - if (!plaintext && ssl->ssl) { - SSL_free(ssl->ssl); - ssl->ssl = nullptr; + connected = tcp_connect_done = false; + client_to_server_length = client_to_server_offset = 0; + last_tick = time(nullptr); + bytes_in = bytes_out = 0; + if (sfd != INVALID_SOCKET) { + log(ll_trace, "ssl_client::close() with sfd"); + owner->socketengine->delete_socket(sfd); + close_socket(sfd); + sfd = INVALID_SOCKET; } - close_socket(sfd); - sfd = INVALID_SOCKET; obuffer.clear(); buffer.clear(); } @@ -659,14 +591,17 @@ void ssl_client::close() void ssl_client::cleanup() { this->close(); - if (!keepalive) { - delete ssl; - } } ssl_client::~ssl_client() { cleanup(); + if (timer_handle) { + owner->stop_timer(timer_handle); + timer_handle = 0; + } + delete ssl; + ssl = nullptr; } } diff --git a/src/dpp/thread_pool.cpp b/src/dpp/thread_pool.cpp new file mode 100644 index 0000000000..c7a64bc176 --- /dev/null +++ b/src/dpp/thread_pool.cpp @@ -0,0 +1,85 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * SPDX-License-Identifier: Apache-2.0 + * 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. + * + ************************************************************************************/ + +#include +#include +#include +#include + +namespace dpp { + +thread_pool::thread_pool(cluster* creator, size_t num_threads) { + for (size_t i = 0; i < num_threads; ++i) { + threads.emplace_back([this, i, creator]() { + dpp::utility::set_thread_name("pool/exec/" + std::to_string(i)); + while (true) { + thread_pool_task task; + { + std::unique_lock lock(queue_mutex); + + cv.wait(lock, [this] { + return !tasks.empty() || stop; + }); + + if (stop && tasks.empty()) { + return; + } + + task = std::move(tasks.top()); + tasks.pop(); + } + + try { + task.function(); + } + catch (const std::exception &e) { + creator->log(ll_warning, "Uncaught exception in thread pool: " + std::string(e.what())); + } + catch (...) { + creator->log(ll_warning, "Uncaught exception in thread pool, but not derived from std::exception!"); + } + } + }); + } +} + +thread_pool::~thread_pool() { + { + std::unique_lock lock(queue_mutex); + stop = true; + } + + cv.notify_all(); + for (auto &thread: threads) { + thread.join(); + } +} + +void thread_pool::enqueue(thread_pool_task task) { + { + std::unique_lock lock(queue_mutex); + tasks.emplace(std::move(task)); + } + cv.notify_one(); +} + +} \ No newline at end of file diff --git a/src/dpp/voice/enabled/cleanup.cpp b/src/dpp/voice/enabled/cleanup.cpp index 5ae1d9c7e2..45288afa8c 100644 --- a/src/dpp/voice/enabled/cleanup.cpp +++ b/src/dpp/voice/enabled/cleanup.cpp @@ -33,17 +33,11 @@ namespace dpp { void discord_voice_client::cleanup() { - if (runner) { - this->terminating = true; - runner->join(); - delete runner; - runner = nullptr; - } - if (encoder) { + if (encoder != nullptr) { opus_encoder_destroy(encoder); encoder = nullptr; } - if (repacketizer) { + if (repacketizer != nullptr) { opus_repacketizer_destroy(repacketizer); repacketizer = nullptr; } @@ -55,6 +49,9 @@ void discord_voice_client::cleanup() voice_courier_shared_state.signal_iteration.notify_one(); voice_courier.join(); } + if (fd != INVALID_SOCKET) { + owner->socketengine->delete_socket(fd); + } } } diff --git a/src/dpp/voice/enabled/constructor.cpp b/src/dpp/voice/enabled/constructor.cpp index fc8e16d0d5..54f33753ae 100644 --- a/src/dpp/voice/enabled/constructor.cpp +++ b/src/dpp/voice/enabled/constructor.cpp @@ -33,14 +33,15 @@ namespace dpp { discord_voice_client::discord_voice_client(dpp::cluster* _cluster, snowflake _channel_id, snowflake _server_id, const std::string &_token, const std::string &_session_id, const std::string &_host, bool enable_dave) - : websocket_client(_host.substr(0, _host.find(':')), _host.substr(_host.find(':') + 1, _host.length()), "/?v=" + std::to_string(voice_protocol_version), OP_TEXT), - runner(nullptr), + : websocket_client(_cluster, _host.substr(0, _host.find(':')), _host.substr(_host.find(':') + 1, _host.length()), "/?v=" + std::to_string(voice_protocol_version), OP_TEXT), connect_time(0), mixer(std::make_unique()), port(0), ssrc(0), timescale(1000000), paused(false), + sent_stop_frames(false), + last_loop_time(time(nullptr)), encoder(nullptr), repacketizer(nullptr), fd(INVALID_SOCKET), @@ -60,6 +61,11 @@ discord_voice_client::discord_voice_client(dpp::cluster* _cluster, snowflake _ch sessionid(_session_id), server_id(_server_id), channel_id(_channel_id) +{ + setup(); +} + +void discord_voice_client::setup() { int opusError = 0; encoder = opus_encoder_create(opus_sample_rate_hz, opus_channel_count, OPUS_APPLICATION_VOIP, &opusError); diff --git a/src/dpp/voice/enabled/courier_loop.cpp b/src/dpp/voice/enabled/courier_loop.cpp index 72ccccdbfc..a8d8f61c0a 100644 --- a/src/dpp/voice/enabled/courier_loop.cpp +++ b/src/dpp/voice/enabled/courier_loop.cpp @@ -146,7 +146,7 @@ void discord_voice_client::voice_courier_loop(discord_voice_client& client, cour * Since this sample comes from a lost packet, * we can only pretend there is an event, without any raw payload byte. */ - voice_receive_t vr(nullptr, "", &client, d.user_id, + voice_receive_t vr(client.creator, 0, "", &client, d.user_id, reinterpret_cast(flush_data_pcm), lost_packet_samples * opus_channel_count * sizeof(opus_int16)); @@ -282,7 +282,7 @@ void discord_voice_client::voice_courier_loop(discord_voice_client& client, cour pcm_downsample_ptr += client.mixer->byte_blocks_per_register; } - voice_receive_t vr(nullptr, "", &client, 0, reinterpret_cast(pcm_downsample), + voice_receive_t vr(client.owner, 0, "", &client, 0, reinterpret_cast(pcm_downsample), max_samples * opus_channel_count * sizeof(opus_int16)); client.creator->on_voice_receive_combined.call(vr); diff --git a/src/dpp/voice/enabled/discover_ip.cpp b/src/dpp/voice/enabled/discover_ip.cpp index 7061c20f08..96ab091a13 100644 --- a/src/dpp/voice/enabled/discover_ip.cpp +++ b/src/dpp/voice/enabled/discover_ip.cpp @@ -148,7 +148,11 @@ std::string discord_voice_client::discover_ip() { return ""; } address_t bind_port(this->ip, this->port); +#ifndef _WIN32 if (::connect(socket.fd, bind_port.get_socket_address(), bind_port.size()) < 0) { +#else + if (WSAConnect(socket.fd, bind_port.get_socket_address(), bind_port.size(), nullptr, nullptr, nullptr, nullptr) < 0) { +#endif log(ll_warning, "Could not connect socket for IP discovery"); return ""; } diff --git a/src/dpp/voice/enabled/handle_frame.cpp b/src/dpp/voice/enabled/handle_frame.cpp index 2a8faca93c..69f879bd92 100644 --- a/src/dpp/voice/enabled/handle_frame.cpp +++ b/src/dpp/voice/enabled/handle_frame.cpp @@ -204,11 +204,13 @@ bool discord_voice_client::handle_frame(const std::string &data, ws_opcode opcod } break; case voice_client_platform: { - voice_client_platform_t vcp(nullptr, data); + voice_client_platform_t vcp(owner, 0, data); vcp.voice_client = this; vcp.user_id = snowflake_not_null(&j["d"], "user_id"); vcp.platform = static_cast(int8_not_null(&j["d"], "platform")); - creator->on_voice_client_platform.call(vcp); + creator->queue_work(0, [this, vcp]() { + creator->on_voice_client_platform.call(vcp); + }); } break; case voice_opcode_multiple_clients_connect: { @@ -300,10 +302,12 @@ bool discord_voice_client::handle_frame(const std::string &data, ws_opcode opcod dave_mls_pending_remove_list.insert(u_id); if (!creator->on_voice_client_disconnect.empty()) { - voice_client_disconnect_t vcd(nullptr, data); + voice_client_disconnect_t vcd(owner, 0, data); vcd.voice_client = this; vcd.user_id = u_id; - creator->on_voice_client_disconnect.call(vcd); + creator->queue_work(0, [this, vcd]() { + creator->on_voice_client_disconnect.call(vcd); + }); } } } @@ -318,11 +322,13 @@ bool discord_voice_client::handle_frame(const std::string &data, ws_opcode opcod ssrc_map[u_ssrc] = u_id; if (!creator->on_voice_client_speaking.empty()) { - voice_client_speaking_t vcs(nullptr, data); + voice_client_speaking_t vcs(owner, 0, data); vcs.voice_client = this; vcs.user_id = u_id; vcs.ssrc = u_ssrc; - creator->on_voice_client_speaking.call(vcs); + creator->queue_work(0, [this, vcs]() { + creator->on_voice_client_speaking.call(vcs); + }); } } } @@ -416,10 +422,12 @@ bool discord_voice_client::handle_frame(const std::string &data, ws_opcode opcod send_silence(20); /* Fire on_voice_ready */ if (!creator->on_voice_ready.empty()) { - voice_ready_t rdy(nullptr, data); + voice_ready_t rdy(owner, 0, data); rdy.voice_client = this; rdy.voice_channel_id = this->channel_id; - creator->on_voice_ready.call(rdy); + creator->queue_work(0, [this, rdy]() { + creator->on_voice_ready.call(rdy); + }); } } } @@ -453,10 +461,17 @@ bool discord_voice_client::handle_frame(const std::string &data, ws_opcode opcod /* Hook poll() in the ssl_client to add a new file descriptor */ this->fd = newfd; - this->custom_writeable_fd = [this] { return want_write(); }; - this->custom_readable_fd = [this] { return want_read(); }; - this->custom_writeable_ready = [this] { write_ready(); }; - this->custom_readable_ready = [this] { read_ready(); }; + + udp_events = dpp::socket_events( + fd, + WANT_READ | WANT_WRITE | WANT_ERROR, + [this](socket, const struct socket_events &e) { read_ready(); }, + [this](socket, const struct socket_events &e) { write_ready(); }, + [this](socket, const struct socket_events &e, int error_code) { + this->close(); + } + ); + owner->socketengine->register_socket(udp_events); int bound_port = address_t().get_port(this->fd); this->write(json({ @@ -518,10 +533,12 @@ void discord_voice_client::ready_for_transition(const std::string &data) { mls_state->done_ready = true; if (!creator->on_voice_ready.empty()) { - voice_ready_t rdy(nullptr, data); + voice_ready_t rdy(owner, 0, data); rdy.voice_client = this; rdy.voice_channel_id = this->channel_id; - creator->on_voice_ready.call(rdy); + creator->queue_work(0, [this, rdy]() { + creator->on_voice_ready.call(rdy); + }); } } } diff --git a/src/dpp/voice/enabled/read_ready.cpp b/src/dpp/voice/enabled/read_ready.cpp index 03c79b0486..e499de3f5d 100644 --- a/src/dpp/voice/enabled/read_ready.cpp +++ b/src/dpp/voice/enabled/read_ready.cpp @@ -68,7 +68,7 @@ void discord_voice_client::read_ready() voice_payload vp{0, // seq, populate later 0, // timestamp, populate later - std::make_unique(nullptr, std::string(reinterpret_cast(buffer), packet_size))}; + std::make_unique(owner, 0, std::string(reinterpret_cast(buffer), packet_size))}; vp.vr->voice_client = this; diff --git a/src/dpp/voice/enabled/read_write.cpp b/src/dpp/voice/enabled/read_write.cpp index 52a09d5a39..fd41423796 100644 --- a/src/dpp/voice/enabled/read_write.cpp +++ b/src/dpp/voice/enabled/read_write.cpp @@ -28,20 +28,6 @@ namespace dpp { -dpp::socket discord_voice_client::want_write() { - std::lock_guard lock(this->stream_mutex); - if (!this->sent_stop_frames && !outbuf.empty()) { - return fd; - } - return INVALID_SOCKET; - -} - -dpp::socket discord_voice_client::want_read() { - return fd; -} - - void discord_voice_client::send(const char* packet, size_t len, uint64_t duration, bool send_now) { if (!send_now) [[likely]] { voice_out_packet frame; diff --git a/src/dpp/voice/enabled/thread.cpp b/src/dpp/voice/enabled/thread.cpp index 0082eb0a72..135d1cc3fe 100644 --- a/src/dpp/voice/enabled/thread.cpp +++ b/src/dpp/voice/enabled/thread.cpp @@ -20,7 +20,6 @@ * ************************************************************************************/ -#include #include #include #include @@ -29,59 +28,48 @@ namespace dpp { -void discord_voice_client::thread_run() -{ - utility::set_thread_name(std::string("vc/") + std::to_string(server_id)); +void discord_voice_client::on_disconnect() { - size_t times_looped = 0; - time_t last_loop_time = time(nullptr); + time_t current_time = time(nullptr); - do { - bool error = false; - ssl_client::read_loop(); - ssl_client::close(); + /* Here, we check if it's been longer than 3 seconds since the previous loop, + * this gives us time to see if it's an actual disconnect, or an error. + * This will prevent us from looping too much, meaning error codes do not cause an infinite loop. + */ + if (current_time - last_loop_time >= 3) { + times_looped = 0; + } - time_t current_time = time(nullptr); - /* Here, we check if it's been longer than 3 seconds since the previous loop, - * this gives us time to see if it's an actual disconnect, or an error. - * This will prevent us from looping too much, meaning error codes do not cause an infinite loop. - */ - if (current_time - last_loop_time >= 3) - times_looped = 0; + /* This does mean we'll always have times_looped at a minimum of 1, this is intended. */ + times_looped++; - /* This does mean we'll always have times_looped at a minimum of 1, this is intended. */ - times_looped++; - /* If we've looped 5 or more times, abort the loop. */ - if (times_looped >= 5) { - log(dpp::ll_warning, "Reached max loops whilst attempting to read from the websocket. Aborting websocket."); - break; - } - - last_loop_time = current_time; + /* If we've looped 5 or more times, abort the loop. */ + if (terminating || times_looped >= 5) { + log(dpp::ll_warning, "Reached max loops whilst attempting to read from the websocket. Aborting websocket."); + return; + } + last_loop_time = current_time; - if (!terminating) { - log(dpp::ll_debug, "Attempting to reconnect the websocket..."); - do { - try { - ssl_client::connect(); - websocket_client::connect(); - } - catch (const std::exception &e) { - log(dpp::ll_error, std::string("Error establishing voice websocket connection, retry in 5 seconds: ") + e.what()); - ssl_client::close(); - std::this_thread::sleep_for(std::chrono::seconds(5)); - error = true; - } - } while (error && !terminating); + ssl_client::close(); + owner->start_timer([this](auto handle) { + log(dpp::ll_debug, "Attempting to reconnect voice websocket " + std::to_string(channel_id) + " to wss://" + hostname + "..."); + owner->stop_timer(handle); + cleanup(); + if (timer_handle) { + owner->stop_timer(timer_handle); + timer_handle = 0; } - } while(!terminating); + start = time(nullptr); + setup(); + terminating = false; + ssl_client::connect(); + websocket_client::connect(); + run(); + }, 1); } -void discord_voice_client::run() -{ - this->runner = new std::thread(&discord_voice_client::thread_run, this); - this->thread_id = runner->native_handle(); +void discord_voice_client::run() { + ssl_client::read_loop(); } - -} +} \ No newline at end of file diff --git a/src/dpp/voice/enabled/write_ready.cpp b/src/dpp/voice/enabled/write_ready.cpp index 8287dea2a3..e2b78ef8c8 100644 --- a/src/dpp/voice/enabled/write_ready.cpp +++ b/src/dpp/voice/enabled/write_ready.cpp @@ -31,6 +31,13 @@ namespace dpp { void discord_voice_client::write_ready() { + /* + * WANT_WRITE has been reset everytime this method is being called, + * ALWAYS set it again no matter what we're gonna do. + */ + udp_events.flags = WANT_READ | WANT_WRITE | WANT_ERROR; + owner->socketengine->update_socket(udp_events); + uint64_t duration = 0; bool track_marker_found = false; uint64_t bufsize = 0; @@ -54,7 +61,8 @@ void discord_voice_client::write_ready() { } } if (!outbuf.empty()) { - if (this->udp_send(outbuf[0].packet.data(), outbuf[0].packet.length()) == (int)outbuf[0].packet.length()) { + int sent_siz = this->udp_send(outbuf[0].packet.data(), outbuf[0].packet.length()); + if (sent_siz == (int)outbuf[0].packet.length()) { duration = outbuf[0].duration * timescale; bufsize = outbuf[0].packet.length(); outbuf.erase(outbuf.begin()); @@ -95,16 +103,18 @@ void discord_voice_client::write_ready() { last_timestamp = std::chrono::high_resolution_clock::now(); if (!creator->on_voice_buffer_send.empty()) { - voice_buffer_send_t snd(nullptr, ""); + voice_buffer_send_t snd(owner, 0, ""); snd.buffer_size = bufsize; snd.packets_left = outbuf.size(); snd.voice_client = this; - creator->on_voice_buffer_send.call(snd); + creator->queue_work(-1, [this, snd]() { + creator->on_voice_buffer_send.call(snd); + }); } } if (track_marker_found) { if (!creator->on_voice_track_marker.empty()) { - voice_track_marker_t vtm(nullptr, ""); + voice_track_marker_t vtm(owner, 0, ""); vtm.voice_client = this; { std::lock_guard lock(this->stream_mutex); @@ -113,10 +123,12 @@ void discord_voice_client::write_ready() { track_meta.erase(track_meta.begin()); } } - creator->on_voice_track_marker.call(vtm); + creator->queue_work(-1, [this, vtm]() { + creator->on_voice_track_marker.call(vtm); + }); + } } } - } diff --git a/src/dpp/voice/stub/stubs.cpp b/src/dpp/voice/stub/stubs.cpp index c027aa8b43..f85a90a9d7 100644 --- a/src/dpp/voice/stub/stubs.cpp +++ b/src/dpp/voice/stub/stubs.cpp @@ -28,7 +28,7 @@ namespace dpp { discord_voice_client::discord_voice_client(dpp::cluster* _cluster, snowflake _channel_id, snowflake _server_id, const std::string &_token, const std::string &_session_id, const std::string &_host, bool enable_dave) - : websocket_client(_host.substr(0, _host.find(':')), _host.substr(_host.find(':') + 1, _host.length()), "/?v=" + std::to_string(voice_protocol_version), OP_TEXT) + : websocket_client(_cluster, _host.substr(0, _host.find(':')), _host.substr(_host.find(':') + 1, _host.length()), "/?v=" + std::to_string(voice_protocol_version), OP_TEXT) { throw dpp::voice_exception(err_no_voice_support, "Voice support not enabled in this build of D++"); } @@ -36,15 +36,12 @@ namespace dpp { void discord_voice_client::voice_courier_loop(discord_voice_client& client, courier_shared_state_t& shared_state) { } - void discord_voice_client::cleanup(){ + void discord_voice_client::cleanup() { } void discord_voice_client::run() { } - void discord_voice_client::thread_run() { - } - bool discord_voice_client::voice_payload::operator<(const voice_payload& other) const { return false; } @@ -71,15 +68,6 @@ namespace dpp { return *this; } - dpp::socket discord_voice_client::want_write() { - return INVALID_SOCKET; - } - - dpp::socket discord_voice_client::want_read() { - return INVALID_SOCKET; - } - - void discord_voice_client::send(const char* packet, size_t len, uint64_t duration, bool send_now) { } @@ -99,4 +87,10 @@ namespace dpp { return ""; } + void discord_voice_client::setup() { + } + + void discord_voice_client::on_disconnect() { + } + } diff --git a/src/dpp/wsclient.cpp b/src/dpp/wsclient.cpp index 618c7e7282..2180ec5f5b 100644 --- a/src/dpp/wsclient.cpp +++ b/src/dpp/wsclient.cpp @@ -26,6 +26,7 @@ #include #include #include +#include namespace dpp { @@ -37,11 +38,13 @@ constexpr size_t WS_MAX_PAYLOAD_LENGTH_SMALL = 125; constexpr size_t WS_MAX_PAYLOAD_LENGTH_LARGE = 65535; constexpr size_t MAXHEADERSIZE = sizeof(uint64_t) + 2; -websocket_client::websocket_client(const std::string& hostname, const std::string& port, const std::string& urlpath, ws_opcode opcode) - : ssl_client(hostname, port), +websocket_client::websocket_client(cluster* creator, const std::string& hostname, const std::string& port, const std::string& urlpath, ws_opcode opcode) + : ssl_client(creator, hostname, port), state(HTTP_HEADERS), path(urlpath), - data_opcode(opcode) + data_opcode(opcode), + timed_out(false), + timeout(time(nullptr) + 5) { uint64_t k = (time(nullptr) * time(nullptr)); /* A 64 bit value as hex with leading zeroes is always 16 chars. @@ -128,6 +131,23 @@ void websocket_client::write(const std::string_view data, ws_opcode _opcode) ssl_client::socket_write(header); ssl_client::socket_write(data); } + + bool should_append_want_write = false; + socket_events *new_se = nullptr; + { + std::lock_guard lk(owner->socketengine->fds_mutex); + auto i = owner->socketengine->fds.find(sfd); + + should_append_want_write = i != owner->socketengine->fds.end() && (i->second->flags & WANT_WRITE) != WANT_WRITE; + if (should_append_want_write) { + new_se = i->second.get(); + new_se->flags |= WANT_WRITE; + } + } + + if (should_append_want_write) { + owner->socketengine->update_socket(*new_se); + } } bool websocket_client::handle_buffer(std::string& buffer) @@ -180,7 +200,13 @@ bool websocket_client::handle_buffer(std::string& buffer) } } else if (state == CONNECTED) { /* Process packets until we can't (buffer will erase data until parseheader returns false) */ - while (this->parseheader(buffer)) { } + try { + while (this->parseheader(buffer)) { } + } + catch (const std::exception &e) { + log(ll_debug, "Receiving exception: " + std::string(e.what())); + return false; + } } return true; @@ -254,7 +280,9 @@ bool websocket_client::parseheader(std::string& data) handle_ping(data.substr(payloadstartoffset, len)); } else if ((opcode & ~WS_FINBIT) != OP_PONG) { /* Otherwise, handle everything else apart from a PONG. */ /* Pass this frame to the deriving class */ - this->handle_frame(data.substr(payloadstartoffset, len), static_cast(opcode & ~WS_FINBIT)); + if (!this->handle_frame(data.substr(payloadstartoffset, len), static_cast(opcode & ~WS_FINBIT))) { + return false; + } } /* Remove this frame from the input buffer */ @@ -285,7 +313,9 @@ bool websocket_client::parseheader(std::string& data) void websocket_client::one_second_timer() { - if (((time(nullptr) % 20) == 0) && (state == CONNECTED)) { + time_t now = time(nullptr); + + if (((now % 20) == 0) && (state == CONNECTED)) { /* For sending pings, we send with payload */ unsigned char out[MAXHEADERSIZE]; std::string payload = "keepalive"; @@ -294,6 +324,23 @@ void websocket_client::one_second_timer() ssl_client::socket_write(header); ssl_client::socket_write(payload); } + + /* Handle timeouts for connect(), SSL negotiation and HTTP negotiation */ + if (!timed_out && sfd != INVALID_SOCKET) { + if (!tcp_connect_done && now >= timeout) { + log(ll_trace, "Websocket connection timed out: connect()"); + timed_out = true; + this->close(); + } else if (tcp_connect_done && !connected && now >= timeout && this->state != CONNECTED) { + log(ll_trace, "Websocket connection timed out: SSL handshake"); + timed_out = true; + this->close(); + } else if (now >= timeout && this->state != CONNECTED) { + log(ll_trace, "Websocket connection timed out: HTTP negotiation"); + timed_out = true; + this->close(); + } + } } void websocket_client::handle_ping(const std::string &payload) @@ -325,8 +372,14 @@ void websocket_client::error(uint32_t errorcode) { } +void websocket_client::on_disconnect() +{ +} + void websocket_client::close() { + log(ll_trace, "websocket_client::close()"); + this->on_disconnect(); this->state = HTTP_HEADERS; ssl_client::close(); } diff --git a/src/dpp/zlibcontext.cpp b/src/dpp/zlibcontext.cpp new file mode 100644 index 0000000000..7dee967276 --- /dev/null +++ b/src/dpp/zlibcontext.cpp @@ -0,0 +1,73 @@ +/************************************************************************************ + * + * 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. + * + ************************************************************************************/ +#include +#include +#include +#include + +namespace dpp { + +zlibcontext::zlibcontext() : d_stream(new z_stream()) { + std::memset(d_stream, 0, sizeof(z_stream)); + int error = inflateInit(d_stream); + if (error != Z_OK) { + delete d_stream; + throw dpp::connection_exception((exception_error_code)error, "Can't initialise stream compression!"); + } + decomp_buffer.resize(DECOMP_BUFFER_SIZE); +} + +zlibcontext::~zlibcontext() { + inflateEnd(d_stream); + delete d_stream; +} + +exception_error_code zlibcontext::decompress(const std::string& buffer, std::string& decompressed) { + decompressed.clear(); + /* This is safe; zlib requires us to cast away the const. The underlying buffer is unchanged. */ + d_stream->next_in = reinterpret_cast(const_cast(buffer.data())); + d_stream->avail_in = static_cast(buffer.size()); + do { + d_stream->next_out = static_cast(decomp_buffer.data()); + d_stream->avail_out = DECOMP_BUFFER_SIZE; + int ret = inflate(d_stream, Z_NO_FLUSH); + size_t have = DECOMP_BUFFER_SIZE - d_stream->avail_out; + switch (ret) { + case Z_NEED_DICT: + case Z_STREAM_ERROR: + return err_compression_stream; + case Z_DATA_ERROR: + return err_compression_data; + case Z_MEM_ERROR: + return err_compression_memory; + case Z_OK: + decompressed.append(decomp_buffer.begin(), decomp_buffer.begin() + have); + decompressed_total += have; + break; + default: + /* Stub */ + break; + } + } while (d_stream->avail_out == 0); + return err_no_code_specified; +} + +}; \ No newline at end of file diff --git a/src/soaktest/soak.cpp b/src/soaktest/soak.cpp index 97195f21fd..fecebdc853 100644 --- a/src/soaktest/soak.cpp +++ b/src/soaktest/soak.cpp @@ -23,21 +23,43 @@ #include #include #include -#include +#include +#ifndef _WIN32 + #include +#endif + +dpp::cluster* s{nullptr}; +std::atomic_bool signalled{false}; int main() { using namespace std::chrono_literals; char* t = getenv("DPP_UNIT_TEST_TOKEN"); if (t) { - dpp::cluster soak_test(t, dpp::i_default_intents | dpp::i_guild_members); - soak_test.set_websocket_protocol(dpp::ws_etf); - soak_test.on_log(dpp::utility::cout_logger()); + dpp::cluster soak_test(t, dpp::i_default_intents | dpp::i_guild_members, 1, 0, 1); + s = &soak_test; + //soak_test.set_websocket_protocol(dpp::ws_etf); + soak_test.on_log([&](const dpp::log_t& log) { + std::cout << "[" << dpp::utility::current_date_time() << "] " << dpp::utility::loglevel(log.severity) << ": " << log.message << std::endl; + }); soak_test.start(dpp::st_return); + +#ifndef _WIN32 + signal(SIGUSR1, [](int sig) { + signalled = true; + }); +#endif + while (true) { - std::this_thread::sleep_for(60s); + std::this_thread::sleep_for(1s); dpp::discord_client* dc = soak_test.get_shard(0); if (dc != nullptr) { - std::cout << "Websocket latency: " << std::fixed << dc->websocket_ping << " Guilds: " << dpp::get_guild_count() << " Users: " << dpp::get_user_count() << "\n"; + if (time(nullptr) % 60 == 0) { + std::cout << "Websocket latency: " << std::fixed << dc->websocket_ping << " Guilds: " << dpp::get_guild_count() << " Users: " << dpp::get_user_count() << "\n"; + } + if (signalled) { + signalled = false; + dc->close(); + } } } } diff --git a/src/sockettest/socket.cpp b/src/sockettest/socket.cpp new file mode 100644 index 0000000000..057b7b112e --- /dev/null +++ b/src/sockettest/socket.cpp @@ -0,0 +1,95 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * SPDX-License-Identifier: Apache-2.0 + * 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. + * + ************************************************************************************/ + +#include +#include +#include +#include +#ifndef _WIN32 + #include +#else + /* Windows-specific sockets includes */ + #include + #include + #include + /* Windows sockets library */ + #pragma comment(lib, "ws2_32") +#endif + +int main() { + dpp::cluster cl("no-token"); + auto se = dpp::create_socket_engine(&cl); + + const dpp::dns_cache_entry* addr = dpp::resolve_hostname("neuron.brainbox.cc", "80"); + std::cout << "Connect to IP: " << addr->resolved_addr << "\n"; + dpp::socket sfd = addr->make_connecting_socket(); + dpp::address_t destination = addr->get_connecting_address(80); + if (sfd == INVALID_SOCKET) { + std::cerr << "Couldn't create outbound socket on port 80\n"; + exit(1); +#ifndef _WIN32 + } else if (::connect(sfd, destination.get_socket_address(), destination.size()) != 0) { +#else + } else if (::WSAConnect(sfd, destination.get_socket_address(), destination.size(), nullptr, nullptr, nullptr, nullptr) != 0) { +#endif + dpp::close_socket(sfd); + std::cerr << "Couldn't connect outbound socket on port 80\n"; + exit(1); + } + + dpp::socket_events events( + sfd, + dpp::WANT_READ | dpp::WANT_WRITE | dpp::WANT_ERROR, + [&se](dpp::socket fd, const struct dpp::socket_events& e) { + int r = 0; + do { + char buf[128]{0}; + r = ::recv(e.fd, buf, sizeof(buf), 0); + if (r > 0) { + buf[127] = 0; + std::cout << buf; + std::cout.flush(); + } + } while (r > 0); + if (r == 0 || (errno && errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR)) { + dpp::close_socket(fd); + se->delete_socket(fd); + } + }, + [](dpp::socket fd, const struct dpp::socket_events& e) { + std::cout << "WANT_WRITE event on socket " << fd << "\n"; + constexpr std::string_view request{"GET / HTTP/1.0\r\nConnection: close\r\n\r\n"}; + std::cout << "Writing: " << request.data() << "\n"; + auto written = ::send(e.fd, request.data(), request.length(), 0); + std::cout << "Written: " << written << "\n"; + }, + [](dpp::socket fd, const struct dpp::socket_events&, int error_code) { + std::cout << "WANT_ERROR event on socket " << fd << " with code " << error_code << "\n"; + } + ); + + se->register_socket(events); + + do { + se->process_events(); + } while (true); +} diff --git a/src/unittest/coro.cpp b/src/unittest/coro.cpp index eead3c85b6..8ec42d89d2 100644 --- a/src/unittest/coro.cpp +++ b/src/unittest/coro.cpp @@ -523,7 +523,7 @@ void coro_offline_tests() void event_handler_test(dpp::cluster *bot) { bot->on_message_create([](dpp::message_create_t event) -> dpp::task { if (event.msg.content == "coro test") { - dpp::cluster *bot = event.from->creator; + dpp::cluster *bot = event.owner; set_status(CORO_EVENT_HANDLER, ts_success); start_test(CORO_API_CALLS); diff --git a/src/unittest/test.cpp b/src/unittest/test.cpp index b85b9386ab..4d5d308ee0 100644 --- a/src/unittest/test.cpp +++ b/src/unittest/test.cpp @@ -26,6 +26,11 @@ #include #include +/** + * @brief global lock for log output + */ +std::mutex loglock; + /** * @brief Type trait to check if a certain type has a build_json method * @@ -282,53 +287,6 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b set_test(HOSTINFO, hci_test); - set_test(HTTPS, false); - if (!offline) { - dpp::multipart_content multipart = dpp::https_client::build_multipart( - "{\"content\":\"test\"}", {"test.txt", "blob.blob"}, {"ABCDEFGHI", "BLOB!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"}, {"text/plain", "application/octet-stream"} - ); - try { - dpp::https_client c("discord.com", 443, "/api/channels/" + std::to_string(TEST_TEXT_CHANNEL_ID) + "/messages", "POST", multipart.body, - { - {"Content-Type", multipart.mimetype}, - {"Authorization", "Bot " + token} - } - ); - std::string hdr1 = c.get_header("server"); - std::string content1 = c.get_content(); - set_test(HTTPS, hdr1 == "cloudflare" && c.get_status() == 200); - } - catch (const dpp::exception& e) { - std::cout << e.what() << "\n"; - set_test(HTTPS, false); - } - } - - set_test(HTTP, false); - try { - dpp::https_client c2("github.com", 80, "/", "GET", "", {}, true); - std::string hdr2 = c2.get_header("location"); - std::string content2 = c2.get_content(); - set_test(HTTP, hdr2 == "https://github.com/" && c2.get_status() == 301); - } - catch (const dpp::exception& e) { - std::cout << e.what() << "\n"; - set_test(HTTP, false); - } - - set_test(MULTIHEADER, false); - try { - dpp::https_client c2("dl.dpp.dev", 443, "/cookietest.php", "GET", "", {}); - size_t count = c2.get_header_count("set-cookie"); - size_t count_list = c2.get_header_list("set-cookie").size(); - // Google sets a bunch of cookies when we start accessing it. - set_test(MULTIHEADER, c2.get_status() == 200 && count > 1 && count == count_list); - } - catch (const dpp::exception& e) { - std::cout << e.what() << "\n"; - set_test(MULTIHEADER, false); - } - std::vector testaudio = load_test_audio(); set_test(READFILE, false); @@ -453,7 +411,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b // create a fake interaction dpp::cluster cluster(""); dpp::discord_client client(&cluster, 1, 1, ""); - dpp::interaction_create_t interaction(&client, ""); + dpp::interaction_create_t interaction(nullptr, 0, ""); /* Check the method with subcommands */ set_test(GET_PARAMETER_WITH_SUBCOMMANDS, false); @@ -973,6 +931,9 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b coro_offline_tests(); } + std::promise ready_promise; + std::future ready_future = ready_promise.get_future(); + std::vector dpp_logo = load_data("DPP-Logo.png"); set_test(PRESENCE, false); @@ -1018,9 +979,6 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b /* This ensures we test both protocols, as voice is json and shard is etf */ bot.set_websocket_protocol(dpp::ws_etf); - bot.on_form_submit([&](const dpp::form_submit_t & event) { - }); - /* This is near impossible to test without a 'clean room' voice channel. * We attach this event just so that the decoder events are fired while we * are sending audio later, this way if the audio receive code is plain unstable @@ -1029,7 +987,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b bot.on_voice_receive_combined([&](const auto& event) { }); - bot.on_guild_create([&](const dpp::guild_create_t& event) { + bot.on_guild_create([dpp_logo,&bot](const dpp::guild_create_t& event) { dpp::guild *g = event.created; if (g->id == TEST_GUILD_ID) { @@ -1063,9 +1021,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b } }); - std::promise ready_promise; - std::future ready_future = ready_promise.get_future(); - bot.on_ready([&](const dpp::ready_t & event) { + bot.on_ready([&ready_promise,&bot,dpp_logo](const dpp::ready_t & event) { set_test(CONNECTION, true); ready_promise.set_value(); @@ -1123,10 +1079,9 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b }); }); - std::mutex loglock; - bot.on_log([&](const dpp::log_t & event) { - std::lock_guard locker(loglock); + bot.on_log([](const dpp::log_t & event) { if (event.severity > dpp::ll_trace) { + std::lock_guard locker(loglock); std::cout << "[" << std::fixed << std::setprecision(3) << (dpp::utility::time_f() - get_start_time()) << "]: [\u001b[36m" << dpp::utility::loglevel(event.severity) << "\u001b[0m] " << event.message << "\n"; } if (event.message == "Test log message") { @@ -1143,7 +1098,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b } set_test(RUNONCE, (runs == 1)); - bot.on_voice_ready([&](const dpp::voice_ready_t & event) { + bot.on_voice_ready([&testaudio](const dpp::voice_ready_t & event) { set_test(VOICECONN, true); dpp::discord_voice_client* v = event.voice_client; set_test(VOICESEND, false); @@ -1168,7 +1123,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b } }); - bot.on_voice_buffer_send([&](const dpp::voice_buffer_send_t & event) { + bot.on_voice_buffer_send([](const dpp::voice_buffer_send_t & event) { static bool sent_some_data = false; if (event.buffer_size > 0) { @@ -1180,13 +1135,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b } }); - set_test(SYNC, false); - if (!offline) { - dpp::message m = dpp::sync(&bot, &dpp::cluster::message_create, dpp::message(TEST_TEXT_CHANNEL_ID, "TEST")); - set_test(SYNC, m.content == "TEST"); - } - - bot.on_guild_create([&](const dpp::guild_create_t & event) { + bot.on_guild_create([&bot](const dpp::guild_create_t & event) { if (event.created->id == TEST_GUILD_ID) { set_test(GUILDCREATE, true); if (event.presences.size() && event.presences.begin()->second.user_id > 0) { @@ -1223,27 +1172,31 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b if (files_tested == std::array{true, true, true} && pin_tested && thread_tested) { set_test(MESSAGEDELETE, false); bot.message_delete(message_id, channel_id, [](const dpp::confirmation_callback_t &callback) { - if (!callback.is_error()) { - set_test(MESSAGEDELETE, true); - } + set_test(MESSAGEDELETE, !callback.is_error()); }); } } void set_pin_tested() { - assert(!pin_tested); + if (pin_tested) { + return; + } pin_tested = true; delete_message_if_done(); } void set_thread_tested() { - assert(!thread_tested); + if (thread_tested) { + return; + } thread_tested = true; delete_message_if_done(); } void set_file_tested(size_t index) { - assert(!files_tested[index]); + if (files_tested[index]) { + return; + } files_tested[index] = true; if (files_tested == std::array{true, true, true}) { set_test(MESSAGEFILE, files_success == std::array{true, true, true}); @@ -1285,21 +1238,21 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b return false; } }; - message.attachments[0].download([&](const dpp::http_request_completion_t &callback) { + message.attachments[0].download([this](const dpp::http_request_completion_t &callback) { std::lock_guard lock(mutex); if (callback.status == 200 && callback.body == "test") { files_success[0] = true; } set_file_tested(0); }); - message.attachments[1].download([&](const dpp::http_request_completion_t &callback) { + message.attachments[1].download([this](const dpp::http_request_completion_t &callback) { std::lock_guard lock(mutex); if (callback.status == 200 && check_mimetype(callback.headers, "text/plain") && callback.body == "test") { files_success[1] = true; } set_file_tested(1); }); - message.attachments[2].download([&](const dpp::http_request_completion_t &callback) { + message.attachments[2].download([this](const dpp::http_request_completion_t &callback) { std::lock_guard lock(mutex); // do not check the contents here because discord can change compression if (callback.status == 200 && check_mimetype(callback.headers, "image/png")) { @@ -1649,7 +1602,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b thread_test_helper thread_helper(bot); - bot.on_thread_create([&](const dpp::thread_create_t &event) { + bot.on_thread_create([&thread_helper](const dpp::thread_create_t &event) { if (event.created.name == "thread test") { set_test(THREAD_CREATE_EVENT, true); thread_helper.run(event.created); @@ -1657,7 +1610,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b }); bool message_tested = false; - bot.on_message_create([&](const dpp::message_create_t & event) { + bot.on_message_create([&message_tested,&bot,&message_helper,&thread_helper](const dpp::message_create_t & event) { if (event.msg.author.id == bot.me.id) { if (event.msg.content == "test message" && !message_tested) { message_tested = true; @@ -1707,7 +1660,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b } }); - bot.on_message_reaction_add([&](const dpp::message_reaction_add_t & event) { + bot.on_message_reaction_add([&bot,&thread_helper](const dpp::message_reaction_add_t & event) { if (event.reacting_user.id == bot.me.id) { if (event.reacting_emoji.name == "😄") { set_test(REACTEVENT, true); @@ -1719,7 +1672,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b } }); - bot.on_message_reaction_remove([&](const dpp::message_reaction_remove_t & event) { + bot.on_message_reaction_remove([&bot,&thread_helper](const dpp::message_reaction_remove_t & event) { if (event.reacting_user_id == bot.me.id) { if (event.channel_id == thread_helper.thread_id && event.reacting_emoji.name == dpp::unicode_emoji::thread) { set_test(THREAD_MESSAGE_REACT_REMOVE_EVENT, true); @@ -1728,7 +1681,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b } }); - bot.on_message_delete([&](const dpp::message_delete_t & event) { + bot.on_message_delete([&thread_helper](const dpp::message_delete_t & event) { if (event.channel_id == thread_helper.thread_id) { set_test(THREAD_MESSAGE_DELETE_EVENT, true); thread_helper.notify_event_tested(thread_test_helper::MESSAGE_DELETE); @@ -1736,7 +1689,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b }); bool message_edit_tested = false; - bot.on_message_update([&](const dpp::message_update_t &event) { + bot.on_message_update([&bot,&thread_helper,&message_edit_tested](const dpp::message_update_t &event) { if (event.msg.author == bot.me.id) { if (event.msg.content == "test edit" && !message_edit_tested) { message_edit_tested = true; @@ -1749,7 +1702,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b } }); - bot.on_thread_update([&](const dpp::thread_update_t &event) { + bot.on_thread_update([&thread_helper](const dpp::thread_update_t &event) { if (event.updating_guild->id == TEST_GUILD_ID && event.updated.id == thread_helper.thread_id && event.updated.name == "edited") { set_test(THREAD_UPDATE_EVENT, true); } @@ -1817,7 +1770,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b && bans.find(deadUser3) != bans.end() ); // unban all three - bot.guild_ban_delete(TEST_GUILD_ID, deadUser1, [&bot, deadUser1, deadUser2, deadUser3](const dpp::confirmation_callback_t &event) { + bot.guild_ban_delete(TEST_GUILD_ID, deadUser1, [&bot, deadUser2, deadUser3](const dpp::confirmation_callback_t &event) { if (!event.is_error()) { bot.guild_ban_delete(TEST_GUILD_ID, deadUser2, [&bot, deadUser3](const dpp::confirmation_callback_t &event) { if (!event.is_error()) { @@ -2029,22 +1982,22 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b // testing all user flags from https://discord.com/developers/docs/resources/user#user-object-user-flags // they're manually set here because the dpp::user_flags don't match to the discord API, so we can't use them to compare with the raw flags! if ( - u.is_discord_employee() == ((raw_flags & (1 << 0)) != 0) && - u.is_partnered_owner() == ((raw_flags & (1 << 1)) != 0) && - u.has_hypesquad_events() == ((raw_flags & (1 << 2)) != 0) && - u.is_bughunter_1() == ((raw_flags & (1 << 3)) != 0) && - u.is_house_bravery() == ((raw_flags & (1 << 6)) != 0) && - u.is_house_brilliance() == ((raw_flags & (1 << 7)) != 0) && - u.is_house_balance() == ((raw_flags & (1 << 8)) != 0) && - u.is_early_supporter() == ((raw_flags & (1 << 9)) != 0) && - u.is_team_user() == ((raw_flags & (1 << 10)) != 0) && - u.is_bughunter_2() == ((raw_flags & (1 << 14)) != 0) && - u.is_verified_bot() == ((raw_flags & (1 << 16)) != 0) && - u.is_verified_bot_dev() == ((raw_flags & (1 << 17)) != 0) && - u.is_certified_moderator() == ((raw_flags & (1 << 18)) != 0) && - u.is_bot_http_interactions() == ((raw_flags & (1 << 19)) != 0) && - u.is_active_developer() == ((raw_flags & (1 << 22)) != 0) - ) { + u.is_discord_employee() == ((raw_flags & (1 << 0)) != 0) && + u.is_partnered_owner() == ((raw_flags & (1 << 1)) != 0) && + u.has_hypesquad_events() == ((raw_flags & (1 << 2)) != 0) && + u.is_bughunter_1() == ((raw_flags & (1 << 3)) != 0) && + u.is_house_bravery() == ((raw_flags & (1 << 6)) != 0) && + u.is_house_brilliance() == ((raw_flags & (1 << 7)) != 0) && + u.is_house_balance() == ((raw_flags & (1 << 8)) != 0) && + u.is_early_supporter() == ((raw_flags & (1 << 9)) != 0) && + u.is_team_user() == ((raw_flags & (1 << 10)) != 0) && + u.is_bughunter_2() == ((raw_flags & (1 << 14)) != 0) && + u.is_verified_bot() == ((raw_flags & (1 << 16)) != 0) && + u.is_verified_bot_dev() == ((raw_flags & (1 << 17)) != 0) && + u.is_certified_moderator() == ((raw_flags & (1 << 18)) != 0) && + u.is_bot_http_interactions() == ((raw_flags & (1 << 19)) != 0) && + u.is_active_developer() == ((raw_flags & (1 << 22)) != 0) + ) { set_test(USER_GET_FLAGS, true); } else { set_test(USER_GET_FLAGS, false); @@ -2066,48 +2019,42 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b .set_name("voice1") .add_permission_overwrite(TEST_GUILD_ID, dpp::ot_role, 0, dpp::p_view_channel) .set_user_limit(99); - dpp::channel createdChannel; - try { - createdChannel = dpp::sync(&bot, &dpp::cluster::channel_create, channel1); - } catch (dpp::rest_exception &exception) { - set_test(VOICE_CHANNEL_CREATE, false); - } - if (createdChannel.name == channel1.name && - createdChannel.user_limit == 99 && - createdChannel.name == "voice1") { - for (auto overwrite: createdChannel.permission_overwrites) { - if (overwrite.id == TEST_GUILD_ID && overwrite.type == dpp::ot_role && overwrite.deny == dpp::p_view_channel) { - set_test(VOICE_CHANNEL_CREATE, true); - } - } - - // edit the voice channel - createdChannel.set_name("foobar2"); - createdChannel.set_user_limit(2); - for (auto overwrite: createdChannel.permission_overwrites) { - if (overwrite.id == TEST_GUILD_ID) { - overwrite.deny.set(0); - overwrite.allow.set(dpp::p_view_channel); - } + bot.channel_create(channel1, [&bot,channel1](const auto& response) { + if (response.is_error()) { + set_test(VOICE_CHANNEL_CREATE, false); + return; } - try { - dpp::channel edited = dpp::sync(&bot, &dpp::cluster::channel_edit, createdChannel); - if (edited.name == "foobar2" && edited.user_limit == 2) { - set_test(VOICE_CHANNEL_EDIT, true); + dpp::channel createdChannel = std::get(response.value); + if (createdChannel.name == channel1.name && createdChannel.user_limit == 99 && createdChannel.name == "voice1") { + for (auto overwrite: createdChannel.permission_overwrites) { + if (overwrite.id == TEST_GUILD_ID && overwrite.type == dpp::ot_role && overwrite.deny == dpp::p_view_channel) { + set_test(VOICE_CHANNEL_CREATE, true); + break; + } } - } catch (dpp::rest_exception &exception) { - set_test(VOICE_CHANNEL_EDIT, false); - } - // delete the voice channel - try { - dpp::sync(&bot, &dpp::cluster::channel_delete, createdChannel.id); - set_test(VOICE_CHANNEL_DELETE, true); - } catch (dpp::rest_exception &exception) { - bot.log(dpp::ll_warning, "Exception: " + std::string(exception.what())); - set_test(VOICE_CHANNEL_DELETE, false); + // edit the voice channel + createdChannel.set_name("foobar2"); + createdChannel.set_user_limit(2); + for (auto overwrite: createdChannel.permission_overwrites) { + if (overwrite.id == TEST_GUILD_ID) { + overwrite.deny.set(0); + overwrite.allow.set(dpp::p_view_channel); + } + } + bot.channel_edit(createdChannel, [&bot,createdChannel](const auto& response) { + if (response.is_error()) { + set_test(VOICE_CHANNEL_EDIT, false); + return; + } + dpp::channel edited = std::get(response.value); + set_test(VOICE_CHANNEL_EDIT, (edited.name == "foobar2" && edited.user_limit == 2)); + bot.channel_delete(createdChannel.id,[](const auto& response) { + set_test(VOICE_CHANNEL_DELETE, !response.is_error()); + }); + }); } - } + }); } set_test(FORUM_CREATION, false); @@ -2295,43 +2242,46 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b r.permissions.add(dpp::p_move_members); r.set_flags(dpp::r_mentionable); r.colour = dpp::colors::moon_yellow; - dpp::role createdRole; - try { - createdRole = dpp::sync(&bot, &dpp::cluster::role_create, r); + bot.role_create(r, [&bot, r](const auto cc) { + if (cc.is_error()) { + set_test(ROLE_CREATE, false); + set_test(ROLE_EDIT, false); + set_test(ROLE_DELETE, false); + return; + } + dpp::role createdRole = std::get(cc.value); + createdRole.guild_id = TEST_GUILD_ID; if (createdRole.name == r.name && createdRole.has_move_members() && createdRole.flags & dpp::r_mentionable && createdRole.colour == r.colour) { + createdRole.name = "Test-Role-Edited"; set_test(ROLE_CREATE, true); + bot.role_edit(createdRole, [&bot, createdRole](const auto& e) { + if (e.is_error()) { + set_test(ROLE_EDIT, false); + set_test(ROLE_DELETE, false); + return; + } + dpp::role edited = std::get(e.value); + set_test(ROLE_EDIT, (createdRole.id == edited.id && edited.name == "Test-Role-Edited")); + bot.role_delete(TEST_GUILD_ID, createdRole.id, [](const auto& e) { + set_test(ROLE_DELETE, !e.is_error()); + }); + }); + } else { + set_test(ROLE_CREATE, false); + set_test(ROLE_EDIT, false); + set_test(ROLE_DELETE, false); } - } catch (dpp::rest_exception &exception) { - set_test(ROLE_CREATE, false); - } - createdRole.guild_id = TEST_GUILD_ID; - createdRole.name = "Test-Role-Edited"; - createdRole.colour = dpp::colors::light_sea_green; - try { - dpp::role edited = dpp::sync(&bot, &dpp::cluster::role_edit, createdRole); - if (createdRole.id == edited.id && edited.name == "Test-Role-Edited") { - set_test(ROLE_EDIT, true); - } - } catch (dpp::rest_exception &exception) { - set_test(ROLE_EDIT, false); - } - try { - dpp::sync(&bot, &dpp::cluster::role_delete, TEST_GUILD_ID, createdRole.id); - set_test(ROLE_DELETE, true); - } catch (dpp::rest_exception &exception) { - bot.log(dpp::ll_warning, "Exception: " + std::string(exception.what())); - set_test(ROLE_DELETE, false); - } + }); } }; set_test(BOTSTART, false); try { if (!offline) { - bot.start(true); + bot.start(dpp::st_return); set_test(BOTSTART, true); } } @@ -2339,12 +2289,63 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b set_test(BOTSTART, false); } + dpp::https_client *c{}; + dpp::https_client *c2{}; + dpp::https_client *c3{}; + + set_test(HTTPS, false); + if (!offline) { + dpp::multipart_content multipart = dpp::https_client::build_multipart( + "{\"content\":\"test\"}", {"test.txt", "blob.blob"}, {"ABCDEFGHI", "BLOB!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"}, {"text/plain", "application/octet-stream"} + ); + try { + c = new dpp::https_client(&bot, "discord.com", 443, "/api/channels/" + std::to_string(TEST_TEXT_CHANNEL_ID) + "/messages", "POST", multipart.body, + { + {"Content-Type", multipart.mimetype}, + {"Authorization", "Bot " + token} + }, false, 5, "1.1", [](dpp::https_client* c) { + std::string hdr1 = c->get_header("server"); + std::string content1 = c->get_content(); + set_test(HTTPS, hdr1 == "cloudflare" && c->get_status() == 200); + } + ); + } + catch (const dpp::exception& e) { + set_status(HTTPS, ts_failed, e.what()); + } + + set_test(HTTP, false); + try { + c2 = new dpp::https_client(&bot, "github.com", 80, "/", "GET", "", {}, true, 5, "1.1", [](dpp::https_client *c2) { + std::string hdr2 = c2->get_header("location"); + std::string content2 = c2->get_content(); + set_test(HTTP, hdr2 == "https://github.com/" && c2->get_status() == 301); + }); + } + catch (const dpp::exception& e) { + set_status(HTTP, ts_failed, e.what()); + } + + set_test(MULTIHEADER, false); + try { + c3 = new dpp::https_client(&bot, "dl.dpp.dev", 443, "/cookietest.php", "GET", "", {}, true, 5, "1.1", [](dpp::https_client *c2) { + size_t count = c2->get_header_count("set-cookie"); + size_t count_list = c2->get_header_list("set-cookie").size(); + // This test script sets a bunch of cookies when we request it. + set_test(MULTIHEADER, c2->get_status() == 200 && count > 1 && count == count_list); + }); + } + catch (const dpp::exception& e) { + set_status(MULTIHEADER, ts_failed, e.what()); + } + } + set_test(TIMERSTART, false); - uint32_t ticks = 0; - dpp::timer th = bot.start_timer([&](dpp::timer timer_handle) { - if (ticks == 5) { + static uint32_t ticks = 0; + dpp::timer th = bot.start_timer([](dpp::timer timer_handle) { + if (ticks == 2) { /* The simple test timer ticks every second. - * If we get to 5 seconds, we know the timer is working. + * If we get to 2 seconds, we know the timer is working. */ set_test(TIMERSTART, true); } @@ -2353,8 +2354,14 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b set_test(USER_GET_CACHED_PRESENT, false); try { - dpp::user_identified u = dpp::sync(&bot, &dpp::cluster::user_get_cached, TEST_USER_ID); - set_test(USER_GET_CACHED_PRESENT, (u.id == TEST_USER_ID)); + bot.user_get_cached(TEST_USER_ID, [](const auto &e) { + if (e.is_error()) { + set_test(USER_GET_CACHED_PRESENT, false); + return; + } + dpp::user_identified u = std::get(e.value); + set_test(USER_GET_CACHED_PRESENT, (u.id == TEST_USER_ID)); + }); } catch (const std::exception&) { set_test(USER_GET_CACHED_PRESENT, false); @@ -2368,8 +2375,14 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b * If this becomes not true any more, we'll pick another well known * user ID. */ - dpp::user_identified u = dpp::sync(&bot, &dpp::cluster::user_get_cached, 90339695967350784); - set_test(USER_GET_CACHED_ABSENT, (u.id == dpp::snowflake(90339695967350784))); + bot.user_get_cached(90339695967350784, [](const auto &e) { + if (e.is_error()) { + set_test(USER_GET_CACHED_ABSENT, false); + return; + } + dpp::user_identified u = std::get(e.value); + set_test(USER_GET_CACHED_ABSENT, (u.id == 90339695967350784)); + }); } catch (const std::exception&) { set_test(USER_GET_CACHED_ABSENT, false); @@ -2444,10 +2457,13 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b wait_for_tests(); + delete c; + delete c2; + delete c3; + } catch (const std::exception &e) { - std::cout << e.what() << "\n"; - set_test(CLUSTER, false); + set_status(CLUSTER, ts_failed, e.what()); } /* Return value = number of failed tests, exit code 0 = success */ diff --git a/src/unittest/test.h b/src/unittest/test.h index 8c0d1e1192..cf9f966a28 100644 --- a/src/unittest/test.h +++ b/src/unittest/test.h @@ -39,6 +39,8 @@ _Pragma("warning( disable : 5105 )"); // 4251 warns when we export classes or st using json = nlohmann::json; +extern std::mutex loglock; + enum test_flags_t { tf_offline = 0, /* A test that requires discord connectivity */ @@ -137,14 +139,13 @@ DPP_TEST(OPTCHOICE_SNOWFLAKE, "command_option_choice::fill_from_json: snowflake" DPP_TEST(OPTCHOICE_STRING, "command_option_choice::fill_from_json: string", tf_offline); DPP_TEST(HOSTINFO, "https_client::get_host_info()", tf_offline); DPP_TEST(HTTPS, "https_client HTTPS request", tf_online); -DPP_TEST(HTTP, "https_client HTTP request", tf_offline); +DPP_TEST(HTTP, "https_client HTTP request", tf_online); DPP_TEST(RUNONCE, "run_once", tf_offline); DPP_TEST(WEBHOOK, "webhook construct from URL", tf_offline); DPP_TEST(MD_ESC_1, "Markdown escaping (ignore code block contents)", tf_offline); DPP_TEST(MD_ESC_2, "Markdown escaping (escape code block contents)", tf_offline); DPP_TEST(URLENC, "URL encoding", tf_offline); DPP_TEST(BASE64ENC, "Base 64 encoding", tf_offline); -DPP_TEST(SYNC, "sync()", tf_online); DPP_TEST(COMPARISON, "manged object comparison", tf_offline); DPP_TEST(CHANNELCACHE, "find_channel()", tf_online); DPP_TEST(CHANNELTYPES, "channel type flags", tf_online); @@ -229,9 +230,9 @@ DPP_TEST(INVITE_CREATE, "cluster::channel_invite_create", tf_online); DPP_TEST(INVITE_GET, "cluster::invite_get", tf_online); DPP_TEST(INVITE_DELETE, "cluster::invite_delete", tf_online); -/* Extended set -- Less important, skipped on the master branch due to rate limits and GitHub actions limitations*/ +/* Extended set -- Less important, skipped on the master branch due to rate limits and GitHub actions limitations */ /* To execute, run unittests with "full" command line argument */ -DPP_TEST(MULTIHEADER, "multiheader cookie test", tf_offline | tf_extended); // Fails in the EU as cookies are not sent without acceptance +DPP_TEST(MULTIHEADER, "multiheader cookie test", tf_online | tf_extended); DPP_TEST(VOICECONN, "Connect to voice channel", tf_online | tf_extended); DPP_TEST(VOICESEND, "Send audio to voice channel", tf_online | tf_extended); // udp unreliable on gitbub @@ -291,7 +292,7 @@ extern dpp::snowflake TEST_EVENT_ID; /* True if we skip tt_online tests */ extern bool offline; -/* True if we skip tt_extended tests*/ +/* True if we skip tt_extended tests */ extern bool extended; #ifdef DPP_CORO inline constexpr bool coro = true; @@ -577,9 +578,9 @@ inline constexpr auto is_owner = [](auto &&user) noexcept { #endif #define DPP_CHECK_CONSTRUCT_ASSIGN(test, type, var) do { \ - DPP_CHECK(test, std::is_default_constructible_v, var); \ - DPP_CHECK(test, std::is_copy_constructible_v, var); \ - DPP_CHECK(test, std::is_move_constructible_v, var); \ - DPP_CHECK(test, std::is_copy_assignable_v, var); \ + DPP_CHECK(test, std::is_default_constructible_v, var); \ + DPP_CHECK(test, std::is_copy_constructible_v, var); \ + DPP_CHECK(test, std::is_move_constructible_v, var); \ + DPP_CHECK(test, std::is_copy_assignable_v, var); \ DPP_CHECK(test, std::is_move_assignable_v, var); \ - } while(0) +} while(0) diff --git a/src/unittest/unittest.cpp b/src/unittest/unittest.cpp index 65059ad3fc..ec58d888c2 100644 --- a/src/unittest/unittest.cpp +++ b/src/unittest/unittest.cpp @@ -38,8 +38,7 @@ test_t::test_t(std::string_view testname, std::string_view testdesc, int testfla } void set_status(test_t &test, test_status_t newstatus, std::string_view message) { - static std::mutex m; - std::scoped_lock lock{m}; + std::lock_guard locker(loglock); if (is_skipped(test) || newstatus == test.status) { return; @@ -99,6 +98,7 @@ double get_time() { int test_summary() { /* Report on all test cases */ + std::lock_guard locker(loglock); int failed = 0, passed = 0, skipped = 0; std::cout << "\u001b[37;1m\n\nUNIT TEST SUMMARY\n==================\n\u001b[0m"; for (auto & t : tests) { @@ -193,7 +193,7 @@ void wait_for_tests() { } } if (finished == tests.size()) { - std::this_thread::sleep_for(std::chrono::seconds(10)); + std::this_thread::sleep_for(std::chrono::seconds(5)); return; } std::this_thread::sleep_for(std::chrono::seconds(1));