From dd567e670b7dfeef6bac1c6a57a03dddfd6ba7e2 Mon Sep 17 00:00:00 2001 From: "Craig Edwards (Brain)" Date: Wed, 18 Dec 2024 17:20:34 +0000 Subject: [PATCH] fix: large https request body could not be sent (#1352) --- .clang-tidy | 2 +- .github/workflows/ci.yml | 4 +- include/dpp/sslclient.h | 28 + src/dpp/cluster.cpp | 18 +- src/dpp/discordclient.cpp | 22 +- src/dpp/dispatcher.cpp | 4 +- src/dpp/events/ready.cpp | 9 +- src/dpp/httpsclient.cpp | 63 +- src/dpp/sslclient.cpp | 229 +- src/dpp/utility.cpp | 43 +- src/dpp/voice/enabled/courier_loop.cpp | 432 +-- src/dpp/voice/enabled/read_ready.cpp | 4 +- src/unittest/test.cpp | 3832 ++++++++++++------------ 13 files changed, 2406 insertions(+), 2284 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index 33014a7878..dcb8730057 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,-cppcoreguidelines-owning-memory,-readability-function-cognitive-complexity,-cppcoreguidelines-avoid-do-while" +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,-cppcoreguidelines-pro-bounds-array-to-pointer-decay,-hicpp-no-array-decay" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 89e025ae05..231d643498 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -96,7 +96,7 @@ jobs: - name: Run unit tests if: ${{ matrix.cfg.ctest == 'yes' }} - run: cd build && ctest -VV + run: cd build/library && ./unittest env: DPP_UNIT_TEST_TOKEN: ${{secrets.DPP_UNIT_TEST_TOKEN}} TEST_GUILD_ID: ${{secrets.TEST_GUILD_ID}} @@ -161,7 +161,7 @@ jobs: DONT_RUN_VCPKG: true - name: Run offline unit tests - run: cd build && ctest -VV + run: cd build/library && ./unittest windows: # Windows x64 and x86 build matrix permissions: diff --git a/include/dpp/sslclient.h b/include/dpp/sslclient.h index 896a60baf5..40381540a1 100644 --- a/include/dpp/sslclient.h +++ b/include/dpp/sslclient.h @@ -96,6 +96,11 @@ class DPP_EXPORT ssl_client */ std::mutex ssl_mutex; + /** + * @brief Mutex for output buffer + */ + std::mutex out_mutex; + /** * @brief Start offset into internal ring buffer for client to server IO */ @@ -217,7 +222,30 @@ class DPP_EXPORT ssl_client */ virtual void connect(); + /** + * @brief Set this to true to log all IO to debug for this connection. + * This is an internal developer facility. Do not enable it unless you + * need to, as it will be very noisy. + */ + bool raw_trace{false}; + + /** + * @brief If raw_trace is set to true, log a debug message for this connection + * @param message debug message + */ + void do_raw_trace(const std::string& message) const; + public: + /** + * @brief For low-level debugging, calling this function will + * enable low level I/O logging for this connection to the logger. + * This can be very loud, and output a lot of data, so only enable it + * selectively where you need it. + * + * Generally, you won't need this, it is a library development utility. + */ + void enable_raw_tracing(); + /** * @brief Get the bytes out objectGet total bytes sent * @return uint64_t bytes sent diff --git a/src/dpp/cluster.cpp b/src/dpp/cluster.cpp index 1169db32d2..b569a3a918 100644 --- a/src/dpp/cluster.cpp +++ b/src/dpp/cluster.cpp @@ -382,12 +382,22 @@ void cluster::start(start_type return_after) { } if (return_after == st_return) { - engine_thread = std::thread([event_loop]() { - dpp::utility::set_thread_name("event_loop"); - event_loop(); + engine_thread = std::thread([this, event_loop]() { + try { + dpp::utility::set_thread_name("event_loop"); + event_loop(); + } + catch (const std::exception& e) { + log(ll_critical, "Event loop unhandled exception: " + std::string(e.what())); + } }); } else { - event_loop(); + try { + event_loop(); + } + catch (const std::exception& e) { + log(ll_critical, "Event loop unhandled exception: " + std::string(e.what())); + } } } diff --git a/src/dpp/discordclient.cpp b/src/dpp/discordclient.cpp index 0865069257..d73d5dd08b 100644 --- a/src/dpp/discordclient.cpp +++ b/src/dpp/discordclient.cpp @@ -602,19 +602,15 @@ voiceconn::~voiceconn() { voiceconn& voiceconn::connect(snowflake guild_id) { if (this->is_ready() && !this->is_active()) { - /* This is wrapped in a thread because instantiating discord_voice_client can initiate a blocking SSL_connect() */ - auto t = std::thread([guild_id, this]() { - try { - this->creator->log(ll_debug, "Connecting voice for guild " + std::to_string(guild_id) + " channel " + std::to_string(this->channel_id)); - this->voiceclient = new discord_voice_client(creator->creator, this->channel_id, guild_id, this->token, this->session_id, this->websocket_hostname, this->dave); - /* Note: Spawns thread! */ - this->voiceclient->run(); - } - catch (std::exception &e) { - this->creator->log(ll_debug, "Can't connect to voice websocket (guild_id: " + std::to_string(guild_id) + ", channel_id: " + std::to_string(this->channel_id) + ": " + std::string(e.what())); - } - }); - t.detach(); + try { + this->creator->log(ll_debug, "Connecting voice for guild " + std::to_string(guild_id) + " channel " + std::to_string(this->channel_id)); + this->voiceclient = new discord_voice_client(creator->creator, this->channel_id, guild_id, this->token, this->session_id, this->websocket_hostname, this->dave); + /* Note: Spawns thread! */ + this->voiceclient->run(); + } + catch (std::exception &e) { + this->creator->log(ll_debug, "Can't connect to voice websocket (guild_id: " + std::to_string(guild_id) + ", channel_id: " + std::to_string(this->channel_id) + "): " + std::string(e.what())); + } } return *this; } diff --git a/src/dpp/dispatcher.cpp b/src/dpp/dispatcher.cpp index 314a1f1efe..4d7015d015 100644 --- a/src/dpp/dispatcher.cpp +++ b/src/dpp/dispatcher.cpp @@ -271,11 +271,11 @@ command_value interaction_create_t::get_parameter(const std::string& name) const return {}; } -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) { +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(creator, shard_id, std::move(raw)), voice_client(vc), user_id(_user_id) { reassign(vc, _user_id, pcm, length); } -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) { +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(creator, shard_id, std::move(raw)), voice_client(vc), user_id(_user_id) { reassign(vc, _user_id, pcm, length); } diff --git a/src/dpp/events/ready.cpp b/src/dpp/events/ready.cpp index 765f9a9daa..5e915237a1 100644 --- a/src/dpp/events/ready.cpp +++ b/src/dpp/events/ready.cpp @@ -56,8 +56,13 @@ void ready::handle(discord_client* client, json &j, const std::string &raw) { client->resume_gateway_url = ugly; } /* Pre-resolve it into our cache so that we aren't waiting on this when we need it later */ - static_cast(resolve_hostname(client->resume_gateway_url, "443")); - client->log(ll_debug, "Resume URL for session " + client->sessionid + " is " + ugly + " (host: " + client->resume_gateway_url + ")"); + try { + static_cast(resolve_hostname(client->resume_gateway_url, "443")); + client->log(ll_debug, "Resume URL for session " + client->sessionid + " is " + ugly + " (host: " + client->resume_gateway_url + ")"); + } + catch (std::exception& e) { + client->log(ll_warning, "Resume URL " + client->resume_gateway_url + " does not resolve: " + std::string(e.what())); + } client->ready = true; diff --git a/src/dpp/httpsclient.cpp b/src/dpp/httpsclient.cpp index 7ab9f5fdac..08e89241e5 100644 --- a/src/dpp/httpsclient.cpp +++ b/src/dpp/httpsclient.cpp @@ -27,6 +27,7 @@ #include #include #include +#include namespace dpp { @@ -54,6 +55,7 @@ void https_client::connect() for (auto& [k,v] : request_headers) { map_headers += k + ": " + v + "\r\n"; } + if (this->sfd != SOCKET_ERROR) { this->socket_write( this->request_type + " " + this->path + " HTTP/" + http_protocol + "\r\n" @@ -72,43 +74,44 @@ void https_client::connect() } multipart_content https_client::build_multipart(const std::string &json, const std::vector& filenames, const std::vector& contents, const std::vector& mimetypes) { + if (filenames.empty() && contents.empty()) { + /* If there are no files to upload, there is no need to build a multipart body */ if (!json.empty()) { return { json, "application/json" }; - } else { - return {json, ""}; } - } else { - /* Note: loss of upper 32 bits on this value is INTENTIONAL */ - uint32_t dummy1 = (uint32_t)time(nullptr) + (uint32_t)time(nullptr); - time_t dummy2 = time(nullptr) * time(nullptr); - const std::string two_cr("\r\n\r\n"); - const std::string boundary("-------------" + to_hex(dummy1) + to_hex(dummy2)); - const std::string part_start("--" + boundary + "\r\nContent-Disposition: form-data; "); - const std::string mime_type_start("\r\nContent-Type: "); - const std::string default_mime_type("application/octet-stream"); - - std::string content("--" + boundary); + return {json, ""}; + } - /* Special case, single file */ - content += "\r\nContent-Type: application/json\r\nContent-Disposition: form-data; name=\"payload_json\"" + two_cr; - content += json + "\r\n"; - if (filenames.size() == 1 && contents.size() == 1) { - content += part_start + "name=\"file\"; filename=\"" + filenames[0] + "\""; - content += mime_type_start + (mimetypes.empty() || mimetypes[0].empty() ? default_mime_type : mimetypes[0]) + two_cr; - content += contents[0]; - } else { - /* Multiple files */ - for (size_t i = 0; i < filenames.size(); ++i) { - content += part_start + "name=\"files[" + std::to_string(i) + "]\"; filename=\"" + filenames[i] + "\""; - content += "\r\nContent-Type: " + (mimetypes.size() <= i || mimetypes[i].empty() ? default_mime_type : mimetypes[i]) + two_cr; - content += contents[i]; - content += "\r\n"; - } + /* Note: loss of upper 32 bits on this value is INTENTIONAL */ + uint32_t dummy1 = (uint32_t)time(nullptr) + (uint32_t)time(nullptr); + time_t dummy2 = time(nullptr) * time(nullptr); + const std::string two_cr("\r\n\r\n"); + const std::string boundary("-------------" + to_hex(dummy1) + to_hex(dummy2)); + const std::string part_start("--" + boundary + "\r\nContent-Disposition: form-data; "); + const std::string mime_type_start("\r\nContent-Type: "); + const std::string default_mime_type("application/octet-stream"); + + std::string content("--" + boundary); + + /* Special case, single file */ + content += "\r\nContent-Type: application/json\r\nContent-Disposition: form-data; name=\"payload_json\"" + two_cr; + content += json + "\r\n"; + if (filenames.size() == 1 && contents.size() == 1) { + content += part_start + "name=\"file\"; filename=\"" + filenames[0] + "\""; + content += mime_type_start + (mimetypes.empty() || mimetypes[0].empty() ? default_mime_type : mimetypes[0]) + two_cr; + content += contents[0]; + } else { + /* Multiple files */ + for (size_t i = 0; i < filenames.size(); ++i) { + content += part_start + "name=\"files[" + std::to_string(i) + "]\"; filename=\"" + filenames[i] + "\""; + content += "\r\nContent-Type: " + (mimetypes.size() <= i || mimetypes[i].empty() ? default_mime_type : mimetypes[i]) + two_cr; + content += contents[i]; + content += "\r\n"; } - content += "\r\n--" + boundary + "--"; - return { content, "multipart/form-data; boundary=" + boundary }; } + content += "\r\n--" + boundary + "--"; + return { content, "multipart/form-data; boundary=" + boundary }; } const std::string https_client::get_header(std::string header_name) const { diff --git a/src/dpp/sslclient.cpp b/src/dpp/sslclient.cpp index c7c9a15c02..89e7242e29 100644 --- a/src/dpp/sslclient.cpp +++ b/src/dpp/sslclient.cpp @@ -49,20 +49,15 @@ #include #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 { +/** + * @brief Unique ID of next socket (for end-user use) + */ uint64_t last_unique_id{1}; /** @@ -98,7 +93,8 @@ class openssl_context_deleter { }; /** - * @brief OpenSSL context + * @brief OpenSSL context. + * Each thread has one of these so it is thread_local. */ thread_local std::unique_ptr openssl_context; @@ -139,7 +135,9 @@ bool set_nonblocking(dpp::socket sockfd, bool non_blocking) } /** - * @brief Connect to TCP socket with a poll() driven timeout + * @brief Start connecting to a TCP socket. + * This simply calls connect() and checks for error return, as the timeout is now handled in the main + * IO events for the ssl_client class. * * @param sockfd socket descriptor * @param addr address to connect to @@ -148,7 +146,7 @@ bool set_nonblocking(dpp::socket sockfd, bool non_blocking) * @return int -1 on error, 0 on success just like POSIX connect() * @throw dpp::connection_exception on failure */ -int start_connecting(dpp::socket sockfd, const struct sockaddr *addr, socklen_t addrlen, unsigned int timeout_ms) { +int start_connecting(dpp::socket sockfd, const struct sockaddr *addr, socklen_t addrlen) { if (!set_nonblocking(sockfd, true)) { throw dpp::connection_exception(err_nonblocking_failure, "Can't switch socket to non-blocking mode!"); } @@ -172,6 +170,15 @@ int start_connecting(dpp::socket sockfd, const struct sockaddr *addr, socklen_t } #ifndef _WIN32 +/** + * @brief Some old Linux and UNIX variants (BSDs) can raise signals for socket + * errors, such as SIGPIPE etc. We filter these out so we can just concern ourselves + * with the return codes from the functions instead. + * + * @note If there is an existing signal handler, it will be preserved. + * + * @param signal Signal code to override + */ void set_signal_handler(int signal) { struct sigaction sa{}; @@ -208,7 +215,6 @@ ssl_client::ssl_client(cluster* creator, const std::string &_hostname, const std ssl = new openssl_connection(); } try { - //this->connect(); ssl_client::connect(); } catch (std::exception&) { @@ -217,9 +223,8 @@ ssl_client::ssl_client(cluster* creator, const std::string &_hostname, const std } } -/* SSL Client constructor throws std::runtime_error if it can't connect to the host */ -void ssl_client::connect() -{ +/* SSL Client constructor throws std::runtime_error if it can't allocate a socket or call connect() */ +void ssl_client::connect() { /* Resolve hostname to IP */ int err = 0; const dns_cache_entry* addr = resolve_hostname(hostname, port); @@ -228,7 +233,7 @@ void ssl_client::connect() if (sfd == ERROR_STATUS) { err = errno; } else { - start_connecting(sfd, destination.get_socket_address(), destination.size(), SOCKET_OP_TIMEOUT); + start_connecting(sfd, destination.get_socket_address(), destination.size()); } /* Check if valid connection started */ if (sfd == ERROR_STATUS) { @@ -236,21 +241,22 @@ void ssl_client::connect() } } -void ssl_client::socket_write(const std::string_view data) -{ +void ssl_client::socket_write(const std::string_view data) { + /* Because this is a non-blocking system we never write immediately. We append to the buffer, + * which writes later. + */ + std::lock_guard lock(out_mutex); obuffer += data; } -void ssl_client::one_second_timer() -{ +void ssl_client::one_second_timer() { } std::string ssl_client::get_cipher() { return cipher; } -void ssl_client::log(dpp::loglevel severity, const std::string &msg) const -{ +void ssl_client::log(dpp::loglevel severity, const std::string &msg) const { } void ssl_client::complete_handshake(const socket_events* ev) @@ -286,6 +292,7 @@ void ssl_client::complete_handshake(const socket_events* ev) } } } else { + do_raw_trace("(SSL): "); socket_events se{*ev}; se.flags = dpp::WANT_WRITE | dpp::WANT_READ | dpp::WANT_ERROR; owner->socketengine->update_socket(se); @@ -295,6 +302,12 @@ void ssl_client::complete_handshake(const socket_events* ev) } +void ssl_client::do_raw_trace(const std::string& message) const { + if (raw_trace) { + log(ll_trace, "RAWTRACE" + message); + } +} + void ssl_client::on_read(socket fd, const struct socket_events& ev) { if (sfd == INVALID_SOCKET) { @@ -308,6 +321,7 @@ void ssl_client::on_read(socket fd, const struct socket_events& ev) { return; } buffer.append(server_to_client_buffer, r); + do_raw_trace("(IN,PLAIN): " + std::string(server_to_client_buffer, r)); if (!this->handle_buffer(buffer)) { this->close(); return; @@ -322,7 +336,7 @@ void ssl_client::on_read(socket fd, const struct socket_events& ev) { /* Data received, add it to the buffer */ if (r > 0) { buffer.append(server_to_client_buffer, r); - + do_raw_trace("(IN,SSL): " + std::string(server_to_client_buffer, r)); if (!this->handle_buffer(buffer)) { this->close(); return; @@ -370,10 +384,13 @@ void ssl_client::on_read(socket fd, const struct socket_events& ev) { complete_handshake(&ev); } - 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); + { + std::lock_guard lock(out_mutex); + 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); + } } } @@ -384,18 +401,24 @@ void ssl_client::on_write(socket fd, const struct socket_events& e) { } if (!tcp_connect_done) { + /* The first write event on an outbound TCP socket indicates connect() has finished */ tcp_connect_done = true; + do_raw_trace("(OUT): "); } if (!connected && plaintext) { - /* Plaintext sockets connect immediately on first write event */ + /* Plaintext sockets connect immediately on first write event. + * There is nothing more to do, so set connected to true. + */ connected = true; } else if (!connected) { - /* SSL handshake and session setup */ + /* SSL handshake and session setup. SSL sessions require more legwork + * to get them initialised after connect() completes. We do that here. + */ /* 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 new client-method instance */ + const SSL_METHOD *method = TLS_client_method(); /* Create SSL context */ openssl_context.reset(SSL_CTX_new(method)); @@ -403,25 +426,32 @@ void ssl_client::on_write(socket fd, const struct socket_events& e) { 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/ - */ + /* This sets the allowed SSL/TLS versions for the connection. + * 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 (ssl != nullptr && ssl->ssl == nullptr) { - /* Create SSL session */ + /* Now we can create SSL session. + * These are unique to each connection, using the context. + */ 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!"); } + /* Associate the SSL session with the file descriptor, and set it as connecting */ SSL_set_fd(ssl->ssl, (int) sfd); SSL_set_connect_state(ssl->ssl); - /* Server name identification (SNI) */ + /* Server name identification (SNI) + * This is needed for modern HTTPS and tells SSL which virtual host to connect a + * socket to: https://www.cloudflare.com/en-gb/learning/ssl/what-is-sni/ + */ SSL_set_tlsext_host_name(ssl->ssl, hostname.c_str()); } @@ -430,59 +460,104 @@ void ssl_client::on_write(socket fd, const struct socket_events& e) { } 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; + { + /* We are fully connected, check if we have output buffer to send */ + std::lock_guard lock(out_mutex); + if (!obuffer.empty() && client_to_server_length == 0) { + /* If we do, copy it to the raw buffer OpenSSL uses */ + 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 (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; + /* For plaintext connections life is simple, we just call ::send() to send as much of the buffer as we can */ if (client_to_server_length > 0) { - socket_events se{e}; - se.flags = WANT_READ | WANT_WRITE | WANT_ERROR; - owner->socketengine->update_socket(se); + int r = static_cast(::send(sfd, client_to_server_buffer + client_to_server_offset, static_cast(client_to_server_length), 0)); + do_raw_trace("(OUT,PLAIN): " + std::string(client_to_server_buffer + client_to_server_offset, client_to_server_length)); + if (r < 0) { + /* Write error */ + do_raw_trace("(OUT,PLAIN): "); + this->close(); + return; + } + /* We wrote some, or all of the buffer */ + client_to_server_length -= r; + client_to_server_offset += r; + bytes_out += r; + } else { + /* Spurious write event for empty buffer */ + do_raw_trace("(OUT,PLAIN): "); + } + { + std::lock_guard lock(out_mutex); + if (!obuffer.empty()) { + /* Still content to send? Request that we get a write event */ + socket_events se{e}; + se.flags = WANT_READ | WANT_WRITE | WANT_ERROR; + do_raw_trace("(OUT,PLAIN): "); + 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); + /* SSL connections are more complex, the SSL_write function can return some weirdness. See below. */ + int err{SSL_ERROR_NONE}; + int r{0}; + if (client_to_server_length > 0) { + r = SSL_write(ssl->ssl, client_to_server_buffer + client_to_server_offset, static_cast(client_to_server_length)); + do_raw_trace("(OUT,SSL): " + std::string(client_to_server_buffer + client_to_server_offset, client_to_server_length)); + err = SSL_get_error(ssl->ssl, r); + } else { + /* Spurious write event for empty buffer */ + do_raw_trace("(OUT,SSL): "); + } + /* Handle SSL_write return code */ switch (err) { - /* We wrote something */ - case SSL_ERROR_NONE: + case SSL_ERROR_NONE: { + /* We wrote some or all of the buffer */ + std::lock_guard lock(out_mutex); client_to_server_length -= r; client_to_server_offset += r; bytes_out += r; + if (!obuffer.empty()) { + /* Still content to send? Request that we get a write event */ + socket_events se{e}; + se.flags = WANT_READ | WANT_WRITE | WANT_ERROR; + owner->socketengine->update_socket(se); + do_raw_trace("(OUT,SSL): "); + } + do_raw_trace("(OUT,SSL): "); break; - /* We would have blocked */ + } case SSL_ERROR_WANT_READ: { + /* OpenSSL said we wrote, but now it wants a read event */ socket_events se{e}; se.flags = WANT_READ | WANT_ERROR; owner->socketengine->update_socket(se); + do_raw_trace("(OUT,SSL): "); break; } case SSL_ERROR_WANT_WRITE: { + /* OpenSSL said it still needs another write event */ socket_events se{e}; se.flags = WANT_READ | WANT_WRITE | WANT_ERROR; owner->socketengine->update_socket(se); + do_raw_trace("(OUT,SSL): "); break; } case SSL_ERROR_SYSCALL: { + /* There was an actual error */ + do_raw_trace("(OUT,SSL): "); if (errno != 0) { + /* If errno != 0, it was a socket error, close socket */ this->close(); } break; } - /* Some other error */ + /* Some other error, these are not valid here, so we do nothing! */ default: { return; } @@ -495,8 +570,7 @@ void ssl_client::on_error(socket fd, const struct socket_events&, int error_code this->close(); } -void ssl_client::read_loop() -{ +void ssl_client::read_loop() { auto setup_events = [this]() { dpp::socket_events events( sfd, @@ -517,7 +591,10 @@ void ssl_client::read_loop() } on_write(fd, e); }, - [this](socket fd, const struct socket_events &e, int error_code) { on_error(fd, e, error_code); } + [this](socket fd, const struct socket_events &e, int error_code) { + do_raw_trace("on_error"); + on_error(fd, e, error_code); + } ); owner->socketengine->register_socket(events); }; @@ -533,14 +610,14 @@ void ssl_client::read_loop() * 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)); + do_raw_trace("(OUT) 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())); + do_raw_trace("(OUT): connect() exception: " + std::string(e.what())); } setup_events(); start = time(nullptr) + 2; @@ -550,23 +627,25 @@ void ssl_client::read_loop() } } -uint64_t ssl_client::get_bytes_out() -{ +uint64_t ssl_client::get_bytes_out() { return bytes_out; } -uint64_t ssl_client::get_bytes_in() -{ +uint64_t ssl_client::get_bytes_in() { return bytes_in; } -bool ssl_client::handle_buffer(std::string &buffer) -{ +bool ssl_client::handle_buffer(std::string &buffer) { return true; } -void ssl_client::close() -{ +void ssl_client::close() { + /** + * Many of the values here are reset to initial values in the case + * we want to reconnect the socket after closing it. This is not something + * that is done often. + */ + std::lock_guard out_lock(out_mutex); if (!plaintext) { std::lock_guard lock(ssl_mutex); if (ssl != nullptr && ssl->ssl != nullptr) { @@ -588,13 +667,11 @@ void ssl_client::close() buffer.clear(); } -void ssl_client::cleanup() -{ +void ssl_client::cleanup() { this->close(); } -ssl_client::~ssl_client() -{ +ssl_client::~ssl_client() { cleanup(); if (timer_handle) { owner->stop_timer(timer_handle); diff --git a/src/dpp/utility.cpp b/src/dpp/utility.cpp index 922fc4195e..79e51c646d 100644 --- a/src/dpp/utility.cpp +++ b/src/dpp/utility.cpp @@ -453,27 +453,30 @@ uint32_t hsl(double h, double s, double l) { void exec(const std::string& cmd, std::vector parameters, cmd_result_t callback) { auto t = std::thread([cmd, parameters, callback]() { - utility::set_thread_name("async_exec"); - std::array buffer; - std::vector my_parameters = parameters; - std::string result; - std::stringstream cmd_and_parameters; - cmd_and_parameters << cmd; - for (auto & parameter : my_parameters) { - cmd_and_parameters << " " << std::quoted(parameter); - } - /* Capture stderr */ - cmd_and_parameters << " 2>&1"; - std::unique_ptr pipe(popen(cmd_and_parameters.str().c_str(), "r"), pclose); - if (!pipe) { - return; - } - while (fgets(buffer.data(), (int)buffer.size(), pipe.get()) != nullptr) { - result += buffer.data(); - } - if (callback) { - callback(result); + try { + utility::set_thread_name("async_exec"); + std::array buffer; + std::vector my_parameters = parameters; + std::string result; + std::stringstream cmd_and_parameters; + cmd_and_parameters << cmd; + for (auto ¶meter: my_parameters) { + cmd_and_parameters << " " << std::quoted(parameter); + } + /* Capture stderr */ + cmd_and_parameters << " 2>&1"; + std::unique_ptr pipe(popen(cmd_and_parameters.str().c_str(), "r"), pclose); + if (!pipe) { + return; + } + while (fgets(buffer.data(), (int) buffer.size(), pipe.get()) != nullptr) { + result += buffer.data(); + } + if (callback) { + callback(result); + } } + catch (...) { /* There is nowhere to log this... */ } }); t.detach(); } diff --git a/src/dpp/voice/enabled/courier_loop.cpp b/src/dpp/voice/enabled/courier_loop.cpp index a8d8f61c0a..e93e20e848 100644 --- a/src/dpp/voice/enabled/courier_loop.cpp +++ b/src/dpp/voice/enabled/courier_loop.cpp @@ -36,258 +36,264 @@ namespace dpp { void discord_voice_client::voice_courier_loop(discord_voice_client& client, courier_shared_state_t& shared_state) { utility::set_thread_name(std::string("vcourier/") + std::to_string(client.server_id)); - while (true) { - std::this_thread::sleep_for(std::chrono::milliseconds{client.iteration_interval}); - - struct flush_data_t { - snowflake user_id; - rtp_seq_t min_seq; - std::priority_queue parked_payloads; - std::vector> pending_decoder_ctls; - std::shared_ptr decoder; - }; - std::vector flush_data; - - /* - * Transport the payloads onto this thread, and - * release the lock as soon as possible. - */ - { - std::unique_lock lk(shared_state.mtx); - - /* mitigates vector resizing while holding the mutex */ - flush_data.reserve(shared_state.parked_voice_payloads.size()); - - bool has_payload_to_deliver = false; - for (auto &[user_id, parking_lot]: shared_state.parked_voice_payloads) { - has_payload_to_deliver = has_payload_to_deliver || !parking_lot.parked_payloads.empty(); - - flush_data.push_back(flush_data_t{ - user_id, - parking_lot.range.min_seq, - std::move(parking_lot.parked_payloads), - /* Quickly check if we already have a decoder and only take the pending ctls if so. */ - parking_lot.decoder ? std::move(parking_lot.pending_decoder_ctls) - : decltype(parking_lot.pending_decoder_ctls){}, - parking_lot.decoder - }); - - parking_lot.range.min_seq = parking_lot.range.max_seq + 1; - parking_lot.range.min_timestamp = parking_lot.range.max_timestamp + 1; - } + try { + + while (true) { + std::this_thread::sleep_for(std::chrono::milliseconds{client.iteration_interval}); - if (!has_payload_to_deliver) { - if (shared_state.terminating) { - /* We have delivered all data to handlers. Terminate now. */ - break; + struct flush_data_t { + snowflake user_id; + rtp_seq_t min_seq; + std::priority_queue parked_payloads; + std::vector> pending_decoder_ctls; + std::shared_ptr decoder; + }; + std::vector flush_data; + + /* + * Transport the payloads onto this thread, and + * release the lock as soon as possible. + */ + { + std::unique_lock lk(shared_state.mtx); + + /* mitigates vector resizing while holding the mutex */ + flush_data.reserve(shared_state.parked_voice_payloads.size()); + + bool has_payload_to_deliver = false; + for (auto &[user_id, parking_lot]: shared_state.parked_voice_payloads) { + has_payload_to_deliver = has_payload_to_deliver || !parking_lot.parked_payloads.empty(); + + flush_data.push_back(flush_data_t{ + user_id, + parking_lot.range.min_seq, + std::move(parking_lot.parked_payloads), + /* Quickly check if we already have a decoder and only take the pending ctls if so. */ + parking_lot.decoder ? std::move(parking_lot.pending_decoder_ctls) + : decltype(parking_lot.pending_decoder_ctls){}, + parking_lot.decoder + }); + + parking_lot.range.min_seq = parking_lot.range.max_seq + 1; + parking_lot.range.min_timestamp = parking_lot.range.max_timestamp + 1; } - shared_state.signal_iteration.wait(lk, [&shared_state](){ + if (!has_payload_to_deliver) { if (shared_state.terminating) { - return true; + /* We have delivered all data to handlers. Terminate now. */ + break; } - /* - * Actually check the state we're looking for instead of waking up - * everytime read_ready was called. - */ - for (auto &[user_id, parking_lot]: shared_state.parked_voice_payloads) { - if (parking_lot.parked_payloads.empty()) { - continue; + shared_state.signal_iteration.wait(lk, [&shared_state]() { + if (shared_state.terminating) { + return true; } - return true; - } - return false; - }); + /* + * Actually check the state we're looking for instead of waking up + * everytime read_ready was called. + */ + for (auto &[user_id, parking_lot]: shared_state.parked_voice_payloads) { + if (parking_lot.parked_payloads.empty()) { + continue; + } + return true; + } + return false; + }); + + /* + * More data came or about to terminate, or just a spurious wake. + * We need to collect the payloads again to determine what to do next. + */ + continue; + } + } + + if (client.creator->on_voice_receive.empty() && client.creator->on_voice_receive_combined.empty()) { /* - * More data came or about to terminate, or just a spurious wake. - * We need to collect the payloads again to determine what to do next. + * We do this check late, to ensure this thread drains the data + * and prevents accumulating them even when there are no handlers. */ continue; } - } - if (client.creator->on_voice_receive.empty() && client.creator->on_voice_receive_combined.empty()) { - /* - * We do this check late, to ensure this thread drains the data - * and prevents accumulating them even when there are no handlers. + /* This 32 bit PCM audio buffer is an upmixed version of the streams + * combined for all users. This is a wider width audio buffer so that + * there is no clipping when there are many loud audio sources at once. */ - continue; - } - - /* This 32 bit PCM audio buffer is an upmixed version of the streams - * combined for all users. This is a wider width audio buffer so that - * there is no clipping when there are many loud audio sources at once. - */ - opus_int32 pcm_mix[23040] = {0}; - size_t park_count = 0; - int max_samples = 0; - int samples = 0; - - opus_int16 flush_data_pcm[23040]; - for (auto &d: flush_data) { - if (!d.decoder) { - continue; - } - for (const auto &decoder_ctl: d.pending_decoder_ctls) { - decoder_ctl(*d.decoder); - } + opus_int32 pcm_mix[23040] = {0}; + size_t park_count = 0; + int max_samples = 0; + int samples = 0; + + opus_int16 flush_data_pcm[23040]; + for (auto &d: flush_data) { + if (!d.decoder) { + continue; + } + for (const auto &decoder_ctl: d.pending_decoder_ctls) { + decoder_ctl(*d.decoder); + } - for (rtp_seq_t seq = d.min_seq; !d.parked_payloads.empty(); ++seq) { - if (d.parked_payloads.top().seq != seq) { - /* - * Lost a packet with sequence number "seq", - * But Opus decoder might be able to guess something. - */ - if (int lost_packet_samples = opus_decode(d.decoder.get(), nullptr, 0, flush_data_pcm, 5760, 0); - lost_packet_samples >= 0) { + for (rtp_seq_t seq = d.min_seq; !d.parked_payloads.empty(); ++seq) { + if (d.parked_payloads.top().seq != seq) { /* - * Since this sample comes from a lost packet, - * we can only pretend there is an event, without any raw payload byte. + * Lost a packet with sequence number "seq", + * But Opus decoder might be able to guess something. */ - 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)); - - park_count = audio_mix(client, *client.mixer, pcm_mix, flush_data_pcm, park_count, lost_packet_samples, max_samples); - client.creator->on_voice_receive.call(vr); - } - } else { - voice_receive_t &vr = *d.parked_payloads.top().vr; - - /* - * We do decryption here to avoid blocking ssl_client and saving cpu time by doing it when needed only. - * - * NOTE: You do not want to send audio while also listening for on_voice_receive/on_voice_receive_combined. - * It will cause gaps in your recording, I have no idea why exactly. - */ - - constexpr size_t header_size = 12; - - uint8_t *buffer = vr.audio_data.data(); - size_t packet_size = vr.audio_data.size(); + if (int lost_packet_samples = opus_decode(d.decoder.get(), nullptr, 0, flush_data_pcm, 5760, 0); + lost_packet_samples >= 0) { + /* + * 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(client.creator, 0, "", &client, d.user_id, + reinterpret_cast(flush_data_pcm), + lost_packet_samples * opus_channel_count * sizeof(opus_int16)); + + park_count = audio_mix(client, *client.mixer, pcm_mix, flush_data_pcm, park_count, lost_packet_samples, max_samples); + client.creator->on_voice_receive.call(vr); + } + } else { + voice_receive_t &vr = *d.parked_payloads.top().vr; - constexpr size_t nonce_size = sizeof(uint32_t); - /* Nonce is 4 byte at the end of payload with zero padding */ - uint8_t nonce[24] = { 0 }; - std::memcpy(nonce, buffer + packet_size - nonce_size, nonce_size); + /* + * We do decryption here to avoid blocking ssl_client and saving cpu time by doing it when needed only. + * + * NOTE: You do not want to send audio while also listening for on_voice_receive/on_voice_receive_combined. + * It will cause gaps in your recording, I have no idea why exactly. + */ - /* Get the number of CSRC in header */ - const size_t csrc_count = buffer[0] & 0b0000'1111; - /* Skip to the encrypted voice data */ - const ptrdiff_t offset_to_data = header_size + sizeof(uint32_t) * csrc_count; - size_t total_header_len = offset_to_data; + constexpr size_t header_size = 12; + + uint8_t *buffer = vr.audio_data.data(); + size_t packet_size = vr.audio_data.size(); + + constexpr size_t nonce_size = sizeof(uint32_t); + /* Nonce is 4 byte at the end of payload with zero padding */ + uint8_t nonce[24] = {0}; + std::memcpy(nonce, buffer + packet_size - nonce_size, nonce_size); + + /* Get the number of CSRC in header */ + const size_t csrc_count = buffer[0] & 0b0000'1111; + /* Skip to the encrypted voice data */ + const ptrdiff_t offset_to_data = header_size + sizeof(uint32_t) * csrc_count; + size_t total_header_len = offset_to_data; + + uint8_t *ciphertext = buffer + offset_to_data; + size_t ciphertext_len = packet_size - offset_to_data - nonce_size; + + size_t ext_len = 0; + if ([[maybe_unused]] const bool uses_extension = (buffer[0] >> 4) & 0b0001) { + /** + * Get the RTP Extensions size, we only get the size here because + * the extension itself is encrypted along with the opus packet + */ + { + uint16_t ext_len_in_words; + memcpy(&ext_len_in_words, &ciphertext[2], sizeof(uint16_t)); + ext_len_in_words = ntohs(ext_len_in_words); + ext_len = sizeof(uint32_t) * ext_len_in_words; + } + constexpr size_t ext_header_len = sizeof(uint16_t) * 2; + ciphertext += ext_header_len; + ciphertext_len -= ext_header_len; + total_header_len += ext_header_len; + } - uint8_t* ciphertext = buffer + offset_to_data; - size_t ciphertext_len = packet_size - offset_to_data - nonce_size; + uint8_t decrypted[65535] = {0}; + unsigned long long opus_packet_len = 0; + if (ssl_crypto_aead_xchacha20poly1305_ietf_decrypt( + decrypted, &opus_packet_len, + nullptr, + ciphertext, ciphertext_len, + buffer, + /** + * Additional Data: + * The whole header (including csrc list) + + * 4 byte extension header (magic 0xBEDE + 16-bit denoting extension length) + */ + total_header_len, + nonce, vr.voice_client->secret_key.data()) != 0) { + /* Invalid Discord RTP payload. */ + return; + } - size_t ext_len = 0; - if ([[maybe_unused]] const bool uses_extension = (buffer[0] >> 4) & 0b0001) { - /** - * Get the RTP Extensions size, we only get the size here because - * the extension itself is encrypted along with the opus packet - */ - { - uint16_t ext_len_in_words; - memcpy(&ext_len_in_words, &ciphertext[2], sizeof(uint16_t)); - ext_len_in_words = ntohs(ext_len_in_words); - ext_len = sizeof(uint32_t) * ext_len_in_words; + uint8_t *opus_packet = decrypted; + if (ext_len > 0) { + /* Skip previously encrypted RTP Header Extension */ + opus_packet += ext_len; + opus_packet_len -= ext_len; } - constexpr size_t ext_header_len = sizeof(uint16_t) * 2; - ciphertext += ext_header_len; - ciphertext_len -= ext_header_len; - total_header_len += ext_header_len; - } - uint8_t decrypted[65535] = { 0 }; - unsigned long long opus_packet_len = 0; - if (ssl_crypto_aead_xchacha20poly1305_ietf_decrypt( - decrypted, &opus_packet_len, - nullptr, - ciphertext, ciphertext_len, - buffer, /** - * Additional Data: - * The whole header (including csrc list) + - * 4 byte extension header (magic 0xBEDE + 16-bit denoting extension length) + * If DAVE is enabled, use the user's ratchet to decrypt the OPUS audio data */ - total_header_len, - nonce, vr.voice_client->secret_key.data()) != 0) { - /* Invalid Discord RTP payload. */ - return; - } - - uint8_t *opus_packet = decrypted; - if (ext_len > 0) { - /* Skip previously encrypted RTP Header Extension */ - opus_packet += ext_len; - opus_packet_len -= ext_len; - } - - /** - * If DAVE is enabled, use the user's ratchet to decrypt the OPUS audio data - */ - std::vector decrypted_dave_frame; - if (vr.voice_client->is_end_to_end_encrypted()) { - auto decryptor = vr.voice_client->mls_state->decryptors.find(vr.user_id); - - if (decryptor != vr.voice_client->mls_state->decryptors.end()) { - decrypted_dave_frame.resize(decryptor->second->get_max_plaintext_byte_size(dave::media_type::media_audio, opus_packet_len)); - - size_t enc_len = decryptor->second->decrypt( - dave::media_type::media_audio, - dave::make_array_view(opus_packet, opus_packet_len), - dave::make_array_view(decrypted_dave_frame) - ); - - if (enc_len > 0) { - opus_packet = decrypted_dave_frame.data(); - opus_packet_len = enc_len; + std::vector decrypted_dave_frame; + if (vr.voice_client->is_end_to_end_encrypted()) { + auto decryptor = vr.voice_client->mls_state->decryptors.find(vr.user_id); + + if (decryptor != vr.voice_client->mls_state->decryptors.end()) { + decrypted_dave_frame.resize(decryptor->second->get_max_plaintext_byte_size(dave::media_type::media_audio, opus_packet_len)); + + size_t enc_len = decryptor->second->decrypt( + dave::media_type::media_audio, + dave::make_array_view(opus_packet, opus_packet_len), + dave::make_array_view(decrypted_dave_frame) + ); + + if (enc_len > 0) { + opus_packet = decrypted_dave_frame.data(); + opus_packet_len = enc_len; + } } } - } - if (opus_packet_len > 0x7FFFFFFF) { - throw dpp::length_exception(err_massive_audio, "audio_data > 2GB! This should never happen!"); - } + if (opus_packet_len > 0x7FFFFFFF) { + throw dpp::length_exception(err_massive_audio, "audio_data > 2GB! This should never happen!"); + } - samples = opus_decode(d.decoder.get(), opus_packet, static_cast(opus_packet_len & 0x7FFFFFFF), flush_data_pcm, 5760, 0); + samples = opus_decode(d.decoder.get(), opus_packet, static_cast(opus_packet_len & 0x7FFFFFFF), flush_data_pcm, 5760, 0); - if (samples >= 0) { - vr.reassign(&client, d.user_id, reinterpret_cast(flush_data_pcm), samples * opus_channel_count * sizeof(opus_int16)); + if (samples >= 0) { + vr.reassign(&client, d.user_id, reinterpret_cast(flush_data_pcm), samples * opus_channel_count * sizeof(opus_int16)); - client.end_gain = 1.0f / client.moving_average; - park_count = audio_mix(client, *client.mixer, pcm_mix, flush_data_pcm, park_count, samples, max_samples); + client.end_gain = 1.0f / client.moving_average; + park_count = audio_mix(client, *client.mixer, pcm_mix, flush_data_pcm, park_count, samples, max_samples); - client.creator->on_voice_receive.call(vr); - } + client.creator->on_voice_receive.call(vr); + } - d.parked_payloads.pop(); + d.parked_payloads.pop(); + } } } - } - /* If combined receive is bound, dispatch it */ - if (park_count) { - /* Downsample the 32 bit samples back to 16 bit */ - opus_int16 pcm_downsample[23040] = {0}; - opus_int16 *pcm_downsample_ptr = pcm_downsample; - opus_int32 *pcm_mix_ptr = pcm_mix; - client.increment = (client.end_gain - client.current_gain) / static_cast(samples); - - for (int64_t x = 0; x < (samples * opus_channel_count) / client.mixer->byte_blocks_per_register; ++x) { - client.mixer->collect_single_register(pcm_mix_ptr, pcm_downsample_ptr, client.current_gain, client.increment); - client.current_gain += client.increment * static_cast(client.mixer->byte_blocks_per_register); - pcm_mix_ptr += client.mixer->byte_blocks_per_register; - pcm_downsample_ptr += client.mixer->byte_blocks_per_register; - } + /* If combined receive is bound, dispatch it */ + if (park_count) { + /* Downsample the 32 bit samples back to 16 bit */ + opus_int16 pcm_downsample[23040] = {0}; + opus_int16 *pcm_downsample_ptr = pcm_downsample; + opus_int32 *pcm_mix_ptr = pcm_mix; + client.increment = (client.end_gain - client.current_gain) / static_cast(samples); + + for (int64_t x = 0; x < (samples * opus_channel_count) / client.mixer->byte_blocks_per_register; ++x) { + client.mixer->collect_single_register(pcm_mix_ptr, pcm_downsample_ptr, client.current_gain, client.increment); + client.current_gain += client.increment * static_cast(client.mixer->byte_blocks_per_register); + pcm_mix_ptr += client.mixer->byte_blocks_per_register; + pcm_downsample_ptr += client.mixer->byte_blocks_per_register; + } - voice_receive_t vr(client.owner, 0, "", &client, 0, reinterpret_cast(pcm_downsample), - max_samples * opus_channel_count * sizeof(opus_int16)); + 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); + client.creator->on_voice_receive_combined.call(vr); + } } } + catch (const std::exception& e) { + client.creator->log(ll_critical, "Voice courier unhandled exception: " + std::string(e.what())); + } } } diff --git a/src/dpp/voice/enabled/read_ready.cpp b/src/dpp/voice/enabled/read_ready.cpp index e499de3f5d..1ef031f47f 100644 --- a/src/dpp/voice/enabled/read_ready.cpp +++ b/src/dpp/voice/enabled/read_ready.cpp @@ -126,9 +126,7 @@ void discord_voice_client::read_ready() if (!voice_courier.joinable()) { /* Courier thread is not running, start it */ - voice_courier = std::thread(&voice_courier_loop, - std::ref(*this), - std::ref(voice_courier_shared_state)); + voice_courier = std::thread(&voice_courier_loop, std::ref(*this), std::ref(voice_courier_shared_state)); } } diff --git a/src/unittest/test.cpp b/src/unittest/test.cpp index c804ff91a7..1d4a24027c 100644 --- a/src/unittest/test.cpp +++ b/src/unittest/test.cpp @@ -72,19 +72,21 @@ constexpr bool has_fill_from_json_v = has_fill_from_json::value; /* Unit tests go here */ int main(int argc, char *argv[]) { - std::string token(get_token()); - - std::cout << "[" << std::fixed << std::setprecision(3) << (dpp::utility::time_f() - get_start_time()) << "]: [\u001b[36mSTART\u001b[0m] "; - if (offline) { - std::cout << "Running offline unit tests only.\n"; - } else { - if (argc > 1 && std::find_if(argv + 1, argv + argc, [](const char *a){ return (std::strcmp(a, "full") == 0); }) != argv + argc) { - extended = true; + try { + + std::string token(get_token()); + + std::cout << "[" << std::fixed << std::setprecision(3) << (dpp::utility::time_f() - get_start_time()) << "]: [\u001b[36mSTART\u001b[0m] "; + if (offline) { + std::cout << "Running offline unit tests only.\n"; + } else { + if (argc > 1 && std::find_if(argv + 1, argv + argc, [](const char *a) { return (std::strcmp(a, "full") == 0); }) != argv + argc) { + extended = true; + } + std::cout << "Running offline and " << (extended ? "extended" : "limited") << " online unit tests. Guild ID: " << TEST_GUILD_ID << " Text Channel ID: " << TEST_TEXT_CHANNEL_ID << " VC ID: " << TEST_VC_ID << " User ID: " << TEST_USER_ID << " Event ID: " << TEST_EVENT_ID << "\n"; } - std::cout << "Running offline and " << (extended ? "extended" : "limited") << " online unit tests. Guild ID: " << TEST_GUILD_ID << " Text Channel ID: " << TEST_TEXT_CHANNEL_ID << " VC ID: " << TEST_VC_ID << " User ID: " << TEST_USER_ID << " Event ID: " << TEST_EVENT_ID << "\n"; - } - std::string test_to_escape = "*** _This is a test_ ***\n```cpp\n\ + std::string test_to_escape = "*** _This is a test_ ***\n```cpp\n\ int main() {\n\ /* Comment */\n\ int answer = 42;\n\ @@ -93,38 +95,38 @@ int main() {\n\ ```\n\ Markdown lol ||spoiler|| ~~strikethrough~~ `small *code* block`\n"; - set_test(COMPARISON, false); - dpp::user u1; - dpp::user u2; - dpp::user u3; - u1.id = u2.id = 666; - u3.id = 777; - set_test(COMPARISON, u1 == u2 && u1 != u3); - - set_test(BIGNUM, false); - std::string big_in{"1234567890123456789012345678901234567890"}; - dpp::bignumber big(big_in); - std::string returned = big.get_number(); - set_test(BIGNUM, big_in == returned); - - set_test(BIGNUM2, false); - std::vector vec{0xff00ff00ff00ff00, 0x1122334455667788}; - dpp::bignumber big2(vec); - returned = big2.get_number(true); - set_test(BIGNUM2, dpp::lowercase(returned) == "1122334455667788ff00ff00ff00ff00"); - - set_test(BIGNUM3, false); - std::vector ret_bin = big2.get_binary(); - set_test(BIGNUM3, ret_bin.size() == 2 && ret_bin[0] == 0xff00ff00ff00ff00 && ret_bin[1] == 0x1122334455667788); - - set_test(ERRORS, false); - - /* Prepare a confirmation_callback_t in error state (400) */ - dpp::confirmation_callback_t error_test; - bool error_message_success = false; - error_test.http_info.status = 400; - - error_test.http_info.body = "{\ + set_test(COMPARISON, false); + dpp::user u1; + dpp::user u2; + dpp::user u3; + u1.id = u2.id = 666; + u3.id = 777; + set_test(COMPARISON, u1 == u2 && u1 != u3); + + set_test(BIGNUM, false); + std::string big_in{"1234567890123456789012345678901234567890"}; + dpp::bignumber big(big_in); + std::string returned = big.get_number(); + set_test(BIGNUM, big_in == returned); + + set_test(BIGNUM2, false); + std::vector vec{0xff00ff00ff00ff00, 0x1122334455667788}; + dpp::bignumber big2(vec); + returned = big2.get_number(true); + set_test(BIGNUM2, dpp::lowercase(returned) == "1122334455667788ff00ff00ff00ff00"); + + set_test(BIGNUM3, false); + std::vector ret_bin = big2.get_binary(); + set_test(BIGNUM3, ret_bin.size() == 2 && ret_bin[0] == 0xff00ff00ff00ff00 && ret_bin[1] == 0x1122334455667788); + + set_test(ERRORS, false); + + /* Prepare a confirmation_callback_t in error state (400) */ + dpp::confirmation_callback_t error_test; + bool error_message_success = false; + error_test.http_info.status = 400; + + error_test.http_info.body = "{\ \"message\": \"Invalid Form Body\",\ \"code\": 50035,\ \"errors\": {\ @@ -146,9 +148,9 @@ Markdown lol ||spoiler|| ~~strikethrough~~ `small *code* block`\n"; }\ }\ }"; - error_message_success = (error_test.get_error().human_readable == "50035: Invalid Form Body\n\t- options[0].name: String value did not match validation regex. (STRING_TYPE_REGEX)\n\t- options[0].name: Command name is invalid (APPLICATION_COMMAND_INVALID_NAME)"); + error_message_success = (error_test.get_error().human_readable == "50035: Invalid Form Body\n\t- options[0].name: String value did not match validation regex. (STRING_TYPE_REGEX)\n\t- options[0].name: Command name is invalid (APPLICATION_COMMAND_INVALID_NAME)"); - error_test.http_info.body = "{\ + error_test.http_info.body = "{\ \"message\": \"Invalid Form Body\",\ \"code\": 50035,\ \"errors\": {\ @@ -162,15 +164,15 @@ Markdown lol ||spoiler|| ~~strikethrough~~ `small *code* block`\n"; }\ }\ }"; - error_message_success = (error_message_success && error_test.get_error().human_readable == "50035: Invalid Form Body - type: Value must be one of {4, 5, 9, 10, 11}. (BASE_TYPE_CHOICES)"); + error_message_success = (error_message_success && error_test.get_error().human_readable == "50035: Invalid Form Body - type: Value must be one of {4, 5, 9, 10, 11}. (BASE_TYPE_CHOICES)"); - error_test.http_info.body = "{\ + error_test.http_info.body = "{\ \"message\": \"Unknown Guild\",\ \"code\": 10004\ }"; - error_message_success = (error_message_success && error_test.get_error().human_readable == "10004: Unknown Guild"); + error_message_success = (error_message_success && error_test.get_error().human_readable == "10004: Unknown Guild"); - error_test.http_info.body = "{\ + error_test.http_info.body = "{\ \"message\": \"Invalid Form Body\",\ \"code\": 50035,\ \"errors\": {\ @@ -184,9 +186,9 @@ Markdown lol ||spoiler|| ~~strikethrough~~ `small *code* block`\n"; }\ }\ }"; - error_message_success = (error_message_success && error_test.get_error().human_readable == "50035: Invalid Form Body - allowed_mentions: parse:[\"users\"] and users: [ids...] are mutually exclusive. (MESSAGE_ALLOWED_MENTIONS_PARSE_EXCLUSIVE)"); + error_message_success = (error_message_success && error_test.get_error().human_readable == "50035: Invalid Form Body - allowed_mentions: parse:[\"users\"] and users: [ids...] are mutually exclusive. (MESSAGE_ALLOWED_MENTIONS_PARSE_EXCLUSIVE)"); - error_test.http_info.body = "{\ + error_test.http_info.body = "{\ \"message\": \"Invalid Form Body\",\ \"code\": 50035,\ \"errors\": {\ @@ -206,9 +208,9 @@ Markdown lol ||spoiler|| ~~strikethrough~~ `small *code* block`\n"; }\ }\ }"; - error_message_success = (error_message_success && error_test.get_error().human_readable == "50035: Invalid Form Body - [1].options[1].description: Must be between 1 and 100 in length. (BASE_TYPE_BAD_LENGTH)"); + error_message_success = (error_message_success && error_test.get_error().human_readable == "50035: Invalid Form Body - [1].options[1].description: Must be between 1 and 100 in length. (BASE_TYPE_BAD_LENGTH)"); - error_test.http_info.body = "{\ + error_test.http_info.body = "{\ \"message\": \"Invalid Form Body\",\ \"code\": 50035,\ \"errors\": {\ @@ -223,15 +225,15 @@ Markdown lol ||spoiler|| ~~strikethrough~~ `small *code* block`\n"; }\ }\ }"; - error_message_success = (error_message_success && error_test.get_error().human_readable == "50035: Invalid Form Body - data.poll: This poll type cannot include attachments, emoji or stickers with the question (POLL_TYPE_QUESTION_ALLOWS_TEXT_ONLY)"); + error_message_success = (error_message_success && error_test.get_error().human_readable == "50035: Invalid Form Body - data.poll: This poll type cannot include attachments, emoji or stickers with the question (POLL_TYPE_QUESTION_ALLOWS_TEXT_ONLY)"); - set_test(ERRORS, error_message_success); + set_test(ERRORS, error_message_success); - set_test(MD_ESC_1, false); - set_test(MD_ESC_2, false); - std::string escaped1 = dpp::utility::markdown_escape(test_to_escape); - std::string escaped2 = dpp::utility::markdown_escape(test_to_escape, true); - set_test(MD_ESC_1, escaped1 == "\\*\\*\\* \\_This is a test\\_ \\*\\*\\*\n\ + set_test(MD_ESC_1, false); + set_test(MD_ESC_2, false); + std::string escaped1 = dpp::utility::markdown_escape(test_to_escape); + std::string escaped2 = dpp::utility::markdown_escape(test_to_escape, true); + set_test(MD_ESC_1, escaped1 == "\\*\\*\\* \\_This is a test\\_ \\*\\*\\*\n\ ```cpp\n\ int main() {\n\ /* Comment */\n\ @@ -240,7 +242,7 @@ int main() {\n\ };\n\ ```\n\ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ `small *code* block`\n"); - set_test(MD_ESC_2, escaped2 == "\\*\\*\\* \\_This is a test\\_ \\*\\*\\*\n\ + set_test(MD_ESC_2, escaped2 == "\\*\\*\\* \\_This is a test\\_ \\*\\*\\*\n\ \\`\\`\\`cpp\n\ int main\\(\\) {\n\ /\\* Comment \\*/\n\ @@ -250,459 +252,459 @@ int main\\(\\) {\n\ \\`\\`\\`\n\ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* block\\`\n"); - set_test(URLENC, false); - set_test(URLENC, dpp::utility::url_encode("ABC123_+\\|$*/AAA[]😄") == "ABC123_%2B%5C%7C%24%2A%2FAAA%5B%5D%F0%9F%98%84"); - - set_test(BASE64ENC, false); - set_test(BASE64ENC, - dpp::base64_encode(reinterpret_cast("a"), 1) == "YQ==" && - dpp::base64_encode(reinterpret_cast("bc"), 2) == "YmM=" && - dpp::base64_encode(reinterpret_cast("def"), 3) == "ZGVm" && - dpp::base64_encode(reinterpret_cast("ghij"), 4) == "Z2hpag==" && - dpp::base64_encode(reinterpret_cast("klmno"), 5) == "a2xtbm8=" && - dpp::base64_encode(reinterpret_cast("pqrstu"), 6) == "cHFyc3R1" && - dpp::base64_encode(reinterpret_cast("vwxyz12"), 7) == "dnd4eXoxMg==" - ); - - dpp::http_connect_info hci; - set_test(HOSTINFO, false); - - hci = dpp::https_client::get_host_info("https://test.com:444"); - bool hci_test = (hci.scheme == "https" && hci.hostname == "test.com" && hci.port == 444 && hci.is_ssl == true); - - hci = dpp::https_client::get_host_info("https://test.com"); - hci_test = hci_test && (hci.scheme == "https" && hci.hostname == "test.com" && hci.port == 443 && hci.is_ssl == true); - - hci = dpp::https_client::get_host_info("http://test.com"); - hci_test = hci_test && (hci.scheme == "http" && hci.hostname == "test.com" && hci.port == 80 && hci.is_ssl == false); - - hci = dpp::https_client::get_host_info("http://test.com:90"); - hci_test = hci_test && (hci.scheme == "http" && hci.hostname == "test.com" && hci.port == 90 && hci.is_ssl == false); - - hci = dpp::https_client::get_host_info("test.com:97"); - hci_test = hci_test && (hci.scheme == "http" && hci.hostname == "test.com" && hci.port == 97 && hci.is_ssl == false); - - hci = dpp::https_client::get_host_info("test.com"); - hci_test = hci_test && (hci.scheme == "http" && hci.hostname == "test.com" && hci.port == 80 && hci.is_ssl == false); - - set_test(HOSTINFO, hci_test); - - std::vector testaudio = load_test_audio(); - - set_test(READFILE, false); - std::string rf_test = dpp::utility::read_file(SHARED_OBJECT); - FILE* fp = fopen(SHARED_OBJECT, "rb"); - fseek(fp, 0, SEEK_END); - size_t off = (size_t)ftell(fp); - fclose(fp); - set_test(READFILE, off == rf_test.length()); - - set_test(TIMESTAMPTOSTRING, false); - set_test(TIMESTAMPTOSTRING, dpp::ts_to_string(1642611864) == "2022-01-19T17:04:24Z"); - - { - set_test(ROLE_COMPARE, false); - dpp::role role_1, role_2; - role_1.id = 99; - role_1.position = 1; - role_1.guild_id = 123; - role_1.id = 98; - role_2.position = 2; - role_2.guild_id = 123; - set_test(ROLE_COMPARE, role_1 < role_2 && !(role_1 > role_2) && role_1 != role_2); - } - { - set_test(ROLE_COMPARE_CONSIDERING_ID, false); - dpp::role role_1, role_2; - role_1.id = 99; - role_1.position = 2; - role_1.guild_id = 123; - role_2.id = 98; - role_2.position = 2; - role_2.guild_id = 123; - set_test(ROLE_COMPARE_CONSIDERING_ID, role_1 < role_2 && !(role_1 > role_2)); - } + set_test(URLENC, false); + set_test(URLENC, dpp::utility::url_encode("ABC123_+\\|$*/AAA[]😄") == "ABC123_%2B%5C%7C%24%2A%2FAAA%5B%5D%F0%9F%98%84"); + + set_test(BASE64ENC, false); + set_test(BASE64ENC, + dpp::base64_encode(reinterpret_cast("a"), 1) == "YQ==" && + dpp::base64_encode(reinterpret_cast("bc"), 2) == "YmM=" && + dpp::base64_encode(reinterpret_cast("def"), 3) == "ZGVm" && + dpp::base64_encode(reinterpret_cast("ghij"), 4) == "Z2hpag==" && + dpp::base64_encode(reinterpret_cast("klmno"), 5) == "a2xtbm8=" && + dpp::base64_encode(reinterpret_cast("pqrstu"), 6) == "cHFyc3R1" && + dpp::base64_encode(reinterpret_cast("vwxyz12"), 7) == "dnd4eXoxMg==" + ); + + dpp::http_connect_info hci; + set_test(HOSTINFO, false); + + hci = dpp::https_client::get_host_info("https://test.com:444"); + bool hci_test = (hci.scheme == "https" && hci.hostname == "test.com" && hci.port == 444 && hci.is_ssl == true); + + hci = dpp::https_client::get_host_info("https://test.com"); + hci_test = hci_test && (hci.scheme == "https" && hci.hostname == "test.com" && hci.port == 443 && hci.is_ssl == true); + + hci = dpp::https_client::get_host_info("http://test.com"); + hci_test = hci_test && (hci.scheme == "http" && hci.hostname == "test.com" && hci.port == 80 && hci.is_ssl == false); + + hci = dpp::https_client::get_host_info("http://test.com:90"); + hci_test = hci_test && (hci.scheme == "http" && hci.hostname == "test.com" && hci.port == 90 && hci.is_ssl == false); + + hci = dpp::https_client::get_host_info("test.com:97"); + hci_test = hci_test && (hci.scheme == "http" && hci.hostname == "test.com" && hci.port == 97 && hci.is_ssl == false); + + hci = dpp::https_client::get_host_info("test.com"); + hci_test = hci_test && (hci.scheme == "http" && hci.hostname == "test.com" && hci.port == 80 && hci.is_ssl == false); + + set_test(HOSTINFO, hci_test); + + std::vector testaudio = load_test_audio(); + + set_test(READFILE, false); + std::string rf_test = dpp::utility::read_file(SHARED_OBJECT); + FILE *fp = fopen(SHARED_OBJECT, "rb"); + fseek(fp, 0, SEEK_END); + size_t off = (size_t) ftell(fp); + fclose(fp); + set_test(READFILE, off == rf_test.length()); + + set_test(TIMESTAMPTOSTRING, false); + set_test(TIMESTAMPTOSTRING, dpp::ts_to_string(1642611864) == "2022-01-19T17:04:24Z"); + + { + set_test(ROLE_COMPARE, false); + dpp::role role_1, role_2; + role_1.id = 99; + role_1.position = 1; + role_1.guild_id = 123; + role_1.id = 98; + role_2.position = 2; + role_2.guild_id = 123; + set_test(ROLE_COMPARE, role_1 < role_2 && !(role_1 > role_2) && role_1 != role_2); + } + { + set_test(ROLE_COMPARE_CONSIDERING_ID, false); + dpp::role role_1, role_2; + role_1.id = 99; + role_1.position = 2; + role_1.guild_id = 123; + role_2.id = 98; + role_2.position = 2; + role_2.guild_id = 123; + set_test(ROLE_COMPARE_CONSIDERING_ID, role_1 < role_2 && !(role_1 > role_2)); + } - set_test(WEBHOOK, false); - try { - dpp::webhook test_wh("https://discord.com/api/webhooks/833047646548133537/ntCHEYYIoHSLy_GOxPx6pmM0sUoLbP101ct-WI6F-S4beAV2vaIcl_Id5loAMyQwxqhE"); - set_test(WEBHOOK, (test_wh.token == "ntCHEYYIoHSLy_GOxPx6pmM0sUoLbP101ct-WI6F-S4beAV2vaIcl_Id5loAMyQwxqhE") && (test_wh.id == dpp::snowflake(833047646548133537))); - } - catch (const dpp::exception&) { set_test(WEBHOOK, false); - } + try { + dpp::webhook test_wh("https://discord.com/api/webhooks/833047646548133537/ntCHEYYIoHSLy_GOxPx6pmM0sUoLbP101ct-WI6F-S4beAV2vaIcl_Id5loAMyQwxqhE"); + set_test(WEBHOOK, (test_wh.token == "ntCHEYYIoHSLy_GOxPx6pmM0sUoLbP101ct-WI6F-S4beAV2vaIcl_Id5loAMyQwxqhE") && (test_wh.id == dpp::snowflake(833047646548133537))); + } + catch (const dpp::exception &) { + set_test(WEBHOOK, false); + } - { // test dpp::snowflake - start_test(SNOWFLAKE); - bool success = true; - dpp::snowflake s = 69420; - json j; - j["value"] = s; - success = dpp::snowflake_not_null(&j, "value") == 69420 && success; - DPP_CHECK_CONSTRUCT_ASSIGN(SNOWFLAKE, dpp::snowflake, success); - s = 42069; - success = success && (s == 42069 && s == dpp::snowflake{42069} && s == "42069"); - success = success && (dpp::snowflake{69} < dpp::snowflake{420} && (dpp::snowflake{69} < 420)); - s = "69420"; - success = success && s == 69420; - auto conversion_test = [](dpp::snowflake sl) { - return sl.str(); - }; - s = conversion_test(std::string{"1337"}); - success = success && s == 1337; /* THIS BREAKS (and i do not care very much): && s == conversion_test(dpp::snowflake{"1337"}); */ - success = success && dpp::snowflake{0} == 0; - set_test(SNOWFLAKE, success); - } + { // test dpp::snowflake + start_test(SNOWFLAKE); + bool success = true; + dpp::snowflake s = 69420; + json j; + j["value"] = s; + success = dpp::snowflake_not_null(&j, "value") == 69420 && success; + DPP_CHECK_CONSTRUCT_ASSIGN(SNOWFLAKE, dpp::snowflake, success); + s = 42069; + success = success && (s == 42069 && s == dpp::snowflake{42069} && s == "42069"); + success = success && (dpp::snowflake{69} < dpp::snowflake{420} && (dpp::snowflake{69} < 420)); + s = "69420"; + success = success && s == 69420; + auto conversion_test = [](dpp::snowflake sl) { + return sl.str(); + }; + s = conversion_test(std::string{"1337"}); + success = success && s == 1337; /* THIS BREAKS (and i do not care very much): && s == conversion_test(dpp::snowflake{"1337"}); */ + success = success && dpp::snowflake{0} == 0; + set_test(SNOWFLAKE, success); + } - { // test snowflake: std::format support - - #ifdef DPP_FORMATTERS - set_test(SNOWFLAKE_STD_FORMAT, - std::format("{}",dpp::snowflake{}) == "0" && - std::format("{}",dpp::snowflake{12345}) == "12345" && - std::format("{} hello {}", dpp::snowflake{12345}, dpp::snowflake{54321}) == "12345 hello 54321" - ); - #else - set_status(SNOWFLAKE_STD_FORMAT,ts_skipped); - #endif // DPP_FORMATTERS - }; - - { // test dpp::json_interface - start_test(JSON_INTERFACE); - struct fillable : dpp::json_interface { - fillable &fill_from_json_impl(dpp::json *) { - return *this; - } - }; - struct buildable : dpp::json_interface { - json to_json_impl(bool = false) const { - return {}; - } - }; - struct fillable_and_buildable : dpp::json_interface { - fillable_and_buildable &fill_from_json_impl(dpp::json *) { - return *this; - } + { // test snowflake: std::format support - json to_json_impl(bool = false) const { - return {}; - } +#ifdef DPP_FORMATTERS + set_test(SNOWFLAKE_STD_FORMAT, + std::format("{}",dpp::snowflake{}) == "0" && + std::format("{}",dpp::snowflake{12345}) == "12345" && + std::format("{} hello {}", dpp::snowflake{12345}, dpp::snowflake{54321}) == "12345 hello 54321" + ); +#else + set_status(SNOWFLAKE_STD_FORMAT, ts_skipped); +#endif // DPP_FORMATTERS }; - bool success = true; - - DPP_CHECK(JSON_INTERFACE, has_build_json_v>, success); - DPP_CHECK(JSON_INTERFACE, !has_fill_from_json_v>, success); - DPP_CHECK(JSON_INTERFACE, has_build_json_v, success); - DPP_CHECK(JSON_INTERFACE, !has_fill_from_json_v, success); - - DPP_CHECK(JSON_INTERFACE, !has_build_json_v>, success); - DPP_CHECK(JSON_INTERFACE, has_fill_from_json_v>, success); - DPP_CHECK(JSON_INTERFACE, !has_build_json_v, success); - DPP_CHECK(JSON_INTERFACE, has_fill_from_json_v, success); - - DPP_CHECK(JSON_INTERFACE, has_build_json_v>, success); - DPP_CHECK(JSON_INTERFACE, has_fill_from_json_v>, success); - DPP_CHECK(JSON_INTERFACE, has_build_json_v, success); - DPP_CHECK(JSON_INTERFACE, has_fill_from_json_v, success); - set_test(JSON_INTERFACE, success); - } - { // test interaction_create_t::get_parameter - // create a fake interaction - dpp::cluster cluster(""); - dpp::discord_client client(&cluster, 1, 1, ""); - dpp::interaction_create_t interaction(nullptr, 0, ""); + { // test dpp::json_interface + start_test(JSON_INTERFACE); + struct fillable : dpp::json_interface { + fillable &fill_from_json_impl(dpp::json *) { + return *this; + } + }; + struct buildable : dpp::json_interface { + json to_json_impl(bool = false) const { + return {}; + } + }; + struct fillable_and_buildable : dpp::json_interface { + fillable_and_buildable &fill_from_json_impl(dpp::json *) { + return *this; + } + + json to_json_impl(bool = false) const { + return {}; + } + }; + bool success = true; + + DPP_CHECK(JSON_INTERFACE, has_build_json_v>, success); + DPP_CHECK(JSON_INTERFACE, !has_fill_from_json_v>, success); + DPP_CHECK(JSON_INTERFACE, has_build_json_v, success); + DPP_CHECK(JSON_INTERFACE, !has_fill_from_json_v, success); + + DPP_CHECK(JSON_INTERFACE, !has_build_json_v>, success); + DPP_CHECK(JSON_INTERFACE, has_fill_from_json_v>, success); + DPP_CHECK(JSON_INTERFACE, !has_build_json_v, success); + DPP_CHECK(JSON_INTERFACE, has_fill_from_json_v, success); + + DPP_CHECK(JSON_INTERFACE, has_build_json_v>, success); + DPP_CHECK(JSON_INTERFACE, has_fill_from_json_v>, success); + DPP_CHECK(JSON_INTERFACE, has_build_json_v, success); + DPP_CHECK(JSON_INTERFACE, has_fill_from_json_v, success); + set_test(JSON_INTERFACE, success); + } - /* Check the method with subcommands */ - set_test(GET_PARAMETER_WITH_SUBCOMMANDS, false); + { // test interaction_create_t::get_parameter + // create a fake interaction + dpp::cluster cluster(""); + dpp::discord_client client(&cluster, 1, 1, ""); + dpp::interaction_create_t interaction(nullptr, 0, ""); - dpp::command_interaction cmd_data; // command - cmd_data.type = dpp::ctxm_chat_input; - cmd_data.name = "command"; + /* Check the method with subcommands */ + set_test(GET_PARAMETER_WITH_SUBCOMMANDS, false); - dpp::command_data_option subcommandgroup; // subcommand group - subcommandgroup.name = "group"; - subcommandgroup.type = dpp::co_sub_command_group; + dpp::command_interaction cmd_data; // command + cmd_data.type = dpp::ctxm_chat_input; + cmd_data.name = "command"; - dpp::command_data_option subcommand; // subcommand - subcommand.name = "add"; - subcommand.type = dpp::co_sub_command; + dpp::command_data_option subcommandgroup; // subcommand group + subcommandgroup.name = "group"; + subcommandgroup.type = dpp::co_sub_command_group; - dpp::command_data_option option1; // slashcommand option - option1.name = "user"; - option1.type = dpp::co_user; - option1.value = dpp::snowflake(189759562910400512); + dpp::command_data_option subcommand; // subcommand + subcommand.name = "add"; + subcommand.type = dpp::co_sub_command; - dpp::command_data_option option2; // slashcommand option - option2.name = "checked"; - option2.type = dpp::co_boolean; - option2.value = true; + dpp::command_data_option option1; // slashcommand option + option1.name = "user"; + option1.type = dpp::co_user; + option1.value = dpp::snowflake(189759562910400512); - // add them - subcommand.options.push_back(option1); - subcommand.options.push_back(option2); - subcommandgroup.options.push_back(subcommand); - cmd_data.options.push_back(subcommandgroup); - interaction.command.data = cmd_data; + dpp::command_data_option option2; // slashcommand option + option2.name = "checked"; + option2.type = dpp::co_boolean; + option2.value = true; - dpp::snowflake value1 = std::get(interaction.get_parameter("user")); - set_test(GET_PARAMETER_WITH_SUBCOMMANDS, value1 == dpp::snowflake(189759562910400512)); + // add them + subcommand.options.push_back(option1); + subcommand.options.push_back(option2); + subcommandgroup.options.push_back(subcommand); + cmd_data.options.push_back(subcommandgroup); + interaction.command.data = cmd_data; - /* Check the method without subcommands */ - set_test(GET_PARAMETER_WITHOUT_SUBCOMMANDS, false); + dpp::snowflake value1 = std::get(interaction.get_parameter("user")); + set_test(GET_PARAMETER_WITH_SUBCOMMANDS, value1 == dpp::snowflake(189759562910400512)); - dpp::command_interaction cmd_data2; // command - cmd_data2.type = dpp::ctxm_chat_input; - cmd_data2.name = "command"; + /* Check the method without subcommands */ + set_test(GET_PARAMETER_WITHOUT_SUBCOMMANDS, false); - dpp::command_data_option option3; // slashcommand option - option3.name = "number"; - option3.type = dpp::co_integer; - option3.value = int64_t(123456); + dpp::command_interaction cmd_data2; // command + cmd_data2.type = dpp::ctxm_chat_input; + cmd_data2.name = "command"; - cmd_data2.options.push_back(option3); - interaction.command.data = cmd_data2; + dpp::command_data_option option3; // slashcommand option + option3.name = "number"; + option3.type = dpp::co_integer; + option3.value = int64_t(123456); - int64_t value2 = std::get(interaction.get_parameter("number")); - set_test(GET_PARAMETER_WITHOUT_SUBCOMMANDS, value2 == 123456); - } + cmd_data2.options.push_back(option3); + interaction.command.data = cmd_data2; - { // test dpp::command_option_choice::fill_from_json - set_test(OPTCHOICE_DOUBLE, false); - set_test(OPTCHOICE_INT, false); - set_test(OPTCHOICE_BOOL, false); - set_test(OPTCHOICE_SNOWFLAKE, false); - set_test(OPTCHOICE_STRING, false); - json j; - dpp::command_option_choice choice; - j["value"] = 54.321; - choice.fill_from_json(&j); - bool success_double = std::holds_alternative(choice.value); - j["value"] = 8223372036854775807; - choice.fill_from_json(&j); - bool success_int = std::holds_alternative(choice.value); - j["value"] = -8223372036854775807; - choice.fill_from_json(&j); - bool success_int2 = std::holds_alternative(choice.value); - j["value"] = true; - choice.fill_from_json(&j); - bool success_bool = std::holds_alternative(choice.value); - dpp::snowflake s(845266178036516757); // example snowflake - j["value"] = s; - choice.fill_from_json(&j); - bool success_snowflake = std::holds_alternative(choice.value) && std::get(choice.value) == s; - j["value"] = "foobar"; - choice.fill_from_json(&j); - bool success_string = std::holds_alternative(choice.value); - set_test(OPTCHOICE_DOUBLE, success_double); - set_test(OPTCHOICE_INT, success_int && success_int2); - set_test(OPTCHOICE_BOOL, success_bool); - set_test(OPTCHOICE_SNOWFLAKE, success_snowflake); - set_test(OPTCHOICE_STRING, success_string); - } + int64_t value2 = std::get(interaction.get_parameter("number")); + set_test(GET_PARAMETER_WITHOUT_SUBCOMMANDS, value2 == 123456); + } - { - set_test(PERMISSION_CLASS, false); - bool success = false; - auto p = dpp::permission(); - p = 16; - success = p == 16; - p |= 4; - success = p == 20 && success; - p <<= 8; // left shift - success = p == 5120 && success; - auto s = std::to_string(p); - success = s == "5120" && success; - p.set(0).add(~uint64_t{0}).remove(dpp::p_speak).set(dpp::p_administrator); - success = !p.has(dpp::p_administrator, dpp::p_ban_members) && success; // must return false because they're not both set - success = !p.has(dpp::p_administrator | dpp::p_ban_members) && success; - success = p.can(dpp::p_ban_members) && success; - success = p.can(dpp::p_speak) && success; - - constexpr auto permission_test = [](dpp::permission p) constexpr noexcept { - bool success{true}; - - p.set(0).add(~uint64_t{0}).remove(dpp::p_speak).set(dpp::p_connect); - p.set(dpp::p_administrator, dpp::p_ban_members); - success = p.has(dpp::p_administrator) && success; - success = p.has(dpp::p_administrator) && p.has(dpp::p_ban_members) && success; - success = p.has(dpp::p_administrator, dpp::p_ban_members) && success; - success = p.has(dpp::p_administrator | dpp::p_ban_members) && success; - success = p.add(dpp::p_speak).has(dpp::p_administrator, dpp::p_speak) && success; - success = !p.remove(dpp::p_speak).has(dpp::p_administrator, dpp::p_speak) && success; - p.remove(dpp::p_administrator); + { // test dpp::command_option_choice::fill_from_json + set_test(OPTCHOICE_DOUBLE, false); + set_test(OPTCHOICE_INT, false); + set_test(OPTCHOICE_BOOL, false); + set_test(OPTCHOICE_SNOWFLAKE, false); + set_test(OPTCHOICE_STRING, false); + json j; + dpp::command_option_choice choice; + j["value"] = 54.321; + choice.fill_from_json(&j); + bool success_double = std::holds_alternative(choice.value); + j["value"] = 8223372036854775807; + choice.fill_from_json(&j); + bool success_int = std::holds_alternative(choice.value); + j["value"] = -8223372036854775807; + choice.fill_from_json(&j); + bool success_int2 = std::holds_alternative(choice.value); + j["value"] = true; + choice.fill_from_json(&j); + bool success_bool = std::holds_alternative(choice.value); + dpp::snowflake s(845266178036516757); // example snowflake + j["value"] = s; + choice.fill_from_json(&j); + bool success_snowflake = std::holds_alternative(choice.value) && std::get(choice.value) == s; + j["value"] = "foobar"; + choice.fill_from_json(&j); + bool success_string = std::holds_alternative(choice.value); + set_test(OPTCHOICE_DOUBLE, success_double); + set_test(OPTCHOICE_INT, success_int && success_int2); + set_test(OPTCHOICE_BOOL, success_bool); + set_test(OPTCHOICE_SNOWFLAKE, success_snowflake); + set_test(OPTCHOICE_STRING, success_string); + } + + { + set_test(PERMISSION_CLASS, false); + bool success = false; + auto p = dpp::permission(); + p = 16; + success = p == 16; + p |= 4; + success = p == 20 && success; + p <<= 8; // left shift + success = p == 5120 && success; + auto s = std::to_string(p); + success = s == "5120" && success; + p.set(0).add(~uint64_t{0}).remove(dpp::p_speak).set(dpp::p_administrator); + success = !p.has(dpp::p_administrator, dpp::p_ban_members) && success; // must return false because they're not both set + success = !p.has(dpp::p_administrator | dpp::p_ban_members) && success; success = p.can(dpp::p_ban_members) && success; - success = !p.can(dpp::p_speak, dpp::p_ban_members) && success; - success = p.can_any(dpp::p_speak, dpp::p_ban_members) && success; - return success; - }; - constexpr auto constexpr_success = permission_test({~uint64_t{0}}); // test in constant evaluated - success = permission_test({~uint64_t{0}}) && constexpr_success && success; // test at runtime - set_test(PERMISSION_CLASS, success); - } + success = p.can(dpp::p_speak) && success; + + constexpr auto permission_test = [](dpp::permission p) constexpr noexcept { + bool success{true}; + + p.set(0).add(~uint64_t{0}).remove(dpp::p_speak).set(dpp::p_connect); + p.set(dpp::p_administrator, dpp::p_ban_members); + success = p.has(dpp::p_administrator) && success; + success = p.has(dpp::p_administrator) && p.has(dpp::p_ban_members) && success; + success = p.has(dpp::p_administrator, dpp::p_ban_members) && success; + success = p.has(dpp::p_administrator | dpp::p_ban_members) && success; + success = p.add(dpp::p_speak).has(dpp::p_administrator, dpp::p_speak) && success; + success = !p.remove(dpp::p_speak).has(dpp::p_administrator, dpp::p_speak) && success; + p.remove(dpp::p_administrator); + success = p.can(dpp::p_ban_members) && success; + success = !p.can(dpp::p_speak, dpp::p_ban_members) && success; + success = p.can_any(dpp::p_speak, dpp::p_ban_members) && success; + return success; + }; + constexpr auto constexpr_success = permission_test({~uint64_t{0}}); // test in constant evaluated + success = permission_test({~uint64_t{0}}) && constexpr_success && success; // test at runtime + set_test(PERMISSION_CLASS, success); + } - { // dpp event classes - start_test(EVENT_CLASS); - bool success = true; - DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::log_t, success); - DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::guild_scheduled_event_user_add_t, success); - DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::guild_scheduled_event_user_remove_t, success); - DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::guild_scheduled_event_create_t, success); - DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::guild_scheduled_event_update_t, success); - DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::guild_scheduled_event_delete_t, success); - DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::automod_rule_create_t, success); - DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::automod_rule_update_t, success); - DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::automod_rule_delete_t, success); - DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::automod_rule_execute_t, success); - DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::stage_instance_create_t, success); - DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::stage_instance_update_t, success); - DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::stage_instance_delete_t, success); - DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::voice_state_update_t, success); - DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::interaction_create_t, success); - DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::slashcommand_t, success); - DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::button_click_t, success); - DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::form_submit_t, success); - DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::autocomplete_t, success); - DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::context_menu_t, success); - DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::message_context_menu_t, success); - DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::user_context_menu_t, success); - DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::select_click_t, success); - DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::guild_delete_t, success); - DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::guild_stickers_update_t, success); - DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::guild_join_request_delete_t, success); - DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::channel_delete_t, success); - DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::channel_update_t, success); - DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::ready_t, success); - DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::message_delete_t, success); - DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::guild_member_remove_t, success); - DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::resumed_t, success); - DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::guild_role_create_t, success); - DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::typing_start_t, success); - DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::voice_track_marker_t, success); - DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::message_reaction_add_t, success); - DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::guild_members_chunk_t, success); - DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::message_reaction_remove_t, success); - DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::guild_create_t, success); - DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::channel_create_t, success); - DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::message_reaction_remove_emoji_t, success); - DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::message_delete_bulk_t, success); - DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::guild_role_update_t, success); - DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::guild_role_delete_t, success); - DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::channel_pins_update_t, success); - DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::message_reaction_remove_all_t, success); - DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::voice_server_update_t, success); - DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::guild_emojis_update_t, success); - DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::presence_update_t, success); - DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::webhooks_update_t, success); - DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::guild_member_add_t, success); - DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::invite_delete_t, success); - DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::guild_update_t, success); - DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::guild_integrations_update_t, success); - DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::guild_member_update_t, success); - DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::invite_create_t, success); - DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::message_update_t, success); - DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::user_update_t, success); - DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::message_create_t, success); - DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::guild_audit_log_entry_create_t, success); - DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::guild_ban_add_t, success); - DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::guild_ban_remove_t, success); - DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::integration_create_t, success); - DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::integration_update_t, success); - DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::integration_delete_t, success); - DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::thread_create_t, success); - DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::thread_update_t, success); - DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::thread_delete_t, success); - DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::thread_list_sync_t, success); - DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::thread_member_update_t, success); - DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::thread_members_update_t, success); - DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::voice_buffer_send_t, success); - DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::voice_ready_t, success); - DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::voice_receive_t, success); - DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::voice_client_speaking_t, success); - DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::voice_client_disconnect_t, success); - set_test(EVENT_CLASS, success); - } + { // dpp event classes + start_test(EVENT_CLASS); + bool success = true; + DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::log_t, success); + DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::guild_scheduled_event_user_add_t, success); + DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::guild_scheduled_event_user_remove_t, success); + DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::guild_scheduled_event_create_t, success); + DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::guild_scheduled_event_update_t, success); + DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::guild_scheduled_event_delete_t, success); + DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::automod_rule_create_t, success); + DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::automod_rule_update_t, success); + DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::automod_rule_delete_t, success); + DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::automod_rule_execute_t, success); + DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::stage_instance_create_t, success); + DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::stage_instance_update_t, success); + DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::stage_instance_delete_t, success); + DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::voice_state_update_t, success); + DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::interaction_create_t, success); + DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::slashcommand_t, success); + DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::button_click_t, success); + DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::form_submit_t, success); + DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::autocomplete_t, success); + DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::context_menu_t, success); + DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::message_context_menu_t, success); + DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::user_context_menu_t, success); + DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::select_click_t, success); + DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::guild_delete_t, success); + DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::guild_stickers_update_t, success); + DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::guild_join_request_delete_t, success); + DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::channel_delete_t, success); + DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::channel_update_t, success); + DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::ready_t, success); + DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::message_delete_t, success); + DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::guild_member_remove_t, success); + DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::resumed_t, success); + DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::guild_role_create_t, success); + DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::typing_start_t, success); + DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::voice_track_marker_t, success); + DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::message_reaction_add_t, success); + DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::guild_members_chunk_t, success); + DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::message_reaction_remove_t, success); + DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::guild_create_t, success); + DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::channel_create_t, success); + DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::message_reaction_remove_emoji_t, success); + DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::message_delete_bulk_t, success); + DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::guild_role_update_t, success); + DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::guild_role_delete_t, success); + DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::channel_pins_update_t, success); + DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::message_reaction_remove_all_t, success); + DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::voice_server_update_t, success); + DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::guild_emojis_update_t, success); + DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::presence_update_t, success); + DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::webhooks_update_t, success); + DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::guild_member_add_t, success); + DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::invite_delete_t, success); + DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::guild_update_t, success); + DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::guild_integrations_update_t, success); + DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::guild_member_update_t, success); + DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::invite_create_t, success); + DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::message_update_t, success); + DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::user_update_t, success); + DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::message_create_t, success); + DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::guild_audit_log_entry_create_t, success); + DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::guild_ban_add_t, success); + DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::guild_ban_remove_t, success); + DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::integration_create_t, success); + DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::integration_update_t, success); + DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::integration_delete_t, success); + DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::thread_create_t, success); + DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::thread_update_t, success); + DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::thread_delete_t, success); + DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::thread_list_sync_t, success); + DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::thread_member_update_t, success); + DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::thread_members_update_t, success); + DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::voice_buffer_send_t, success); + DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::voice_ready_t, success); + DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::voice_receive_t, success); + DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::voice_client_speaking_t, success); + DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::voice_client_disconnect_t, success); + set_test(EVENT_CLASS, success); + } - { // some dpp::user methods - dpp::user user1; - user1.id = 189759562910400512; - user1.discriminator = 0001; - user1.username = "brain"; + { // some dpp::user methods + dpp::user user1; + user1.id = 189759562910400512; + user1.discriminator = 0001; + user1.username = "brain"; - set_test(USER_GET_MENTION, false); - set_test(USER_GET_MENTION, user1.get_mention() == "<@189759562910400512>"); + set_test(USER_GET_MENTION, false); + set_test(USER_GET_MENTION, user1.get_mention() == "<@189759562910400512>"); - set_test(USER_FORMAT_USERNAME, false); - set_test(USER_FORMAT_USERNAME, user1.format_username() == "brain#0001"); + set_test(USER_FORMAT_USERNAME, false); + set_test(USER_FORMAT_USERNAME, user1.format_username() == "brain#0001"); - set_test(USER_GET_CREATION_TIME, false); - set_test(USER_GET_CREATION_TIME, (uint64_t)user1.get_creation_time() == 1465312605); + set_test(USER_GET_CREATION_TIME, false); + set_test(USER_GET_CREATION_TIME, (uint64_t) user1.get_creation_time() == 1465312605); - set_test(USER_GET_URL, false); + set_test(USER_GET_URL, false); - dpp::user user2; - set_test(USER_GET_URL, + dpp::user user2; + set_test(USER_GET_URL, user1.get_url() == dpp::utility::url_host + "/users/189759562910400512" && user2.get_url() == "" - ); - } + ); + } - { // avatar size function - set_test(UTILITY_AVATAR_SIZE, false); - bool success = false; - success = dpp::utility::avatar_size(0).empty(); - success = dpp::utility::avatar_size(16) == "?size=16" && success; - success = dpp::utility::avatar_size(256) == "?size=256" && success; - success = dpp::utility::avatar_size(4096) == "?size=4096" && success; - success = dpp::utility::avatar_size(8192).empty() && success; - success = dpp::utility::avatar_size(3000).empty() && success; - set_test(UTILITY_AVATAR_SIZE, success); - } + { // avatar size function + set_test(UTILITY_AVATAR_SIZE, false); + bool success = false; + success = dpp::utility::avatar_size(0).empty(); + success = dpp::utility::avatar_size(16) == "?size=16" && success; + success = dpp::utility::avatar_size(256) == "?size=256" && success; + success = dpp::utility::avatar_size(4096) == "?size=4096" && success; + success = dpp::utility::avatar_size(8192).empty() && success; + success = dpp::utility::avatar_size(3000).empty() && success; + set_test(UTILITY_AVATAR_SIZE, success); + } - { // cdn endpoint url getter - set_test(UTILITY_CDN_ENDPOINT_URL_HASH, false); - bool success = false; - success = dpp::utility::cdn_endpoint_url_hash({ dpp::i_png }, "foobar/test", "", dpp::i_jpg, 0).empty(); - success = dpp::utility::cdn_endpoint_url_hash({ dpp::i_png }, "foobar/test", "", dpp::i_png, 0) == "https://cdn.discordapp.com/foobar/test.png" && success; - success = dpp::utility::cdn_endpoint_url_hash({ dpp::i_png }, "foobar/test", "", dpp::i_png, 128) == "https://cdn.discordapp.com/foobar/test.png?size=128" && success; - success = dpp::utility::cdn_endpoint_url_hash({ dpp::i_png, dpp::i_gif }, "foobar/test", "12345", dpp::i_gif, 0, false, true) == "https://cdn.discordapp.com/foobar/test/a_12345.gif" && success; - success = dpp::utility::cdn_endpoint_url_hash({ dpp::i_png, dpp::i_gif }, "foobar/test", "12345", dpp::i_png, 0, false, true) == "https://cdn.discordapp.com/foobar/test/a_12345.png" && success; - success = dpp::utility::cdn_endpoint_url_hash({ dpp::i_png, dpp::i_gif }, "foobar/test", "12345", dpp::i_png, 0, false, false) == "https://cdn.discordapp.com/foobar/test/12345.png" && success; - success = dpp::utility::cdn_endpoint_url_hash({ dpp::i_png, dpp::i_gif }, "foobar/test", "12345", dpp::i_png, 0, true, true) == "https://cdn.discordapp.com/foobar/test/a_12345.gif" && success; - success = dpp::utility::cdn_endpoint_url_hash({ dpp::i_png, dpp::i_gif }, "foobar/test", "", dpp::i_png, 0, true, true) == "https://cdn.discordapp.com/foobar/test.gif" && success; - success = dpp::utility::cdn_endpoint_url_hash({ dpp::i_png, dpp::i_gif }, "foobar/test", "", dpp::i_gif, 0, false, false).empty() && success; - set_test(UTILITY_CDN_ENDPOINT_URL_HASH, success); - } + { // cdn endpoint url getter + set_test(UTILITY_CDN_ENDPOINT_URL_HASH, false); + bool success = false; + success = dpp::utility::cdn_endpoint_url_hash({dpp::i_png}, "foobar/test", "", dpp::i_jpg, 0).empty(); + success = dpp::utility::cdn_endpoint_url_hash({dpp::i_png}, "foobar/test", "", dpp::i_png, 0) == "https://cdn.discordapp.com/foobar/test.png" && success; + success = dpp::utility::cdn_endpoint_url_hash({dpp::i_png}, "foobar/test", "", dpp::i_png, 128) == "https://cdn.discordapp.com/foobar/test.png?size=128" && success; + success = dpp::utility::cdn_endpoint_url_hash({dpp::i_png, dpp::i_gif}, "foobar/test", "12345", dpp::i_gif, 0, false, true) == "https://cdn.discordapp.com/foobar/test/a_12345.gif" && success; + success = dpp::utility::cdn_endpoint_url_hash({dpp::i_png, dpp::i_gif}, "foobar/test", "12345", dpp::i_png, 0, false, true) == "https://cdn.discordapp.com/foobar/test/a_12345.png" && success; + success = dpp::utility::cdn_endpoint_url_hash({dpp::i_png, dpp::i_gif}, "foobar/test", "12345", dpp::i_png, 0, false, false) == "https://cdn.discordapp.com/foobar/test/12345.png" && success; + success = dpp::utility::cdn_endpoint_url_hash({dpp::i_png, dpp::i_gif}, "foobar/test", "12345", dpp::i_png, 0, true, true) == "https://cdn.discordapp.com/foobar/test/a_12345.gif" && success; + success = dpp::utility::cdn_endpoint_url_hash({dpp::i_png, dpp::i_gif}, "foobar/test", "", dpp::i_png, 0, true, true) == "https://cdn.discordapp.com/foobar/test.gif" && success; + success = dpp::utility::cdn_endpoint_url_hash({dpp::i_png, dpp::i_gif}, "foobar/test", "", dpp::i_gif, 0, false, false).empty() && success; + set_test(UTILITY_CDN_ENDPOINT_URL_HASH, success); + } - { // sticker url getter - set_test(STICKER_GET_URL, false); - dpp::sticker s; - s.format_type = dpp::sf_png; - bool success = s.get_url().empty(); - s.id = 12345; - success = s.get_url() == "https://cdn.discordapp.com/stickers/12345.png" && success; - s.format_type = dpp::sf_gif; - success = s.get_url() == "https://cdn.discordapp.com/stickers/12345.gif" && success; - s.format_type = dpp::sf_lottie; - success = s.get_url() == "https://cdn.discordapp.com/stickers/12345.json" && success; - set_test(STICKER_GET_URL, success); - } + { // sticker url getter + set_test(STICKER_GET_URL, false); + dpp::sticker s; + s.format_type = dpp::sf_png; + bool success = s.get_url().empty(); + s.id = 12345; + success = s.get_url() == "https://cdn.discordapp.com/stickers/12345.png" && success; + s.format_type = dpp::sf_gif; + success = s.get_url() == "https://cdn.discordapp.com/stickers/12345.gif" && success; + s.format_type = dpp::sf_lottie; + success = s.get_url() == "https://cdn.discordapp.com/stickers/12345.json" && success; + set_test(STICKER_GET_URL, success); + } - { // user url getter - dpp::user user1; - user1.id = 189759562910400512; - user1.username = "Brain"; - user1.discriminator = 0001; + { // user url getter + dpp::user user1; + user1.id = 189759562910400512; + user1.username = "Brain"; + user1.discriminator = 0001; - auto user2 = user1; - user2.avatar = "5532c6414c70765a28cf9448c117205f"; + auto user2 = user1; + user2.avatar = "5532c6414c70765a28cf9448c117205f"; - auto user3 = user2; - user3.flags |= dpp::u_animated_icon; + auto user3 = user2; + user3.flags |= dpp::u_animated_icon; - set_test(USER_GET_AVATAR_URL, false); - set_test(USER_GET_AVATAR_URL, + set_test(USER_GET_AVATAR_URL, false); + set_test(USER_GET_AVATAR_URL, dpp::user().get_avatar_url().empty() && user1.get_avatar_url() == dpp::utility::cdn_host + "/embed/avatars/1.png" && user2.get_avatar_url() == dpp::utility::cdn_host + "/avatars/189759562910400512/5532c6414c70765a28cf9448c117205f.png" && @@ -714,48 +716,48 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b user3.get_avatar_url(512, dpp::i_jpg) == dpp::utility::cdn_host + "/avatars/189759562910400512/a_5532c6414c70765a28cf9448c117205f.gif?size=512" && user3.get_avatar_url(16, dpp::i_jpg, false) == dpp::utility::cdn_host + "/avatars/189759562910400512/a_5532c6414c70765a28cf9448c117205f.jpg?size=16" && user3.get_avatar_url(5000) == dpp::utility::cdn_host + "/avatars/189759562910400512/a_5532c6414c70765a28cf9448c117205f.gif" - ); - } + ); + } - { // emoji url getter - dpp::emoji emoji; - emoji.id = 825407338755653641; + { // emoji url getter + dpp::emoji emoji; + emoji.id = 825407338755653641; - set_test(EMOJI_GET_URL, false); - set_test(EMOJI_GET_URL, emoji.get_url() == dpp::utility::cdn_host + "/emojis/825407338755653641.png"); - } + set_test(EMOJI_GET_URL, false); + set_test(EMOJI_GET_URL, emoji.get_url() == dpp::utility::cdn_host + "/emojis/825407338755653641.png"); + } - { // message methods - dpp::message m; - m.guild_id = 825407338755653642; - m.channel_id = 956230231277072415; - m.id = 1151617986541666386; + { // message methods + dpp::message m; + m.guild_id = 825407338755653642; + m.channel_id = 956230231277072415; + m.id = 1151617986541666386; - dpp::message m2; - m2.guild_id = 825407338755653642; - m2.channel_id = 956230231277072415; + dpp::message m2; + m2.guild_id = 825407338755653642; + m2.channel_id = 956230231277072415; - dpp::message m3; - m3.guild_id = 825407338755653642; - m3.id = 1151617986541666386; + dpp::message m3; + m3.guild_id = 825407338755653642; + m3.id = 1151617986541666386; - dpp::message m4; - m4.channel_id = 956230231277072415; - m4.id = 1151617986541666386; + dpp::message m4; + m4.channel_id = 956230231277072415; + m4.id = 1151617986541666386; - dpp::message m5; - m5.guild_id = 825407338755653642; + dpp::message m5; + m5.guild_id = 825407338755653642; - dpp::message m6; - m6.channel_id = 956230231277072415; + dpp::message m6; + m6.channel_id = 956230231277072415; - dpp::message m7; - m7.id = 1151617986541666386; + dpp::message m7; + m7.id = 1151617986541666386; - dpp::message m8; + dpp::message m8; - set_test(MESSAGE_GET_URL, false); - set_test(MESSAGE_GET_URL, + set_test(MESSAGE_GET_URL, false); + set_test(MESSAGE_GET_URL, m.get_url() == dpp::utility::url_host + "/channels/825407338755653642/956230231277072415/1151617986541666386" && m2.get_url() == "" && m3.get_url() == "" && @@ -764,1707 +766,1701 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b m6.get_url() == "" && m7.get_url() == "" && m8.get_url() == "" - ); - } + ); + } - { // channel methods - set_test(CHANNEL_SET_TYPE, false); - dpp::channel c; - c.set_flags(dpp::c_nsfw | dpp::c_video_quality_720p); - c.set_type(dpp::CHANNEL_CATEGORY); - bool before = c.is_category() && !c.is_forum(); - c.set_type(dpp::CHANNEL_FORUM); - bool after = !c.is_category() && c.is_forum(); - set_test(CHANNEL_SET_TYPE, before && after); + { // channel methods + set_test(CHANNEL_SET_TYPE, false); + dpp::channel c; + c.set_flags(dpp::c_nsfw | dpp::c_video_quality_720p); + c.set_type(dpp::CHANNEL_CATEGORY); + bool before = c.is_category() && !c.is_forum(); + c.set_type(dpp::CHANNEL_FORUM); + bool after = !c.is_category() && c.is_forum(); + set_test(CHANNEL_SET_TYPE, before && after); - set_test(CHANNEL_GET_MENTION, false); - c.id = 825411707521728511; - set_test(CHANNEL_GET_MENTION, c.get_mention() == "<#825411707521728511>"); + set_test(CHANNEL_GET_MENTION, false); + c.id = 825411707521728511; + set_test(CHANNEL_GET_MENTION, c.get_mention() == "<#825411707521728511>"); - set_test(CHANNEL_GET_URL, false); - c.guild_id = 825407338755653642; + set_test(CHANNEL_GET_URL, false); + c.guild_id = 825407338755653642; - dpp::channel c2; - c2.id = 825411707521728511; + dpp::channel c2; + c2.id = 825411707521728511; - dpp::channel c3; - c3.guild_id = 825407338755653642; + dpp::channel c3; + c3.guild_id = 825407338755653642; - dpp::channel c4; + dpp::channel c4; - set_test(CHANNEL_GET_URL, + set_test(CHANNEL_GET_URL, c.get_url() == dpp::utility::url_host + "/channels/825407338755653642/825411707521728511" && c2.get_url() == "" && c3.get_url() == "" && c4.get_url() == "" - ); - } + ); + } - { // utility methods - set_test(UTILITY_GUILD_NAVIGATION, false); - auto gn1 = dpp::utility::guild_navigation(123, dpp::utility::gnt_customize); - auto gn2 = dpp::utility::guild_navigation(1234, dpp::utility::gnt_browse); - auto gn3 = dpp::utility::guild_navigation(12345, dpp::utility::gnt_guide); - set_test(UTILITY_GUILD_NAVIGATION, gn1 == "<123:customize>" && gn2 == "<1234:browse>" && gn3 == "<12345:guide>"); - - set_test(UTILITY_ICONHASH, false); - auto iconhash1 = dpp::utility::iconhash("a_5532c6414c70765a28cf9448c117205f"); - set_test(UTILITY_ICONHASH, iconhash1.first == 6139187225817019994 && - iconhash1.second == 2940732121894297695 && - iconhash1.to_string() == "5532c6414c70765a28cf9448c117205f" - ); + { // utility methods + set_test(UTILITY_GUILD_NAVIGATION, false); + auto gn1 = dpp::utility::guild_navigation(123, dpp::utility::gnt_customize); + auto gn2 = dpp::utility::guild_navigation(1234, dpp::utility::gnt_browse); + auto gn3 = dpp::utility::guild_navigation(12345, dpp::utility::gnt_guide); + set_test(UTILITY_GUILD_NAVIGATION, gn1 == "<123:customize>" && gn2 == "<1234:browse>" && gn3 == "<12345:guide>"); + + set_test(UTILITY_ICONHASH, false); + auto iconhash1 = dpp::utility::iconhash("a_5532c6414c70765a28cf9448c117205f"); + set_test(UTILITY_ICONHASH, iconhash1.first == 6139187225817019994 && + iconhash1.second == 2940732121894297695 && + iconhash1.to_string() == "5532c6414c70765a28cf9448c117205f" + ); - set_test(UTILITY_MAKE_URL_PARAMETERS, false); - auto url_params1 = dpp::utility::make_url_parameters({ - {"foo", 15}, - {"bar", 7} - }); - auto url_params2 = dpp::utility::make_url_parameters({ - {"foo", "hello"}, - {"bar", "two words"} - }); - set_test(UTILITY_MAKE_URL_PARAMETERS, url_params1 == "?bar=7&foo=15" && url_params2 == "?bar=two%20words&foo=hello"); - - set_test(UTILITY_MARKDOWN_ESCAPE, false); - auto escaped = dpp::utility::markdown_escape( + set_test(UTILITY_MAKE_URL_PARAMETERS, false); + auto url_params1 = dpp::utility::make_url_parameters({ + {"foo", 15}, + {"bar", 7} + }); + auto url_params2 = dpp::utility::make_url_parameters({ + {"foo", "hello"}, + {"bar", "two words"} + }); + set_test(UTILITY_MAKE_URL_PARAMETERS, url_params1 == "?bar=7&foo=15" && url_params2 == "?bar=two%20words&foo=hello"); + + set_test(UTILITY_MARKDOWN_ESCAPE, false); + auto escaped = dpp::utility::markdown_escape( "> this is a quote\n" "**some bold text**"); - set_test(UTILITY_MARKDOWN_ESCAPE, "\\>this is a quote\\n\\*\\*some bold text\\*\\*"); - - set_test(UTILITY_TOKENIZE, false); - auto tokens = dpp::utility::tokenize("some Whitespace seperated Text to Tokenize", " "); - std::vector expected_tokens = {"some", "Whitespace", "seperated", "Text", "to", "Tokenize"}; - set_test(UTILITY_TOKENIZE, tokens == expected_tokens); - - set_test(UTILITY_URL_ENCODE, false); - auto url_encoded = dpp::utility::url_encode("S2-^$1Nd+U!g'8+_??o?p-bla bla"); - set_test(UTILITY_URL_ENCODE, url_encoded == "S2-%5E%241Nd%2BU%21g%278%2B_%3F%3Fo%3Fp-bla%20bla"); - - set_test(UTILITY_SLASHCOMMAND_MENTION, false); - auto mention1 = dpp::utility::slashcommand_mention(123, "name"); - auto mention2 = dpp::utility::slashcommand_mention(123, "name", "sub"); - auto mention3 = dpp::utility::slashcommand_mention(123, "name", "group", "sub"); - bool success = mention1 == "" && mention2 == "" && mention3 == ""; - set_test(UTILITY_SLASHCOMMAND_MENTION, success); - - set_test(UTILITY_CHANNEL_MENTION, false); - auto channel_mention = dpp::utility::channel_mention(123); - set_test(UTILITY_CHANNEL_MENTION, channel_mention == "<#123>"); - - set_test(UTILITY_USER_MENTION, false); - auto user_mention = dpp::utility::user_mention(123); - set_test(UTILITY_USER_MENTION, user_mention == "<@123>"); - - set_test(UTILITY_ROLE_MENTION, false); - auto role_mention = dpp::utility::role_mention(123); - set_test(UTILITY_ROLE_MENTION, role_mention == "<@&123>"); - - set_test(UTILITY_EMOJI_MENTION, false); - auto emoji_mention1 = dpp::utility::emoji_mention("role1", 123, false); - auto emoji_mention2 = dpp::utility::emoji_mention("role2", 234, true); - auto emoji_mention3 = dpp::utility::emoji_mention("white_check_mark", 0, false); - auto emoji_mention4 = dpp::utility::emoji_mention("white_check_mark", 0, true); - set_test(UTILITY_EMOJI_MENTION, + set_test(UTILITY_MARKDOWN_ESCAPE, "\\>this is a quote\\n\\*\\*some bold text\\*\\*"); + + set_test(UTILITY_TOKENIZE, false); + auto tokens = dpp::utility::tokenize("some Whitespace seperated Text to Tokenize", " "); + std::vector expected_tokens = {"some", "Whitespace", "seperated", "Text", "to", "Tokenize"}; + set_test(UTILITY_TOKENIZE, tokens == expected_tokens); + + set_test(UTILITY_URL_ENCODE, false); + auto url_encoded = dpp::utility::url_encode("S2-^$1Nd+U!g'8+_??o?p-bla bla"); + set_test(UTILITY_URL_ENCODE, url_encoded == "S2-%5E%241Nd%2BU%21g%278%2B_%3F%3Fo%3Fp-bla%20bla"); + + set_test(UTILITY_SLASHCOMMAND_MENTION, false); + auto mention1 = dpp::utility::slashcommand_mention(123, "name"); + auto mention2 = dpp::utility::slashcommand_mention(123, "name", "sub"); + auto mention3 = dpp::utility::slashcommand_mention(123, "name", "group", "sub"); + bool success = mention1 == "" && mention2 == "" && mention3 == ""; + set_test(UTILITY_SLASHCOMMAND_MENTION, success); + + set_test(UTILITY_CHANNEL_MENTION, false); + auto channel_mention = dpp::utility::channel_mention(123); + set_test(UTILITY_CHANNEL_MENTION, channel_mention == "<#123>"); + + set_test(UTILITY_USER_MENTION, false); + auto user_mention = dpp::utility::user_mention(123); + set_test(UTILITY_USER_MENTION, user_mention == "<@123>"); + + set_test(UTILITY_ROLE_MENTION, false); + auto role_mention = dpp::utility::role_mention(123); + set_test(UTILITY_ROLE_MENTION, role_mention == "<@&123>"); + + set_test(UTILITY_EMOJI_MENTION, false); + auto emoji_mention1 = dpp::utility::emoji_mention("role1", 123, false); + auto emoji_mention2 = dpp::utility::emoji_mention("role2", 234, true); + auto emoji_mention3 = dpp::utility::emoji_mention("white_check_mark", 0, false); + auto emoji_mention4 = dpp::utility::emoji_mention("white_check_mark", 0, true); + set_test(UTILITY_EMOJI_MENTION, emoji_mention1 == "<:role1:123>" && emoji_mention2 == "" && emoji_mention3 == ":white_check_mark:" && emoji_mention4 == ":white_check_mark:" - ); + ); - set_test(UTILITY_USER_URL, false); - auto user_url = dpp::utility::user_url(123); - set_test(UTILITY_USER_URL, + set_test(UTILITY_USER_URL, false); + auto user_url = dpp::utility::user_url(123); + set_test(UTILITY_USER_URL, user_url == dpp::utility::url_host + "/users/123" && dpp::utility::user_url(0) == "" - ); + ); - set_test(UTILITY_MESSAGE_URL, false); - auto message_url = dpp::utility::message_url(1,2,3); - set_test(UTILITY_MESSAGE_URL, - message_url == dpp::utility::url_host+ "/channels/1/2/3" && - dpp::utility::message_url(0,2,3) == "" && - dpp::utility::message_url(1,0,3) == "" && - dpp::utility::message_url(1,2,0) == "" && - dpp::utility::message_url(0,0,3) == "" && - dpp::utility::message_url(0,2,0) == "" && - dpp::utility::message_url(1,0,0) == "" && - dpp::utility::message_url(0,0,0) == "" - ); + set_test(UTILITY_MESSAGE_URL, false); + auto message_url = dpp::utility::message_url(1, 2, 3); + set_test(UTILITY_MESSAGE_URL, + message_url == dpp::utility::url_host + "/channels/1/2/3" && + dpp::utility::message_url(0, 2, 3) == "" && + dpp::utility::message_url(1, 0, 3) == "" && + dpp::utility::message_url(1, 2, 0) == "" && + dpp::utility::message_url(0, 0, 3) == "" && + dpp::utility::message_url(0, 2, 0) == "" && + dpp::utility::message_url(1, 0, 0) == "" && + dpp::utility::message_url(0, 0, 0) == "" + ); - set_test(UTILITY_CHANNEL_URL, false); - auto channel_url = dpp::utility::channel_url(1,2); - set_test(UTILITY_CHANNEL_URL, - channel_url == dpp::utility::url_host+ "/channels/1/2" && - dpp::utility::channel_url(0,2) == "" && - dpp::utility::channel_url(1,0) == "" && - dpp::utility::channel_url(0,0) == "" - ); + set_test(UTILITY_CHANNEL_URL, false); + auto channel_url = dpp::utility::channel_url(1, 2); + set_test(UTILITY_CHANNEL_URL, + channel_url == dpp::utility::url_host + "/channels/1/2" && + dpp::utility::channel_url(0, 2) == "" && + dpp::utility::channel_url(1, 0) == "" && + dpp::utility::channel_url(0, 0) == "" + ); - set_test(UTILITY_THREAD_URL, false); - auto thread_url = dpp::utility::thread_url(1,2); - set_test(UTILITY_THREAD_URL, - thread_url == dpp::utility::url_host+ "/channels/1/2" && - dpp::utility::thread_url(0,2) == "" && - dpp::utility::thread_url(1,0) == "" && - dpp::utility::thread_url(0,0) == "" - ); - } + set_test(UTILITY_THREAD_URL, false); + auto thread_url = dpp::utility::thread_url(1, 2); + set_test(UTILITY_THREAD_URL, + thread_url == dpp::utility::url_host + "/channels/1/2" && + dpp::utility::thread_url(0, 2) == "" && + dpp::utility::thread_url(1, 0) == "" && + dpp::utility::thread_url(0, 0) == "" + ); + } #ifndef _WIN32 - set_test(TIMESTRINGTOTIMESTAMP, false); - json tj; - tj["t1"] = "2022-01-19T17:18:14.506000+00:00"; - tj["t2"] = "2022-01-19T17:18:14+00:00"; - uint32_t inTimestamp = 1642612694; - set_test(TIMESTRINGTOTIMESTAMP, (uint64_t)dpp::ts_not_null(&tj, "t1") == inTimestamp && (uint64_t)dpp::ts_not_null(&tj, "t2") == inTimestamp); + set_test(TIMESTRINGTOTIMESTAMP, false); + json tj; + tj["t1"] = "2022-01-19T17:18:14.506000+00:00"; + tj["t2"] = "2022-01-19T17:18:14+00:00"; + uint32_t inTimestamp = 1642612694; + set_test(TIMESTRINGTOTIMESTAMP, (uint64_t) dpp::ts_not_null(&tj, "t1") == inTimestamp && (uint64_t) dpp::ts_not_null(&tj, "t2") == inTimestamp); #else - set_test(TIMESTRINGTOTIMESTAMP, true); + set_test(TIMESTRINGTOTIMESTAMP, true); #endif - { - set_test(TS, false); - dpp::managed m(189759562910400512); - set_test(TS, ((uint64_t) m.get_creation_time()) == 1465312605); - } + { + set_test(TS, false); + dpp::managed m(189759562910400512); + set_test(TS, ((uint64_t) m.get_creation_time()) == 1465312605); + } - { - coro_offline_tests(); - } + { + coro_offline_tests(); + } - std::promise ready_promise; - std::future ready_future = ready_promise.get_future(); + std::promise ready_promise; + std::future ready_future = ready_promise.get_future(); - std::vector dpp_logo = load_data("DPP-Logo.png"); + std::vector dpp_logo = load_data("DPP-Logo.png"); - set_test(PRESENCE, false); - set_test(CLUSTER, false); - try { - dpp::cluster bot(token, dpp::i_all_intents); - bot.set_websocket_protocol(dpp::ws_etf); - set_test(CLUSTER, true); - set_test(CONNECTION, false); - set_test(GUILDCREATE, false); - set_test(ICONHASH, false); - - set_test(MSGCOLLECT, false); - if (!offline) { - /* Intentional leak: freed on unit test end */ - [[maybe_unused]] - message_collector* collect_messages = new message_collector(&bot, 25); - } + set_test(PRESENCE, false); + set_test(CLUSTER, false); + try { + dpp::cluster bot(token, dpp::i_all_intents); + bot.set_websocket_protocol(dpp::ws_etf); + set_test(CLUSTER, true); + set_test(CONNECTION, false); + set_test(GUILDCREATE, false); + set_test(ICONHASH, false); + + set_test(MSGCOLLECT, false); + if (!offline) { + /* Intentional leak: freed on unit test end */ + [[maybe_unused]] + message_collector *collect_messages = new message_collector(&bot, 25); + } + + set_test(JSON_PARSE_ERROR, false); + dpp::rest_request(&bot, "/nonexistent", "address", "", dpp::m_get, "", [](const dpp::confirmation_callback_t &e) { + if (e.is_error() && e.get_error().code == 404) { + set_test(JSON_PARSE_ERROR, true); + } else { + set_test(JSON_PARSE_ERROR, false); + } + }); - set_test(JSON_PARSE_ERROR, false); - dpp::rest_request(&bot, "/nonexistent", "address", "", dpp::m_get, "", [](const dpp::confirmation_callback_t& e) { - if (e.is_error() && e.get_error().code == 404) { - set_test(JSON_PARSE_ERROR, true); + if (!offline) { + start_test(INVALIDUTF8); + bot.message_create(dpp::message(TEST_TEXT_CHANNEL_ID, "ä\xA9ü"), [](const auto &cc) { + set_status(INVALIDUTF8, ts_success); + }); } else { - set_test(JSON_PARSE_ERROR, false); + set_status(INVALIDUTF8, ts_skipped); } - }); - if (!offline) { - start_test(INVALIDUTF8); - bot.message_create(dpp::message(TEST_TEXT_CHANNEL_ID, "ä\xA9ü"), [](const auto &cc) { - set_status(INVALIDUTF8, ts_success); + dpp::utility::iconhash i; + std::string dummyval("fcffffffffffff55acaaaaaaaaaaaa66"); + i = dummyval; + set_test(ICONHASH, (i.to_string() == dummyval)); + + /* This ensures we test both protocols, as voice is json and shard is etf */ + bot.set_websocket_protocol(dpp::ws_etf); + + /* 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 + * the test suite will crash and fail. + */ + bot.on_voice_receive_combined([&](const auto &event) { }); - } else { - set_status(INVALIDUTF8, ts_skipped); - } - dpp::utility::iconhash i; - std::string dummyval("fcffffffffffff55acaaaaaaaaaaaa66"); - i = dummyval; - set_test(ICONHASH, (i.to_string() == dummyval)); - - /* This ensures we test both protocols, as voice is json and shard is etf */ - bot.set_websocket_protocol(dpp::ws_etf); - - /* 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 - * the test suite will crash and fail. - */ - bot.on_voice_receive_combined([&](const auto& 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) { - start_test(GUILD_EDIT); - g.set_icon(dpp::i_png, dpp_logo.data(), static_cast(dpp_logo.size())); - bot.guild_edit(g, [&bot](const dpp::confirmation_callback_t &result) { - if (result.is_error()) { - set_status(GUILD_EDIT, ts_failed, "guild_edit 1 errored:\n" + result.get_error().human_readable); - return; - } - dpp::guild g2 = result.get(); + bot.on_guild_create([dpp_logo, &bot](const dpp::guild_create_t &event) { + dpp::guild g = event.created; - if (g2.get_icon_url().empty()) { - set_status(GUILD_EDIT, ts_failed, "icon not set or not retrieved"); - return; - } - g2.remove_icon(); - bot.guild_edit(g2, [](const dpp::confirmation_callback_t &result) { + if (g.id == TEST_GUILD_ID) { + start_test(GUILD_EDIT); + g.set_icon(dpp::i_png, dpp_logo.data(), static_cast(dpp_logo.size())); + bot.guild_edit(g, [&bot](const dpp::confirmation_callback_t &result) { if (result.is_error()) { - set_status(GUILD_EDIT, ts_failed, "guild_edit 2 errored:\n" + result.get_error().human_readable); + set_status(GUILD_EDIT, ts_failed, "guild_edit 1 errored:\n" + result.get_error().human_readable); return; } - const dpp::guild g3 = result.get(); - if (!g3.get_icon_url().empty()) { - set_status(GUILD_EDIT, ts_failed, "icon not removed"); + dpp::guild g2 = result.get(); + + if (g2.get_icon_url().empty()) { + set_status(GUILD_EDIT, ts_failed, "icon not set or not retrieved"); return; } - set_status(GUILD_EDIT, ts_success); - }); - }); - } - }); - - bot.on_ready([&ready_promise,&bot,dpp_logo](const dpp::ready_t & event) { - set_test(CONNECTION, true); - ready_promise.set_value(); - - set_test(APPCOMMAND, false); - set_test(LOGGER, false); - bot.log(dpp::ll_info, "Test log message"); - - bot.guild_command_create(dpp::slashcommand().set_name("testcommand") - .set_description("Test command for DPP unit test") - .add_option(dpp::command_option(dpp::co_attachment, "file", "a file")) - .set_application_id(bot.me.id) - .add_localization("fr", "zut", "Ou est la salor dans Discord?"), - TEST_GUILD_ID, [&](const dpp::confirmation_callback_t &callback) { - if (!callback.is_error()) { - set_test(APPCOMMAND, true); - set_test(DELCOMMAND, false); - dpp::slashcommand s = std::get(callback.value); - bot.guild_command_delete(s.id, TEST_GUILD_ID, [&](const dpp::confirmation_callback_t &callback) { - if (!callback.is_error()) { - dpp::message test_message(TEST_TEXT_CHANNEL_ID, "test message"); - - set_test(DELCOMMAND, true); - set_test(MESSAGECREATE, false); - set_test(MESSAGEEDIT, false); - set_test(MESSAGERECEIVE, false); - test_message.add_file("no-mime", "test"); - test_message.add_file("test.txt", "test", "text/plain"); - test_message.add_file("test.png", std::string{reinterpret_cast(dpp_logo.data()), dpp_logo.size()}, "image/png"); - bot.message_create(test_message, [&bot](const dpp::confirmation_callback_t &callback) { - if (!callback.is_error()) { - set_test(MESSAGECREATE, true); - set_test(REACT, false); - dpp::message m = std::get(callback.value); - set_test(REACTEVENT, false); - bot.message_add_reaction(m.id, TEST_TEXT_CHANNEL_ID, "😄", [](const dpp::confirmation_callback_t &callback) { - if (!callback.is_error()) { - set_test(REACT, true); - } else { - set_test(REACT, false); - } - }); - set_test(EDITEVENT, false); - bot.message_edit(dpp::message(m).set_content("test edit"), [](const dpp::confirmation_callback_t &callback) { - if (!callback.is_error()) { - set_test(MESSAGEEDIT, true); - } - }); - } - }); - } else { - set_test(DELCOMMAND, false); + g2.remove_icon(); + bot.guild_edit(g2, [](const dpp::confirmation_callback_t &result) { + if (result.is_error()) { + set_status(GUILD_EDIT, ts_failed, "guild_edit 2 errored:\n" + result.get_error().human_readable); + return; + } + const dpp::guild g3 = result.get(); + if (!g3.get_icon_url().empty()) { + set_status(GUILD_EDIT, ts_failed, "icon not removed"); + return; } + set_status(GUILD_EDIT, ts_success); }); - } - }); - }); - - 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") { - set_test(LOGGER, true); - } - }); - - set_test(RUNONCE, false); - uint8_t runs = 0; - for (int x = 0; x < 10; ++x) { - if (dpp::run_once()) { - runs++; - } - } - set_test(RUNONCE, (runs == 1)); - - 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); - if (v && v->is_ready()) { - v->send_audio_raw((uint16_t*)testaudio.data(), testaudio.size()); - } else { - set_test(VOICESEND, false); - } - }); - - bot.on_invite_create([](const dpp::invite_create_t &event) { - auto &inv = event.created_invite; - if (!inv.code.empty() && inv.channel_id == TEST_TEXT_CHANNEL_ID && inv.guild_id == TEST_GUILD_ID && inv.created_at != 0 && inv.max_uses == 100) { - set_test(INVITE_CREATE_EVENT, true); - } - }); + }); + } + }); - bot.on_invite_delete([](const dpp::invite_delete_t &event) { - auto &inv = event.deleted_invite; - if (!inv.code.empty() && inv.channel_id == TEST_TEXT_CHANNEL_ID && inv.guild_id == TEST_GUILD_ID) { - set_test(INVITE_DELETE_EVENT, true); - } - }); + bot.on_ready([&ready_promise, &bot, dpp_logo](const dpp::ready_t &event) { + set_test(CONNECTION, true); + ready_promise.set_value(); - bot.on_voice_buffer_send([](const dpp::voice_buffer_send_t & event) { - static bool sent_some_data = false; + set_test(APPCOMMAND, false); + set_test(LOGGER, false); + bot.log(dpp::ll_info, "Test log message"); - if (event.buffer_size > 0) { - sent_some_data = true; - } + bot.guild_command_create(dpp::slashcommand().set_name("testcommand") + .set_description("Test command for DPP unit test") + .add_option(dpp::command_option(dpp::co_attachment, "file", "a file")) + .set_application_id(bot.me.id) + .add_localization("fr", "zut", "Ou est la salor dans Discord?"), + TEST_GUILD_ID, [&](const dpp::confirmation_callback_t &callback) { + if (!callback.is_error()) { + set_test(APPCOMMAND, true); + set_test(DELCOMMAND, false); + dpp::slashcommand s = std::get(callback.value); + bot.guild_command_delete(s.id, TEST_GUILD_ID, [&](const dpp::confirmation_callback_t &callback) { + if (!callback.is_error()) { + dpp::message test_message(TEST_TEXT_CHANNEL_ID, "test message"); + + set_test(DELCOMMAND, true); + set_test(MESSAGECREATE, false); + set_test(MESSAGEEDIT, false); + set_test(MESSAGERECEIVE, false); + test_message.add_file("no-mime", "test"); + test_message.add_file("test.txt", "test", "text/plain"); + test_message.add_file("test.png", std::string{reinterpret_cast(dpp_logo.data()), dpp_logo.size()}, "image/png"); + bot.message_create(test_message, [&bot](const dpp::confirmation_callback_t &callback) { + if (!callback.is_error()) { + set_test(MESSAGECREATE, true); + set_test(REACT, false); + dpp::message m = std::get(callback.value); + set_test(REACTEVENT, false); + bot.message_add_reaction(m.id, TEST_TEXT_CHANNEL_ID, "😄", [](const dpp::confirmation_callback_t &callback) { + if (!callback.is_error()) { + set_test(REACT, true); + } else { + set_test(REACT, false); + } + }); + set_test(EDITEVENT, false); + bot.message_edit(dpp::message(m).set_content("test edit"), [](const dpp::confirmation_callback_t &callback) { + if (!callback.is_error()) { + set_test(MESSAGEEDIT, true); + } + }); + } + }); + } else { + set_test(DELCOMMAND, false); + } + }); + } + }); + }); - if (sent_some_data && event.packets_left == 0) { - set_test(VOICESEND, true); - } - }); - - 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) { - set_test(PRESENCE, true); - } - dpp::guild* g = dpp::find_guild(TEST_GUILD_ID); - set_test(CACHE, false); - if (g) { - set_test(CACHE, true); - set_test(VOICECONN, false); - dpp::discord_client* s = bot.get_shard(0); - s->connect_voice(g->id, TEST_VC_ID, false, false); - } - else { - set_test(CACHE, false); + 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"; } - } - }); - - // this helper class contains logic for the message tests, deletes the message when all tests are done - class message_test_helper - { - private: - std::mutex mutex; - bool pin_tested; - bool thread_tested; - std::array files_tested; - std::array files_success; - dpp::snowflake channel_id; - dpp::snowflake message_id; - dpp::cluster ⊥ - - void delete_message_if_done() { - 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) { - set_test(MESSAGEDELETE, !callback.is_error()); - }); + if (event.message == "Test log message") { + set_test(LOGGER, true); } - } + }); - void set_pin_tested() { - if (pin_tested) { - return; + set_test(RUNONCE, false); + uint8_t runs = 0; + for (int x = 0; x < 10; ++x) { + if (dpp::run_once()) { + runs++; } - pin_tested = true; - delete_message_if_done(); } + set_test(RUNONCE, (runs == 1)); - void set_thread_tested() { - if (thread_tested) { - return; + 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); + if (v && v->is_ready()) { + v->send_audio_raw((uint16_t *) testaudio.data(), testaudio.size()); + } else { + set_test(VOICESEND, false); } - thread_tested = true; - delete_message_if_done(); - } + }); - void set_file_tested(size_t 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}); + bot.on_invite_create([](const dpp::invite_create_t &event) { + auto &inv = event.created_invite; + if (!inv.code.empty() && inv.channel_id == TEST_TEXT_CHANNEL_ID && inv.guild_id == TEST_GUILD_ID && inv.created_at != 0 && inv.max_uses == 100) { + set_test(INVITE_CREATE_EVENT, true); } - delete_message_if_done(); - } - - void test_threads(const dpp::message &message) - { - set_test(THREAD_CREATE_MESSAGE, false); - set_test(THREAD_DELETE, false); - set_test(THREAD_DELETE_EVENT, false); - bot.thread_create_with_message("test", message.channel_id, message.id, 60, 60, [this](const dpp::confirmation_callback_t &callback) { - std::lock_guard lock(mutex); - if (callback.is_error()) { - set_thread_tested(); - } - else { - auto thread = callback.get(); - thread_id = thread.id; - set_test(THREAD_CREATE_MESSAGE, true); - bot.channel_delete(thread.id, [this](const dpp::confirmation_callback_t &callback) { - set_test(THREAD_DELETE, !callback.is_error()); - set_thread_tested(); - }); - } - }); - } + }); - void test_files(const dpp::message &message) { - set_test(MESSAGEFILE, false); - if (message.attachments.size() == 3) { - static constexpr auto check_mimetype = [](const auto &headers, std::string mimetype) { - if (auto it = headers.find("content-type"); it != headers.end()) { - // check that the mime type starts with what we gave : for example discord will change "text/plain" to "text/plain; charset=UTF-8" - return it->second.size() >= mimetype.size() && std::equal(it->second.begin(), it->second.begin() + mimetype.size(), mimetype.begin()); - } - else { - return false; - } - }; - 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([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([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")) { - files_success[2] = true; - } - set_file_tested(2); - }); - } - else { - set_file_tested(0); - set_file_tested(1); - set_file_tested(2); + bot.on_invite_delete([](const dpp::invite_delete_t &event) { + auto &inv = event.deleted_invite; + if (!inv.code.empty() && inv.channel_id == TEST_TEXT_CHANNEL_ID && inv.guild_id == TEST_GUILD_ID) { + set_test(INVITE_DELETE_EVENT, true); } - } - - void test_pin() { - if (!extended) { - set_pin_tested(); - return; - } - set_test(MESSAGEPIN, false); - set_test(MESSAGEUNPIN, false); - bot.message_pin(channel_id, message_id, [this](const dpp::confirmation_callback_t &callback) { - std::lock_guard lock(mutex); - if (!callback.is_error()) { - set_test(MESSAGEPIN, true); - bot.message_unpin(TEST_TEXT_CHANNEL_ID, message_id, [this](const dpp::confirmation_callback_t &callback) { - std::lock_guard lock(mutex); - if (!callback.is_error()) { - set_test(MESSAGEUNPIN, true); - } - set_pin_tested(); - }); - } - else { - set_pin_tested(); - } - }); - } - - public: - dpp::snowflake thread_id; - - message_test_helper(dpp::cluster &_bot) : bot(_bot) {} - - void run(const dpp::message &message) { - pin_tested = false; - thread_tested = false; - files_tested = {false, false, false}; - files_success = {false, false, false}; - channel_id = message.channel_id; - message_id = message.id; - test_pin(); - test_files(message); - test_threads(message); - } - }; - - message_test_helper message_helper(bot); + }); - class thread_test_helper - { - public: - enum event_flag - { - MESSAGE_CREATE = 1 << 0, - MESSAGE_EDIT = 1 << 1, - MESSAGE_REACT = 1 << 2, - MESSAGE_REMOVE_REACT = 1 << 3, - MESSAGE_DELETE = 1 << 4, - EVENT_END = 1 << 5 - }; - private: - std::mutex mutex; - dpp::cluster ⊥ - bool edit_tested = false; - bool members_tested = false; - bool messages_tested = false; - bool events_tested = false; - bool get_active_tested = false; - uint32_t events_tested_mask = 0; - uint32_t events_to_test_mask = 0; - - void delete_if_done() - { - if (edit_tested && members_tested && messages_tested && events_tested && get_active_tested) { - bot.channel_delete(thread_id); - } - } + bot.on_voice_buffer_send([](const dpp::voice_buffer_send_t &event) { + static bool sent_some_data = false; - void set_events_tested() - { - if (events_tested) { - return; + if (event.buffer_size > 0) { + sent_some_data = true; } - events_tested = true; - delete_if_done(); - } - void set_edit_tested() - { - if (edit_tested) { - return; + if (sent_some_data && event.packets_left == 0) { + set_test(VOICESEND, true); } - edit_tested = true; - delete_if_done(); - } + }); - void set_members_tested() - { - if (members_tested) { - return; + 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) { + set_test(PRESENCE, true); + } + dpp::guild *g = dpp::find_guild(TEST_GUILD_ID); + set_test(CACHE, false); + if (g) { + set_test(CACHE, true); + set_test(VOICECONN, false); + dpp::discord_client *s = bot.get_shard(0); + s->connect_voice(g->id, TEST_VC_ID, false, false); + } else { + set_test(CACHE, false); + } } - members_tested = true; - delete_if_done(); - } + }); - void set_get_active_tested() - { - if (get_active_tested) { - return; + // this helper class contains logic for the message tests, deletes the message when all tests are done + class message_test_helper { + private: + std::mutex mutex; + bool pin_tested; + bool thread_tested; + std::array files_tested; + std::array files_success; + dpp::snowflake channel_id; + dpp::snowflake message_id; + dpp::cluster ⊥ + + void delete_message_if_done() { + 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) { + set_test(MESSAGEDELETE, !callback.is_error()); + }); + } } - get_active_tested = true; - delete_if_done(); - } - void set_messages_tested() - { - if (messages_tested) { - return; + void set_pin_tested() { + if (pin_tested) { + return; + } + pin_tested = true; + delete_message_if_done(); } - messages_tested = true; - delete_if_done(); - } - void set_event_tested(event_flag flag) - { - if (events_tested_mask & flag) { - return; - } - events_tested_mask |= flag; - for (uint32_t i = 1; i < EVENT_END; i <<= 1) { - if ((events_to_test_mask & i) && (events_tested_mask & i) != i) { + void set_thread_tested() { + if (thread_tested) { return; } + thread_tested = true; + delete_message_if_done(); } - set_events_tested(); - } - void events_abort() - { - events_tested_mask |= ~events_to_test_mask; - for (uint32_t i = 1; i < EVENT_END; i <<= 1) { - if ((events_tested_mask & i) != i) { + void set_file_tested(size_t 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}); + } + delete_message_if_done(); } - set_events_tested(); - } - - public: - /** - * @Brief wrapper for set_event_tested, locking the mutex. Meant to be used from outside the class - */ - void notify_event_tested(event_flag flag) - { - std::lock_guard lock{mutex}; - - set_event_tested(flag); - } - dpp::snowflake thread_id; + void test_threads(const dpp::message &message) { + set_test(THREAD_CREATE_MESSAGE, false); + set_test(THREAD_DELETE, false); + set_test(THREAD_DELETE_EVENT, false); + bot.thread_create_with_message("test", message.channel_id, message.id, 60, 60, [this](const dpp::confirmation_callback_t &callback) { + std::lock_guard lock(mutex); + if (callback.is_error()) { + set_thread_tested(); + } else { + auto thread = callback.get(); + thread_id = thread.id; + set_test(THREAD_CREATE_MESSAGE, true); + bot.channel_delete(thread.id, [this](const dpp::confirmation_callback_t &callback) { + set_test(THREAD_DELETE, !callback.is_error()); + set_thread_tested(); + }); + } + }); + } - void test_edit(const dpp::thread &thread) - { - std::lock_guard lock{mutex}; + void test_files(const dpp::message &message) { + set_test(MESSAGEFILE, false); + if (message.attachments.size() == 3) { + static constexpr auto check_mimetype = [](const auto &headers, std::string mimetype) { + if (auto it = headers.find("content-type"); it != headers.end()) { + // check that the mime type starts with what we gave : for example discord will change "text/plain" to "text/plain; charset=UTF-8" + return it->second.size() >= mimetype.size() && std::equal(it->second.begin(), it->second.begin() + mimetype.size(), mimetype.begin()); + } else { + return false; + } + }; + 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([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([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")) { + files_success[2] = true; + } + set_file_tested(2); + }); + } else { + set_file_tested(0); + set_file_tested(1); + set_file_tested(2); + } + } - if (!edit_tested) { - dpp::thread edit = thread; - set_test(THREAD_EDIT, false); - set_test(THREAD_UPDATE_EVENT, false); - edit.name = "edited"; - edit.metadata.locked = true; - bot.thread_edit(edit, [this](const dpp::confirmation_callback_t &callback) { + void test_pin() { + if (!extended) { + set_pin_tested(); + return; + } + set_test(MESSAGEPIN, false); + set_test(MESSAGEUNPIN, false); + bot.message_pin(channel_id, message_id, [this](const dpp::confirmation_callback_t &callback) { std::lock_guard lock(mutex); if (!callback.is_error()) { - set_test(THREAD_EDIT, true); + set_test(MESSAGEPIN, true); + bot.message_unpin(TEST_TEXT_CHANNEL_ID, message_id, [this](const dpp::confirmation_callback_t &callback) { + std::lock_guard lock(mutex); + if (!callback.is_error()) { + set_test(MESSAGEUNPIN, true); + } + set_pin_tested(); + }); + } else { + set_pin_tested(); } - set_edit_tested(); }); } - } - void test_get_active(const dpp::thread &thread) - { - std::lock_guard lock{mutex}; + public: + dpp::snowflake thread_id; - set_test(THREAD_GET_ACTIVE, false); - bot.threads_get_active(TEST_GUILD_ID, [this](const dpp::confirmation_callback_t &callback) { - std::lock_guard lock{mutex}; - if (!callback.is_error()) { - const auto &threads = callback.get(); - if (auto thread_it = threads.find(thread_id); thread_it != threads.end()) { - const auto &thread = thread_it->second.active_thread; - const auto &member = thread_it->second.bot_member; - if (thread.id == thread_id && member.has_value() && member->user_id == bot.me.id) { - set_test(THREAD_GET_ACTIVE, true); - } - } + message_test_helper(dpp::cluster &_bot) : bot(_bot) { + } + + void run(const dpp::message &message) { + pin_tested = false; + thread_tested = false; + files_tested = {false, false, false}; + files_success = {false, false, false}; + channel_id = message.channel_id; + message_id = message.id; + test_pin(); + test_files(message); + test_threads(message); + } + }; + + message_test_helper message_helper(bot); + + class thread_test_helper { + public: + enum event_flag { + MESSAGE_CREATE = 1 << 0, + MESSAGE_EDIT = 1 << 1, + MESSAGE_REACT = 1 << 2, + MESSAGE_REMOVE_REACT = 1 << 3, + MESSAGE_DELETE = 1 << 4, + EVENT_END = 1 << 5 + }; + private: + std::mutex mutex; + dpp::cluster ⊥ + bool edit_tested = false; + bool members_tested = false; + bool messages_tested = false; + bool events_tested = false; + bool get_active_tested = false; + uint32_t events_tested_mask = 0; + uint32_t events_to_test_mask = 0; + + void delete_if_done() { + if (edit_tested && members_tested && messages_tested && events_tested && get_active_tested) { + bot.channel_delete(thread_id); } - set_get_active_tested(); - }); - } + } - void test_members(const dpp::thread &thread) - { - std::lock_guard lock{mutex}; + void set_events_tested() { + if (events_tested) { + return; + } + events_tested = true; + delete_if_done(); + } - if (!members_tested) { - if (!extended) { - set_members_tested(); + void set_edit_tested() { + if (edit_tested) { + return; + } + edit_tested = true; + delete_if_done(); + } + + void set_members_tested() { + if (members_tested) { + return; + } + members_tested = true; + delete_if_done(); + } + + void set_get_active_tested() { + if (get_active_tested) { + return; + } + get_active_tested = true; + delete_if_done(); + } + + void set_messages_tested() { + if (messages_tested) { + return; + } + messages_tested = true; + delete_if_done(); + } + + void set_event_tested(event_flag flag) { + if (events_tested_mask & flag) { return; } - set_test(THREAD_MEMBER_ADD, false); - set_test(THREAD_MEMBER_GET, false); - set_test(THREAD_MEMBERS_GET, false); - set_test(THREAD_MEMBER_REMOVE, false); - set_test(THREAD_MEMBERS_ADD_EVENT, false); - set_test(THREAD_MEMBERS_REMOVE_EVENT, false); - bot.thread_member_add(thread_id, TEST_USER_ID, [this](const dpp::confirmation_callback_t &callback) { + events_tested_mask |= flag; + for (uint32_t i = 1; i < EVENT_END; i <<= 1) { + if ((events_to_test_mask & i) && (events_tested_mask & i) != i) { + return; + } + } + set_events_tested(); + } + + void events_abort() { + events_tested_mask |= ~events_to_test_mask; + for (uint32_t i = 1; i < EVENT_END; i <<= 1) { + if ((events_tested_mask & i) != i) { + return; + } + } + set_events_tested(); + } + + public: + /** + * @Brief wrapper for set_event_tested, locking the mutex. Meant to be used from outside the class + */ + void notify_event_tested(event_flag flag) { + std::lock_guard lock{mutex}; + + set_event_tested(flag); + } + + dpp::snowflake thread_id; + + void test_edit(const dpp::thread &thread) { + std::lock_guard lock{mutex}; + + if (!edit_tested) { + dpp::thread edit = thread; + set_test(THREAD_EDIT, false); + set_test(THREAD_UPDATE_EVENT, false); + edit.name = "edited"; + edit.metadata.locked = true; + bot.thread_edit(edit, [this](const dpp::confirmation_callback_t &callback) { + std::lock_guard lock(mutex); + if (!callback.is_error()) { + set_test(THREAD_EDIT, true); + } + set_edit_tested(); + }); + } + } + + void test_get_active(const dpp::thread &thread) { + std::lock_guard lock{mutex}; + + set_test(THREAD_GET_ACTIVE, false); + bot.threads_get_active(TEST_GUILD_ID, [this](const dpp::confirmation_callback_t &callback) { std::lock_guard lock{mutex}; - if (callback.is_error()) { + if (!callback.is_error()) { + const auto &threads = callback.get(); + if (auto thread_it = threads.find(thread_id); thread_it != threads.end()) { + const auto &thread = thread_it->second.active_thread; + const auto &member = thread_it->second.bot_member; + if (thread.id == thread_id && member.has_value() && member->user_id == bot.me.id) { + set_test(THREAD_GET_ACTIVE, true); + } + } + } + set_get_active_tested(); + }); + } + + void test_members(const dpp::thread &thread) { + std::lock_guard lock{mutex}; + + if (!members_tested) { + if (!extended) { set_members_tested(); return; } - set_test(THREAD_MEMBER_ADD, true); - bot.thread_member_get(thread_id, TEST_USER_ID, [this](const dpp::confirmation_callback_t &callback) { + set_test(THREAD_MEMBER_ADD, false); + set_test(THREAD_MEMBER_GET, false); + set_test(THREAD_MEMBERS_GET, false); + set_test(THREAD_MEMBER_REMOVE, false); + set_test(THREAD_MEMBERS_ADD_EVENT, false); + set_test(THREAD_MEMBERS_REMOVE_EVENT, false); + bot.thread_member_add(thread_id, TEST_USER_ID, [this](const dpp::confirmation_callback_t &callback) { std::lock_guard lock{mutex}; if (callback.is_error()) { set_members_tested(); return; } - set_test(THREAD_MEMBER_GET, true); - bot.thread_members_get(thread_id, [this](const dpp::confirmation_callback_t &callback) { + set_test(THREAD_MEMBER_ADD, true); + bot.thread_member_get(thread_id, TEST_USER_ID, [this](const dpp::confirmation_callback_t &callback) { std::lock_guard lock{mutex}; if (callback.is_error()) { set_members_tested(); return; } - const auto &members = callback.get(); - if (members.find(TEST_USER_ID) == members.end() || members.find(bot.me.id) == members.end()) { - set_members_tested(); - return; - } - set_test(THREAD_MEMBERS_GET, true); - bot.thread_member_remove(thread_id, TEST_USER_ID, [this](const dpp::confirmation_callback_t &callback) { + set_test(THREAD_MEMBER_GET, true); + bot.thread_members_get(thread_id, [this](const dpp::confirmation_callback_t &callback) { std::lock_guard lock{mutex}; - if (!callback.is_error()) { - set_test(THREAD_MEMBER_REMOVE, true); + if (callback.is_error()) { + set_members_tested(); + return; } - set_members_tested(); + const auto &members = callback.get(); + if (members.find(TEST_USER_ID) == members.end() || members.find(bot.me.id) == members.end()) { + set_members_tested(); + return; + } + set_test(THREAD_MEMBERS_GET, true); + bot.thread_member_remove(thread_id, TEST_USER_ID, [this](const dpp::confirmation_callback_t &callback) { + std::lock_guard lock{mutex}; + if (!callback.is_error()) { + set_test(THREAD_MEMBER_REMOVE, true); + } + set_members_tested(); + }); }); }); }); - }); + } } - } - void test_messages(const dpp::thread &thread) - { - if (!extended) { - set_messages_tested(); - set_events_tested(); - return; - } - - std::lock_guard lock{mutex}; - set_test(THREAD_MESSAGE, false); - set_test(THREAD_MESSAGE_CREATE_EVENT, false); - set_test(THREAD_MESSAGE_EDIT_EVENT, false); - set_test(THREAD_MESSAGE_REACT_ADD_EVENT, false); - set_test(THREAD_MESSAGE_REACT_REMOVE_EVENT, false); - set_test(THREAD_MESSAGE_DELETE_EVENT, false); - events_to_test_mask |= MESSAGE_CREATE; - bot.message_create(dpp::message{"hello thread"}.set_channel_id(thread.id), [this](const dpp::confirmation_callback_t &callback) { - std::lock_guard lock{mutex}; - if (callback.is_error()) { - events_abort(); + void test_messages(const dpp::thread &thread) { + if (!extended) { set_messages_tested(); + set_events_tested(); return; } - auto m = callback.get(); - m.content = "hello thread?"; - events_to_test_mask |= MESSAGE_EDIT; - bot.message_edit(m, [this, message_id = m.id](const dpp::confirmation_callback_t &callback) { + + std::lock_guard lock{mutex}; + set_test(THREAD_MESSAGE, false); + set_test(THREAD_MESSAGE_CREATE_EVENT, false); + set_test(THREAD_MESSAGE_EDIT_EVENT, false); + set_test(THREAD_MESSAGE_REACT_ADD_EVENT, false); + set_test(THREAD_MESSAGE_REACT_REMOVE_EVENT, false); + set_test(THREAD_MESSAGE_DELETE_EVENT, false); + events_to_test_mask |= MESSAGE_CREATE; + bot.message_create(dpp::message{"hello thread"}.set_channel_id(thread.id), [this](const dpp::confirmation_callback_t &callback) { std::lock_guard lock{mutex}; if (callback.is_error()) { events_abort(); set_messages_tested(); return; } - events_to_test_mask |= MESSAGE_REACT; - bot.message_add_reaction(message_id, thread_id, dpp::unicode_emoji::thread, [this, message_id](const dpp::confirmation_callback_t &callback) { + auto m = callback.get(); + m.content = "hello thread?"; + events_to_test_mask |= MESSAGE_EDIT; + bot.message_edit(m, [this, message_id = m.id](const dpp::confirmation_callback_t &callback) { std::lock_guard lock{mutex}; if (callback.is_error()) { events_abort(); set_messages_tested(); return; } - events_to_test_mask |= MESSAGE_REMOVE_REACT; - bot.message_delete_reaction(message_id, thread_id, bot.me.id, dpp::unicode_emoji::thread, [this, message_id](const dpp::confirmation_callback_t &callback) { + events_to_test_mask |= MESSAGE_REACT; + bot.message_add_reaction(message_id, thread_id, dpp::unicode_emoji::thread, [this, message_id](const dpp::confirmation_callback_t &callback) { std::lock_guard lock{mutex}; if (callback.is_error()) { events_abort(); set_messages_tested(); return; } - events_to_test_mask |= MESSAGE_DELETE; - bot.message_delete(message_id, thread_id, [this] (const dpp::confirmation_callback_t &callback) { + events_to_test_mask |= MESSAGE_REMOVE_REACT; + bot.message_delete_reaction(message_id, thread_id, bot.me.id, dpp::unicode_emoji::thread, [this, message_id](const dpp::confirmation_callback_t &callback) { std::lock_guard lock{mutex}; - set_messages_tested(); if (callback.is_error()) { events_abort(); + set_messages_tested(); return; } - set_test(THREAD_MESSAGE, true); + events_to_test_mask |= MESSAGE_DELETE; + bot.message_delete(message_id, thread_id, [this](const dpp::confirmation_callback_t &callback) { + std::lock_guard lock{mutex}; + set_messages_tested(); + if (callback.is_error()) { + events_abort(); + return; + } + set_test(THREAD_MESSAGE, true); + }); }); }); }); }); - }); - } + } - void run(const dpp::thread &thread) - { - thread_id = thread.id; - test_get_active(thread); - test_edit(thread); - test_members(thread); - test_messages(thread); - } + void run(const dpp::thread &thread) { + thread_id = thread.id; + test_get_active(thread); + test_edit(thread); + test_members(thread); + test_messages(thread); + } - thread_test_helper(dpp::cluster &bot_) : bot{bot_} - { - } - }; + thread_test_helper(dpp::cluster &bot_) : bot{bot_} { + } + }; - thread_test_helper thread_helper(bot); + thread_test_helper thread_helper(bot); - 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); - } - }); - - bool message_tested = false; - 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; - set_test(MESSAGERECEIVE, true); - message_helper.run(event.msg); - set_test(MESSAGESGET, false); - bot.messages_get(event.msg.channel_id, 0, event.msg.id, 0, 5, [](const dpp::confirmation_callback_t &cc){ - if (!cc.is_error()) { - dpp::message_map mm = std::get(cc.value); - if (mm.size()) { - set_test(MESSAGESGET, true); - set_test(TIMESTAMP, false); - dpp::message m = mm.begin()->second; - if (m.sent > 0) { - set_test(TIMESTAMP, true); - } else { + 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); + } + }); + + bool message_tested = false; + 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; + set_test(MESSAGERECEIVE, true); + message_helper.run(event.msg); + set_test(MESSAGESGET, false); + bot.messages_get(event.msg.channel_id, 0, event.msg.id, 0, 5, [](const dpp::confirmation_callback_t &cc) { + if (!cc.is_error()) { + dpp::message_map mm = std::get(cc.value); + if (mm.size()) { + set_test(MESSAGESGET, true); set_test(TIMESTAMP, false); + dpp::message m = mm.begin()->second; + if (m.sent > 0) { + set_test(TIMESTAMP, true); + } else { + set_test(TIMESTAMP, false); + } + } else { + set_test(MESSAGESGET, false); } } else { set_test(MESSAGESGET, false); } - } else { - set_test(MESSAGESGET, false); - } - }); - set_test(MSGCREATESEND, false); - event.send("MSGCREATESEND", [&bot, ch_id = event.msg.channel_id] (const auto& cc) { - if (!cc.is_error()) { - dpp::message m = std::get(cc.value); - if (m.channel_id == ch_id) { - set_test(MSGCREATESEND, true); + }); + set_test(MSGCREATESEND, false); + event.send("MSGCREATESEND", [&bot, ch_id = event.msg.channel_id](const auto &cc) { + if (!cc.is_error()) { + dpp::message m = std::get(cc.value); + if (m.channel_id == ch_id) { + set_test(MSGCREATESEND, true); + } else { + bot.log(dpp::ll_debug, cc.http_info.body); + set_test(MSGCREATESEND, false); + } + bot.message_delete(m.id, m.channel_id); } else { bot.log(dpp::ll_debug, cc.http_info.body); set_test(MSGCREATESEND, false); } - bot.message_delete(m.id, m.channel_id); - } else { - bot.log(dpp::ll_debug, cc.http_info.body); - set_test(MSGCREATESEND, false); - } - }); - } - if (event.msg.channel_id == thread_helper.thread_id && event.msg.content == "hello thread") { - set_test(THREAD_MESSAGE_CREATE_EVENT, true); - thread_helper.notify_event_tested(thread_test_helper::MESSAGE_CREATE); + }); + } + if (event.msg.channel_id == thread_helper.thread_id && event.msg.content == "hello thread") { + set_test(THREAD_MESSAGE_CREATE_EVENT, true); + thread_helper.notify_event_tested(thread_test_helper::MESSAGE_CREATE); + } } - } - }); + }); - 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); - } - if (event.channel_id == thread_helper.thread_id && event.reacting_emoji.name == dpp::unicode_emoji::thread) { - set_test(THREAD_MESSAGE_REACT_ADD_EVENT, true); - thread_helper.notify_event_tested(thread_test_helper::MESSAGE_REACT); + 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); + } + if (event.channel_id == thread_helper.thread_id && event.reacting_emoji.name == dpp::unicode_emoji::thread) { + set_test(THREAD_MESSAGE_REACT_ADD_EVENT, true); + thread_helper.notify_event_tested(thread_test_helper::MESSAGE_REACT); + } } - } - }); + }); - 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); - thread_helper.notify_event_tested(thread_test_helper::MESSAGE_REMOVE_REACT); + 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); + thread_helper.notify_event_tested(thread_test_helper::MESSAGE_REMOVE_REACT); + } } - } - }); - - 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); - } - }); + }); - bool message_edit_tested = false; - 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; - set_test(EDITEVENT, true); - } - if (event.msg.channel_id == thread_helper.thread_id && event.msg.content == "hello thread?") { - set_test(THREAD_MESSAGE_EDIT_EVENT, true); - thread_helper.notify_event_tested(thread_test_helper::MESSAGE_EDIT); + 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); } - } - }); + }); - 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); - } - }); + bool message_edit_tested = false; + 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; + set_test(EDITEVENT, true); + } + if (event.msg.channel_id == thread_helper.thread_id && event.msg.content == "hello thread?") { + set_test(THREAD_MESSAGE_EDIT_EVENT, true); + thread_helper.notify_event_tested(thread_test_helper::MESSAGE_EDIT); + } + } + }); - bot.on_thread_members_update([&](const dpp::thread_members_update_t &event) { - if (event.updating_guild->id == TEST_GUILD_ID && event.thread_id == thread_helper.thread_id) { - if (std::find_if(std::begin(event.added), std::end(event.added), is_owner) != std::end(event.added)) { - set_test(THREAD_MEMBERS_ADD_EVENT, true); + 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); } - if (std::find_if(std::begin(event.removed_ids), std::end(event.removed_ids), is_owner) != std::end(event.removed_ids)) { - set_test(THREAD_MEMBERS_REMOVE_EVENT, true); + }); + + bot.on_thread_members_update([&](const dpp::thread_members_update_t &event) { + if (event.updating_guild->id == TEST_GUILD_ID && event.thread_id == thread_helper.thread_id) { + if (std::find_if(std::begin(event.added), std::end(event.added), is_owner) != std::end(event.added)) { + set_test(THREAD_MEMBERS_ADD_EVENT, true); + } + if (std::find_if(std::begin(event.removed_ids), std::end(event.removed_ids), is_owner) != std::end(event.removed_ids)) { + set_test(THREAD_MEMBERS_REMOVE_EVENT, true); + } } - } - }); + }); - bot.on_thread_delete([&](const dpp::thread_delete_t &event) { - if (event.deleting_guild.id == TEST_GUILD_ID && event.deleted.id == message_helper.thread_id) { - set_test(THREAD_DELETE_EVENT, true); - } - }); - - // set to execute from this thread (main thread) after on_ready is fired - auto do_online_tests = [&] { - coro_online_tests(&bot); - set_test(GUILD_BAN_CREATE, false); - set_test(GUILD_BAN_GET, false); - set_test(GUILD_BANS_GET, false); - set_test(GUILD_BAN_DELETE, false); - if (!offline) { - // some discord accounts to test the ban stuff with... - dpp::snowflake deadUser1(155149108183695360); // Dyno - dpp::snowflake deadUser2(159985870458322944); // MEE6 - dpp::snowflake deadUser3(936929561302675456); // MidJourney Bot - - bot.set_audit_reason("ban reason one").guild_ban_add(TEST_GUILD_ID, deadUser1, 0, [deadUser1, deadUser2, deadUser3, &bot](const dpp::confirmation_callback_t &event) { - if (!event.is_error()) bot.guild_ban_add(TEST_GUILD_ID, deadUser2, 0, [deadUser1, deadUser2, deadUser3, &bot](const dpp::confirmation_callback_t &event) { - if (!event.is_error()) bot.set_audit_reason("ban reason three").guild_ban_add(TEST_GUILD_ID, deadUser3, 0, [deadUser1, deadUser2, deadUser3, &bot](const dpp::confirmation_callback_t &event) { - if (event.is_error()) { - return; - } - set_test(GUILD_BAN_CREATE, true); + bot.on_thread_delete([&](const dpp::thread_delete_t &event) { + if (event.deleting_guild.id == TEST_GUILD_ID && event.deleted.id == message_helper.thread_id) { + set_test(THREAD_DELETE_EVENT, true); + } + }); - // get ban - bot.guild_get_ban(TEST_GUILD_ID, deadUser1, [&bot, deadUser1, deadUser2, deadUser3](const dpp::confirmation_callback_t &event) { - if (!event.is_error()) { - dpp::ban ban = event.get(); - // Check the audit reason was set correctly - if (ban.user_id == deadUser1 && ban.reason == "ban reason one") { - set_test(GUILD_BAN_GET, true); - } else { - set_test(GUILD_BAN_GET, false); - } - // get all bans after deadUser1 - bot.guild_get_bans(TEST_GUILD_ID, 0, deadUser1, 50, [&bot, deadUser1, deadUser2, deadUser3](const dpp::confirmation_callback_t &event) { - if (!event.is_error()) { - auto bans = event.get(); - // The ban set should contain at least two bans, two of which should be deadUser2 and deadUser3, - // but never deadUser1. Take into account that *other bans* might exist in the list so we can't - // just assume there are three or set a limit of 3 above. - set_test( - GUILD_BANS_GET, - bans.find(deadUser1) == bans.end() - && bans.find(deadUser2) != bans.end() - && bans.find(deadUser3) != bans.end() - ); - // unban all three - 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()) { - bot.guild_ban_delete(TEST_GUILD_ID, deadUser3, [](const dpp::confirmation_callback_t &event) { - if (!event.is_error()) { - set_test(GUILD_BAN_DELETE, true); - } else { - set_test(GUILD_BAN_DELETE, false); - } - }); - } else { - set_test(GUILD_BAN_DELETE, false); - } - }); + // set to execute from this thread (main thread) after on_ready is fired + auto do_online_tests = [&] { + coro_online_tests(&bot); + set_test(GUILD_BAN_CREATE, false); + set_test(GUILD_BAN_GET, false); + set_test(GUILD_BANS_GET, false); + set_test(GUILD_BAN_DELETE, false); + if (!offline) { + // some discord accounts to test the ban stuff with... + dpp::snowflake deadUser1(155149108183695360); // Dyno + dpp::snowflake deadUser2(159985870458322944); // MEE6 + dpp::snowflake deadUser3(936929561302675456); // MidJourney Bot + + bot.set_audit_reason("ban reason one").guild_ban_add(TEST_GUILD_ID, deadUser1, 0, [deadUser1, deadUser2, deadUser3, &bot](const dpp::confirmation_callback_t &event) { + if (!event.is_error()) + bot.guild_ban_add(TEST_GUILD_ID, deadUser2, 0, [deadUser1, deadUser2, deadUser3, &bot](const dpp::confirmation_callback_t &event) { + if (!event.is_error()) + bot.set_audit_reason("ban reason three").guild_ban_add(TEST_GUILD_ID, deadUser3, 0, [deadUser1, deadUser2, deadUser3, &bot](const dpp::confirmation_callback_t &event) { + if (event.is_error()) { + return; + } + set_test(GUILD_BAN_CREATE, true); + + // get ban + bot.guild_get_ban(TEST_GUILD_ID, deadUser1, [&bot, deadUser1, deadUser2, deadUser3](const dpp::confirmation_callback_t &event) { + if (!event.is_error()) { + dpp::ban ban = event.get(); + // Check the audit reason was set correctly + if (ban.user_id == deadUser1 && ban.reason == "ban reason one") { + set_test(GUILD_BAN_GET, true); } else { - set_test(GUILD_BAN_DELETE, false); + set_test(GUILD_BAN_GET, false); } - }); - } else { - /* An error in this test cascades to others failing immediately */ - set_test(GUILD_BANS_GET, false); - set_test(GUILD_BAN_DELETE, false); - } + // get all bans after deadUser1 + bot.guild_get_bans(TEST_GUILD_ID, 0, deadUser1, 50, [&bot, deadUser1, deadUser2, deadUser3](const dpp::confirmation_callback_t &event) { + if (!event.is_error()) { + auto bans = event.get(); + // The ban set should contain at least two bans, two of which should be deadUser2 and deadUser3, + // but never deadUser1. Take into account that *other bans* might exist in the list so we can't + // just assume there are three or set a limit of 3 above. + set_test( + GUILD_BANS_GET, + bans.find(deadUser1) == bans.end() + && bans.find(deadUser2) != bans.end() + && bans.find(deadUser3) != bans.end() + ); + // unban all three + 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()) { + bot.guild_ban_delete(TEST_GUILD_ID, deadUser3, [](const dpp::confirmation_callback_t &event) { + if (!event.is_error()) { + set_test(GUILD_BAN_DELETE, true); + } else { + set_test(GUILD_BAN_DELETE, false); + } + }); + } else { + set_test(GUILD_BAN_DELETE, false); + } + }); + } else { + set_test(GUILD_BAN_DELETE, false); + } + }); + } else { + /* An error in this test cascades to others failing immediately */ + set_test(GUILD_BANS_GET, false); + set_test(GUILD_BAN_DELETE, false); + } + }); + } else { + /* An error in the parent test cascades to the others failing immediately */ + set_test(GUILD_BAN_GET, false); + set_test(GUILD_BANS_GET, false); + set_test(GUILD_BAN_DELETE, false); + } + }); }); - } else { - /* An error in the parent test cascades to the others failing immediately */ - set_test(GUILD_BAN_GET, false); - set_test(GUILD_BANS_GET, false); - set_test(GUILD_BAN_DELETE, false); - } }); - }); }); - }); - } - - set_test(REQUEST_GET_IMAGE, false); - if (!offline) { - bot.request("https://dpp.dev/DPP-Logo.png", dpp::m_get, [&bot](const dpp::http_request_completion_t &callback) { - if (callback.status != 200) { - return; - } - set_test(REQUEST_GET_IMAGE, true); - - dpp::emoji emoji; - emoji.load_image(callback.body, dpp::i_png); - emoji.name = "dpp"; + } - // emoji unit test with the requested image - set_test(EMOJI_CREATE, false); - set_test(EMOJI_GET, false); - set_test(EMOJI_DELETE, false); - bot.guild_emoji_create(TEST_GUILD_ID, emoji, [&bot](const dpp::confirmation_callback_t &event) { - if (event.is_error()) { + set_test(REQUEST_GET_IMAGE, false); + if (!offline) { + bot.request("https://dpp.dev/DPP-Logo.png", dpp::m_get, [&bot](const dpp::http_request_completion_t &callback) { + if (callback.status != 200) { return; } - set_test(EMOJI_CREATE, true); + set_test(REQUEST_GET_IMAGE, true); + + dpp::emoji emoji; + emoji.load_image(callback.body, dpp::i_png); + emoji.name = "dpp"; - auto created = event.get(); - bot.guild_emoji_get(TEST_GUILD_ID, created.id, [&bot, created](const dpp::confirmation_callback_t &event) { + // emoji unit test with the requested image + set_test(EMOJI_CREATE, false); + set_test(EMOJI_GET, false); + set_test(EMOJI_DELETE, false); + bot.guild_emoji_create(TEST_GUILD_ID, emoji, [&bot](const dpp::confirmation_callback_t &event) { if (event.is_error()) { return; } - auto fetched = event.get(); - if (created.id == fetched.id && created.name == fetched.name && created.flags == fetched.flags) { - set_test(EMOJI_GET, true); - } + set_test(EMOJI_CREATE, true); - bot.guild_emoji_delete(TEST_GUILD_ID, fetched.id, [](const dpp::confirmation_callback_t &event) { - if (!event.is_error()) { - set_test(EMOJI_DELETE, true); + auto created = event.get(); + bot.guild_emoji_get(TEST_GUILD_ID, created.id, [&bot, created](const dpp::confirmation_callback_t &event) { + if (event.is_error()) { + return; + } + auto fetched = event.get(); + if (created.id == fetched.id && created.name == fetched.name && created.flags == fetched.flags) { + set_test(EMOJI_GET, true); } + + bot.guild_emoji_delete(TEST_GUILD_ID, fetched.id, [](const dpp::confirmation_callback_t &event) { + if (!event.is_error()) { + set_test(EMOJI_DELETE, true); + } + }); }); }); }); - }); - } + } - set_test(INVITE_CREATE, false); - set_test(INVITE_GET, false); - set_test(INVITE_DELETE, false); - if (!offline) { - dpp::channel channel; - channel.id = TEST_TEXT_CHANNEL_ID; - dpp::invite invite; - invite.max_age = 0; - invite.max_uses = 100; - set_test(INVITE_CREATE_EVENT, false); - bot.channel_invite_create(channel, invite, [&bot, invite](const dpp::confirmation_callback_t &event) { - if (event.is_error()) { - return; - } + set_test(INVITE_CREATE, false); + set_test(INVITE_GET, false); + set_test(INVITE_DELETE, false); + if (!offline) { + dpp::channel channel; + channel.id = TEST_TEXT_CHANNEL_ID; + dpp::invite invite; + invite.max_age = 0; + invite.max_uses = 100; + set_test(INVITE_CREATE_EVENT, false); + bot.channel_invite_create(channel, invite, [&bot, invite](const dpp::confirmation_callback_t &event) { + if (event.is_error()) { + return; + } - auto created = event.get(); - if (!created.code.empty() && created.channel_id == TEST_TEXT_CHANNEL_ID && created.guild_id == TEST_GUILD_ID && created.inviter.id == bot.me.id) { - set_test(INVITE_CREATE, true); - } + auto created = event.get(); + if (!created.code.empty() && created.channel_id == TEST_TEXT_CHANNEL_ID && created.guild_id == TEST_GUILD_ID && created.inviter.id == bot.me.id) { + set_test(INVITE_CREATE, true); + } + + bot.invite_get(created.code, [&bot, created](const dpp::confirmation_callback_t &event) { + if (!event.is_error()) { + auto retrieved = event.get(); + if (retrieved.code == created.code && retrieved.guild_id == created.guild_id && retrieved.channel_id == created.channel_id && retrieved.inviter.id == created.inviter.id) { + if (retrieved.destination_guild.flags & dpp::g_community) { + set_test(INVITE_GET, retrieved.expires_at == 0); + } else { + set_test(INVITE_GET, true); + } - bot.invite_get(created.code, [&bot, created](const dpp::confirmation_callback_t &event) { - if (!event.is_error()) { - auto retrieved = event.get(); - if (retrieved.code == created.code && retrieved.guild_id == created.guild_id && retrieved.channel_id == created.channel_id && retrieved.inviter.id == created.inviter.id) { - if (retrieved.destination_guild.flags & dpp::g_community) { - set_test(INVITE_GET, retrieved.expires_at == 0); } else { - set_test(INVITE_GET, true); + set_test(INVITE_GET, false); } - } else { set_test(INVITE_GET, false); } - } else { - set_test(INVITE_GET, false); - } - set_test(INVITE_DELETE_EVENT, false); - bot.invite_delete(created.code, [](const dpp::confirmation_callback_t &event) { - set_test(INVITE_DELETE, !event.is_error()); + set_test(INVITE_DELETE_EVENT, false); + bot.invite_delete(created.code, [](const dpp::confirmation_callback_t &event) { + set_test(INVITE_DELETE, !event.is_error()); + }); }); }); - }); - } - - set_test(AUTOMOD_RULE_CREATE, false); - set_test(AUTOMOD_RULE_GET, false); - set_test(AUTOMOD_RULE_GET_ALL, false); - set_test(AUTOMOD_RULE_DELETE, false); - if (!offline) { - dpp::automod_rule automodRule; - automodRule.name = "automod rule (keyword type)"; - automodRule.trigger_type = dpp::amod_type_keyword; - dpp::automod_metadata metadata1; - metadata1.keywords.emplace_back("*cat*"); - metadata1.keywords.emplace_back("train"); - metadata1.keywords.emplace_back("*.exe"); - metadata1.regex_patterns.emplace_back("^[^a-z]$"); - metadata1.allow_list.emplace_back("@silent*"); - automodRule.trigger_metadata = metadata1; - dpp::automod_action automodAction; - automodAction.type = dpp::amod_action_timeout; - automodAction.duration_seconds = 6000; - automodRule.actions.emplace_back(automodAction); - - bot.automod_rules_get(TEST_GUILD_ID, [&bot, automodRule](const dpp::confirmation_callback_t &event) { - if (event.is_error()) { - return; - } - auto rules = event.get(); - set_test(AUTOMOD_RULE_GET_ALL, true); - for (const auto &rule: rules) { - if (rule.second.trigger_type == dpp::amod_type_keyword) { - // delete one automod rule of type KEYWORD before creating one to make space... - bot.automod_rule_delete(TEST_GUILD_ID, rule.first); - } - } + } - // start creating the automod rules - bot.automod_rule_create(TEST_GUILD_ID, automodRule, [&bot, automodRule](const dpp::confirmation_callback_t &event) { + set_test(AUTOMOD_RULE_CREATE, false); + set_test(AUTOMOD_RULE_GET, false); + set_test(AUTOMOD_RULE_GET_ALL, false); + set_test(AUTOMOD_RULE_DELETE, false); + if (!offline) { + dpp::automod_rule automodRule; + automodRule.name = "automod rule (keyword type)"; + automodRule.trigger_type = dpp::amod_type_keyword; + dpp::automod_metadata metadata1; + metadata1.keywords.emplace_back("*cat*"); + metadata1.keywords.emplace_back("train"); + metadata1.keywords.emplace_back("*.exe"); + metadata1.regex_patterns.emplace_back("^[^a-z]$"); + metadata1.allow_list.emplace_back("@silent*"); + automodRule.trigger_metadata = metadata1; + dpp::automod_action automodAction; + automodAction.type = dpp::amod_action_timeout; + automodAction.duration_seconds = 6000; + automodRule.actions.emplace_back(automodAction); + + bot.automod_rules_get(TEST_GUILD_ID, [&bot, automodRule](const dpp::confirmation_callback_t &event) { if (event.is_error()) { return; } - auto created = event.get(); - if (created.name == automodRule.name) { - set_test(AUTOMOD_RULE_CREATE, true); + auto rules = event.get(); + set_test(AUTOMOD_RULE_GET_ALL, true); + for (const auto &rule: rules) { + if (rule.second.trigger_type == dpp::amod_type_keyword) { + // delete one automod rule of type KEYWORD before creating one to make space... + bot.automod_rule_delete(TEST_GUILD_ID, rule.first); + } } - // get automod rule - bot.automod_rule_get(TEST_GUILD_ID, created.id, [automodRule, &bot, created](const dpp::confirmation_callback_t &event) { + // start creating the automod rules + bot.automod_rule_create(TEST_GUILD_ID, automodRule, [&bot, automodRule](const dpp::confirmation_callback_t &event) { if (event.is_error()) { return; } - auto retrieved = event.get(); - if (retrieved.name == automodRule.name && - retrieved.trigger_type == automodRule.trigger_type && - retrieved.trigger_metadata.keywords == automodRule.trigger_metadata.keywords && - retrieved.trigger_metadata.regex_patterns == automodRule.trigger_metadata.regex_patterns && - retrieved.trigger_metadata.allow_list == automodRule.trigger_metadata.allow_list && retrieved.actions.size() == automodRule.actions.size()) { - set_test(AUTOMOD_RULE_GET, true); + auto created = event.get(); + if (created.name == automodRule.name) { + set_test(AUTOMOD_RULE_CREATE, true); } - // delete the automod rule - bot.automod_rule_delete(TEST_GUILD_ID, retrieved.id, [](const dpp::confirmation_callback_t &event) { - if (!event.is_error()) { - set_test(AUTOMOD_RULE_DELETE, true); + // get automod rule + bot.automod_rule_get(TEST_GUILD_ID, created.id, [automodRule, &bot, created](const dpp::confirmation_callback_t &event) { + if (event.is_error()) { + return; + } + auto retrieved = event.get(); + if (retrieved.name == automodRule.name && + retrieved.trigger_type == automodRule.trigger_type && + retrieved.trigger_metadata.keywords == automodRule.trigger_metadata.keywords && + retrieved.trigger_metadata.regex_patterns == automodRule.trigger_metadata.regex_patterns && + retrieved.trigger_metadata.allow_list == automodRule.trigger_metadata.allow_list && retrieved.actions.size() == automodRule.actions.size()) { + set_test(AUTOMOD_RULE_GET, true); } + + // delete the automod rule + bot.automod_rule_delete(TEST_GUILD_ID, retrieved.id, [](const dpp::confirmation_callback_t &event) { + if (!event.is_error()) { + set_test(AUTOMOD_RULE_DELETE, true); + } + }); }); }); }); - }); - } + } - set_test(USER_GET, false); - set_test(USER_GET_FLAGS, false); - if (!offline) { - bot.user_get(TEST_USER_ID, [](const dpp::confirmation_callback_t &event) { - if (!event.is_error()) { - auto u = std::get(event.value); - if (u.id == TEST_USER_ID) { - set_test(USER_GET, true); + set_test(USER_GET, false); + set_test(USER_GET_FLAGS, false); + if (!offline) { + bot.user_get(TEST_USER_ID, [](const dpp::confirmation_callback_t &event) { + if (!event.is_error()) { + auto u = std::get(event.value); + if (u.id == TEST_USER_ID) { + set_test(USER_GET, true); + } else { + set_test(USER_GET, false); + } + json j = json::parse(event.http_info.body); + uint64_t raw_flags = j["public_flags"]; + if (j.contains("flags")) { + uint64_t flags = j["flags"]; + raw_flags |= flags; + } + // 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) + ) { + set_test(USER_GET_FLAGS, true); + } else { + set_test(USER_GET_FLAGS, false); + } } else { set_test(USER_GET, false); - } - json j = json::parse(event.http_info.body); - uint64_t raw_flags = j["public_flags"]; - if (j.contains("flags")) { - uint64_t flags = j["flags"]; - raw_flags |= flags; - } - // 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) - ) { - set_test(USER_GET_FLAGS, true); - } else { set_test(USER_GET_FLAGS, false); } - } else { - set_test(USER_GET, false); - set_test(USER_GET_FLAGS, false); - } - }); - } - - set_test(VOICE_CHANNEL_CREATE, false); - set_test(VOICE_CHANNEL_EDIT, false); - set_test(VOICE_CHANNEL_DELETE, false); - if (!offline) { - dpp::channel channel1; - channel1.set_type(dpp::CHANNEL_VOICE) - .set_guild_id(TEST_GUILD_ID) - .set_name("voice1") - .add_permission_overwrite(TEST_GUILD_ID, dpp::ot_role, 0, dpp::p_view_channel) - .set_user_limit(99); - bot.channel_create(channel1, [&bot,channel1](const auto& response) { - if (response.is_error()) { - set_test(VOICE_CHANNEL_CREATE, false); - return; - } - 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; - } - } + }); + } - // 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); - } + set_test(VOICE_CHANNEL_CREATE, false); + set_test(VOICE_CHANNEL_EDIT, false); + set_test(VOICE_CHANNEL_DELETE, false); + if (!offline) { + dpp::channel channel1; + channel1.set_type(dpp::CHANNEL_VOICE) + .set_guild_id(TEST_GUILD_ID) + .set_name("voice1") + .add_permission_overwrite(TEST_GUILD_ID, dpp::ot_role, 0, dpp::p_view_channel) + .set_user_limit(99); + bot.channel_create(channel1, [&bot, channel1](const auto &response) { + if (response.is_error()) { + set_test(VOICE_CHANNEL_CREATE, false); + return; } - bot.channel_edit(createdChannel, [&bot,createdChannel](const auto& response) { - if (response.is_error()) { - set_test(VOICE_CHANNEL_EDIT, false); - return; + 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; + } } - 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); - set_test(FORUM_CHANNEL_GET, false); - set_test(FORUM_CHANNEL_DELETE, false); - if (!offline) { - dpp::channel c; - c.name = "test-forum-channel"; - c.guild_id = TEST_GUILD_ID; - c.set_topic("This is a forum channel"); - c.set_flags(dpp::CHANNEL_FORUM); - c.default_sort_order = dpp::so_creation_date; - dpp::forum_tag t; - t.name = "Alpha"; - t.emoji = "❌"; - c.available_tags = {t}; - c.default_auto_archive_duration = dpp::arc_1_day; - c.default_reaction = "✅"; - c.default_thread_rate_limit_per_user = 10; - bot.channel_create(c, [&bot](const dpp::confirmation_callback_t &event) { - if (!event.is_error()) { - set_test(FORUM_CREATION, true); - auto channel = std::get(event.value); - // retrieve the forum channel and check the values - bot.channel_get(channel.id, [forum_id = channel.id, &bot](const dpp::confirmation_callback_t &event) { - if (!event.is_error()) { - auto channel = std::get(event.value); - bot.log(dpp::ll_debug, event.http_info.body); - bool tag = false; - for (auto &t : channel.available_tags) { - if (t.name == "Alpha" && std::holds_alternative(t.emoji) && std::get(t.emoji) == "❌") { - tag = 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); } - bool name = channel.name == "test-forum-channel"; - bool sort = channel.default_sort_order == dpp::so_creation_date; - bool rateLimit = channel.default_thread_rate_limit_per_user == 10; - set_test(FORUM_CHANNEL_GET, tag && name && sort && rateLimit); - } else { - set_test(FORUM_CHANNEL_GET, false); } - // delete the forum channel - bot.channel_delete(forum_id, [](const dpp::confirmation_callback_t &event) { + 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); + set_test(FORUM_CHANNEL_GET, false); + set_test(FORUM_CHANNEL_DELETE, false); + if (!offline) { + dpp::channel c; + c.name = "test-forum-channel"; + c.guild_id = TEST_GUILD_ID; + c.set_topic("This is a forum channel"); + c.set_flags(dpp::CHANNEL_FORUM); + c.default_sort_order = dpp::so_creation_date; + dpp::forum_tag t; + t.name = "Alpha"; + t.emoji = "❌"; + c.available_tags = {t}; + c.default_auto_archive_duration = dpp::arc_1_day; + c.default_reaction = "✅"; + c.default_thread_rate_limit_per_user = 10; + bot.channel_create(c, [&bot](const dpp::confirmation_callback_t &event) { + if (!event.is_error()) { + set_test(FORUM_CREATION, true); + auto channel = std::get(event.value); + // retrieve the forum channel and check the values + bot.channel_get(channel.id, [forum_id = channel.id, &bot](const dpp::confirmation_callback_t &event) { if (!event.is_error()) { - set_test(FORUM_CHANNEL_DELETE, true); + auto channel = std::get(event.value); + bot.log(dpp::ll_debug, event.http_info.body); + bool tag = false; + for (auto &t: channel.available_tags) { + if (t.name == "Alpha" && std::holds_alternative(t.emoji) && std::get(t.emoji) == "❌") { + tag = true; + } + } + bool name = channel.name == "test-forum-channel"; + bool sort = channel.default_sort_order == dpp::so_creation_date; + bool rateLimit = channel.default_thread_rate_limit_per_user == 10; + set_test(FORUM_CHANNEL_GET, tag && name && sort && rateLimit); } else { - set_test(FORUM_CHANNEL_DELETE, false); + set_test(FORUM_CHANNEL_GET, false); } + // delete the forum channel + bot.channel_delete(forum_id, [](const dpp::confirmation_callback_t &event) { + if (!event.is_error()) { + set_test(FORUM_CHANNEL_DELETE, true); + } else { + set_test(FORUM_CHANNEL_DELETE, false); + } + }); }); - }); - } else { - set_test(FORUM_CREATION, false); - set_test(FORUM_CHANNEL_GET, false); - } - }); - } - - set_test(THREAD_CREATE, false); - if (!offline) { - bot.thread_create("thread test", TEST_TEXT_CHANNEL_ID, 60, dpp::channel_type::CHANNEL_PUBLIC_THREAD, true, 60, [&](const dpp::confirmation_callback_t &event) { - if (!event.is_error()) { - [[maybe_unused]] const auto &thread = event.get(); - set_test(THREAD_CREATE, true); - } - // the thread tests are in the on_thread_create event handler - }); - } + } else { + set_test(FORUM_CREATION, false); + set_test(FORUM_CHANNEL_GET, false); + } + }); + } - start_test(POLL_CREATE); - if (!offline) { - dpp::message poll_msg{}; - - poll_msg.set_poll(dpp::poll{} - .set_question("hello!") - .add_answer("one", dpp::unicode_emoji::one) - .add_answer("two", dpp::unicode_emoji::two) - .add_answer("three", dpp::unicode_emoji::three) - .add_answer("four") - .set_duration(48) - .set_allow_multiselect(true) - ).set_channel_id(TEST_TEXT_CHANNEL_ID); - - bot.message_create(poll_msg, [&bot, poll_msg](const dpp::confirmation_callback_t& result) { - if (result.is_error()) { - set_status(POLL_CREATE, ts_failed, result.get_error().human_readable); - return; - } + set_test(THREAD_CREATE, false); + if (!offline) { + bot.thread_create("thread test", TEST_TEXT_CHANNEL_ID, 60, dpp::channel_type::CHANNEL_PUBLIC_THREAD, true, 60, [&](const dpp::confirmation_callback_t &event) { + if (!event.is_error()) { + [[maybe_unused]] const auto &thread = event.get(); + set_test(THREAD_CREATE, true); + } + // the thread tests are in the on_thread_create event handler + }); + } - const dpp::message& m = std::get(result.value); + start_test(POLL_CREATE); + if (!offline) { + dpp::message poll_msg{}; + + poll_msg.set_poll(dpp::poll{} + .set_question("hello!") + .add_answer("one", dpp::unicode_emoji::one) + .add_answer("two", dpp::unicode_emoji::two) + .add_answer("three", dpp::unicode_emoji::three) + .add_answer("four") + .set_duration(48) + .set_allow_multiselect(true) + ).set_channel_id(TEST_TEXT_CHANNEL_ID); + + bot.message_create(poll_msg, [&bot, poll_msg](const dpp::confirmation_callback_t &result) { + if (result.is_error()) { + set_status(POLL_CREATE, ts_failed, result.get_error().human_readable); + return; + } - if (!m.attached_poll.has_value()) { - set_status(POLL_CREATE, ts_failed, "poll missing in received message"); - return; - } + const dpp::message &m = std::get(result.value); - if (m.attached_poll->find_answer(std::numeric_limits::max()) != nullptr) { - set_status(POLL_CREATE, ts_failed, "poll::find_answer failed to return nullptr"); - return; - } + if (!m.attached_poll.has_value()) { + set_status(POLL_CREATE, ts_failed, "poll missing in received message"); + return; + } - std::array correct = {false, false, false, false}; - int i = 0; - for (const auto& [_, answer] : m.attached_poll->answers) { - if (m.attached_poll->find_answer(answer.id) != &answer.media) { - set_status(POLL_CREATE, ts_failed, "poll::find_answer failed to return valid answer"); + if (m.attached_poll->find_answer(std::numeric_limits::max()) != nullptr) { + set_status(POLL_CREATE, ts_failed, "poll::find_answer failed to return nullptr"); return; } - if (answer.media.text == "one" && answer.media.emoji.name == dpp::unicode_emoji::one) { - if (correct[i]) { - set_status(POLL_CREATE, ts_failed, "poll answer found twice"); + + std::array correct = {false, false, false, false}; + int i = 0; + for (const auto &[_, answer]: m.attached_poll->answers) { + if (m.attached_poll->find_answer(answer.id) != &answer.media) { + set_status(POLL_CREATE, ts_failed, "poll::find_answer failed to return valid answer"); return; } - correct[i] = true; - } - if (answer.media.text == "two" && answer.media.emoji.name == dpp::unicode_emoji::two) { - if (correct[i]) { - set_status(POLL_CREATE, ts_failed, "poll answer found twice"); - return; + if (answer.media.text == "one" && answer.media.emoji.name == dpp::unicode_emoji::one) { + if (correct[i]) { + set_status(POLL_CREATE, ts_failed, "poll answer found twice"); + return; + } + correct[i] = true; } - correct[i] = true; - } - if (answer.media.text == "three" && answer.media.emoji.name == dpp::unicode_emoji::three) { - if (correct[i]) { - set_status(POLL_CREATE, ts_failed, "poll answer found twice"); - return; + if (answer.media.text == "two" && answer.media.emoji.name == dpp::unicode_emoji::two) { + if (correct[i]) { + set_status(POLL_CREATE, ts_failed, "poll answer found twice"); + return; + } + correct[i] = true; } - correct[i] = true; - } - if (answer.media.text == "four" && answer.media.emoji.name.empty()) { - if (correct[i]) { - set_status(POLL_CREATE, ts_failed, "poll answer found twice"); - return; + if (answer.media.text == "three" && answer.media.emoji.name == dpp::unicode_emoji::three) { + if (correct[i]) { + set_status(POLL_CREATE, ts_failed, "poll answer found twice"); + return; + } + correct[i] = true; } - correct[i] = true; - bot.poll_get_answer_voters(m, answer.id, 0, 100, [m, &bot](const dpp::confirmation_callback_t& result) { - if (result.is_error()) { - set_status(POLL_CREATE, ts_failed, "poll_get_answer_voters: " + result.get_error().human_readable); + if (answer.media.text == "four" && answer.media.emoji.name.empty()) { + if (correct[i]) { + set_status(POLL_CREATE, ts_failed, "poll answer found twice"); return; } - - start_test(POLL_END); - bot.poll_end(m, [message_id = m.id, channel_id = m.channel_id, &bot](const dpp::confirmation_callback_t& result) { + correct[i] = true; + bot.poll_get_answer_voters(m, answer.id, 0, 100, [m, &bot](const dpp::confirmation_callback_t &result) { if (result.is_error()) { - set_status(POLL_END, ts_failed, result.get_error().human_readable); + set_status(POLL_CREATE, ts_failed, "poll_get_answer_voters: " + result.get_error().human_readable); return; } - set_status(POLL_END, ts_success); - bot.message_delete(message_id, channel_id); + + start_test(POLL_END); + bot.poll_end(m, [message_id = m.id, channel_id = m.channel_id, &bot](const dpp::confirmation_callback_t &result) { + if (result.is_error()) { + set_status(POLL_END, ts_failed, result.get_error().human_readable); + return; + } + set_status(POLL_END, ts_success); + bot.message_delete(message_id, channel_id); + }); }); - }); + } + ++i; } - ++i; - } - if (correct == std::array{true, true, true, true}) { - set_status(POLL_CREATE, ts_success); - } else { - set_status(POLL_CREATE, ts_failed, "failed to find the submitted answers"); - } - }); - } + if (correct == std::array{true, true, true, true}) { + set_status(POLL_CREATE, ts_success); + } else { + set_status(POLL_CREATE, ts_failed, "failed to find the submitted answers"); + } + }); + } - set_test(MEMBER_GET, false); - if (!offline) { - bot.guild_get_member(TEST_GUILD_ID, TEST_USER_ID, [](const dpp::confirmation_callback_t &event){ - if (!event.is_error()) { - dpp::guild_member m = std::get(event.value); - if (m.guild_id == TEST_GUILD_ID && m.user_id == TEST_USER_ID) { - set_test(MEMBER_GET, true); + set_test(MEMBER_GET, false); + if (!offline) { + bot.guild_get_member(TEST_GUILD_ID, TEST_USER_ID, [](const dpp::confirmation_callback_t &event) { + if (!event.is_error()) { + dpp::guild_member m = std::get(event.value); + if (m.guild_id == TEST_GUILD_ID && m.user_id == TEST_USER_ID) { + set_test(MEMBER_GET, true); + } else { + set_test(MEMBER_GET, false); + } } else { set_test(MEMBER_GET, false); } - } else { - set_test(MEMBER_GET, false); - } - }); - } + }); + } - set_test(ROLE_CREATE, false); - set_test(ROLE_EDIT, false); - set_test(ROLE_DELETE, false); - if (!offline) { - dpp::role r; - r.guild_id = TEST_GUILD_ID; - r.name = "Test-Role"; - r.permissions.add(dpp::p_move_members); - r.set_flags(dpp::r_mentionable); - r.colour = dpp::colors::moon_yellow; - 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()); + set_test(ROLE_CREATE, false); + set_test(ROLE_EDIT, false); + set_test(ROLE_DELETE, false); + if (!offline) { + dpp::role r; + r.guild_id = TEST_GUILD_ID; + r.name = "Test-Role"; + r.permissions.add(dpp::p_move_members); + r.set_flags(dpp::r_mentionable); + r.colour = dpp::colors::moon_yellow; + 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); - } - }); - } - }; + } else { + set_test(ROLE_CREATE, false); + set_test(ROLE_EDIT, false); + set_test(ROLE_DELETE, false); + } + }); + } + }; - set_test(BOTSTART, false); - try { - if (!offline) { - bot.start(dpp::st_return); - set_test(BOTSTART, true); - } - } - catch (const std::exception &) { set_test(BOTSTART, false); - } + try { + if (!offline) { + bot.start(dpp::st_return); + set_test(BOTSTART, true); + } + } + catch (const std::exception &) { + set_test(BOTSTART, false); + } - dpp::https_client *c{}; - dpp::https_client *c2{}; - dpp::https_client *c3{}; + 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); - } + 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"} ); - } - catch (const dpp::exception& e) { - set_status(HTTPS, ts_failed, e.what()); + 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(HTTP, false); + set_test(TIMERSTART, false); + dpp::timer th = bot.start_timer([ticks = 0](dpp::timer timer_handle) mutable { + if (ticks == 2) { + /* The simple test timer ticks every second. + * If we get to 2 seconds, we know the timer is working. + */ + set_test(TIMERSTART, true); + } + ticks++; + }, 1); + + set_test(USER_GET_CACHED_PRESENT, 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); + 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 dpp::exception& e) { - set_status(HTTP, ts_failed, e.what()); + catch (const std::exception &) { + set_test(USER_GET_CACHED_PRESENT, false); } - set_test(MULTIHEADER, false); + set_test(USER_GET_CACHED_ABSENT, 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); + /* This is the snowflake ID of a discord staff member. + * We assume here that staffer's discord IDs will remain constant + * for long periods of time and they won't lurk in the unit test server. + * If this becomes not true any more, we'll pick another well known + * user ID. + */ + 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 dpp::exception& e) { - set_status(MULTIHEADER, ts_failed, e.what()); - } - } - - set_test(TIMERSTART, false); - dpp::timer th = bot.start_timer([ticks = 0](dpp::timer timer_handle) mutable { - if (ticks == 2) { - /* The simple test timer ticks every second. - * If we get to 2 seconds, we know the timer is working. - */ - set_test(TIMERSTART, true); + catch (const std::exception &) { + set_test(USER_GET_CACHED_ABSENT, false); } - ticks++; - }, 1); - set_test(USER_GET_CACHED_PRESENT, false); - try { - 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)); + set_test(TIMEDLISTENER, false); + dpp::timed_listener tl(&bot, 10, bot.on_log, [&](const dpp::log_t &event) { + set_test(TIMEDLISTENER, true); }); - } - catch (const std::exception&) { - set_test(USER_GET_CACHED_PRESENT, false); - } - set_test(USER_GET_CACHED_ABSENT, false); - try { - /* This is the snowflake ID of a discord staff member. - * We assume here that staffer's discord IDs will remain constant - * for long periods of time and they won't lurk in the unit test server. - * If this becomes not true any more, we'll pick another well known - * user ID. - */ - bot.user_get_cached(90339695967350784, [](const auto &e) { - if (e.is_error()) { - set_test(USER_GET_CACHED_ABSENT, false); - return; + set_test(ONESHOT, false); + bool once = false; + dpp::oneshot_timer ost(&bot, 5, [&](dpp::timer timer_handle) { + if (!once) { + set_test(ONESHOT, true); + } else { + set_test(ONESHOT, false); } - dpp::user_identified u = std::get(e.value); - set_test(USER_GET_CACHED_ABSENT, (u.id == 90339695967350784)); + once = true; }); - } - catch (const std::exception&) { - set_test(USER_GET_CACHED_ABSENT, false); - } - - set_test(TIMEDLISTENER, false); - dpp::timed_listener tl(&bot, 10, bot.on_log, [&](const dpp::log_t & event) { - set_test(TIMEDLISTENER, true); - }); - set_test(ONESHOT, false); - bool once = false; - dpp::oneshot_timer ost(&bot, 5, [&](dpp::timer timer_handle) { - if (!once) { - set_test(ONESHOT, true); + set_test(CUSTOMCACHE, false); + dpp::cache testcache; + test_cached_object_t *tco = new test_cached_object_t(666); + tco->foo = "bar"; + testcache.store(tco); + test_cached_object_t *found_tco = testcache.find(666); + if (found_tco && found_tco->id == dpp::snowflake(666) && found_tco->foo == "bar") { + set_test(CUSTOMCACHE, true); } else { - set_test(ONESHOT, false); + set_test(CUSTOMCACHE, false); } - once = true; - }); - - set_test(CUSTOMCACHE, false); - dpp::cache testcache; - test_cached_object_t* tco = new test_cached_object_t(666); - tco->foo = "bar"; - testcache.store(tco); - test_cached_object_t* found_tco = testcache.find(666); - if (found_tco && found_tco->id == dpp::snowflake(666) && found_tco->foo == "bar") { - set_test(CUSTOMCACHE, true); - } else { - set_test(CUSTOMCACHE, false); - } - testcache.remove(found_tco); + testcache.remove(found_tco); - if (!offline) { - if (std::future_status status = ready_future.wait_for(std::chrono::seconds(20)); status != std::future_status::timeout) { - do_online_tests(); + if (!offline) { + if (std::future_status status = ready_future.wait_for(std::chrono::seconds(20)); status != std::future_status::timeout) { + do_online_tests(); + } } - } - noparam_api_test(current_user_get, dpp::user_identified, CURRENTUSER); - singleparam_api_test(channel_get, TEST_TEXT_CHANNEL_ID, dpp::channel, GETCHAN); - singleparam_api_test(guild_get, TEST_GUILD_ID, dpp::guild, GETGUILD); - singleparam_api_test_list(roles_get, TEST_GUILD_ID, dpp::role_map, GETROLES); - singleparam_api_test_list(channels_get, TEST_GUILD_ID, dpp::channel_map, GETCHANS); - singleparam_api_test_list(guild_get_invites, TEST_GUILD_ID, dpp::invite_map, GETINVS); - multiparam_api_test_list(guild_get_bans, TEST_GUILD_ID, dpp::ban_map, GETBANS); - singleparam_api_test_list(channel_pins_get, TEST_TEXT_CHANNEL_ID, dpp::message_map, GETPINS); - singleparam_api_test_list(guild_events_get, TEST_GUILD_ID, dpp::scheduled_event_map, GETEVENTS); - twoparam_api_test(guild_event_get, TEST_GUILD_ID, TEST_EVENT_ID, dpp::scheduled_event, GETEVENT); - twoparam_api_test_list(guild_event_users_get, TEST_GUILD_ID, TEST_EVENT_ID, dpp::event_member_map, GETEVENTUSERS); - - std::this_thread::sleep_for(std::chrono::seconds(20)); - - /* Test stopping timer */ - set_test(TIMERSTOP, false); - set_test(TIMERSTOP, bot.stop_timer(th)); - - set_test(USERCACHE, false); - if (!offline) { - dpp::user* u = dpp::find_user(TEST_USER_ID); - set_test(USERCACHE, u); + noparam_api_test(current_user_get, dpp::user_identified, CURRENTUSER); + singleparam_api_test(channel_get, TEST_TEXT_CHANNEL_ID, dpp::channel, GETCHAN); + singleparam_api_test(guild_get, TEST_GUILD_ID, dpp::guild, GETGUILD); + singleparam_api_test_list(roles_get, TEST_GUILD_ID, dpp::role_map, GETROLES); + singleparam_api_test_list(channels_get, TEST_GUILD_ID, dpp::channel_map, GETCHANS); + singleparam_api_test_list(guild_get_invites, TEST_GUILD_ID, dpp::invite_map, GETINVS); + multiparam_api_test_list(guild_get_bans, TEST_GUILD_ID, dpp::ban_map, GETBANS); + singleparam_api_test_list(channel_pins_get, TEST_TEXT_CHANNEL_ID, dpp::message_map, GETPINS); + singleparam_api_test_list(guild_events_get, TEST_GUILD_ID, dpp::scheduled_event_map, GETEVENTS); + twoparam_api_test(guild_event_get, TEST_GUILD_ID, TEST_EVENT_ID, dpp::scheduled_event, GETEVENT); + twoparam_api_test_list(guild_event_users_get, TEST_GUILD_ID, TEST_EVENT_ID, dpp::event_member_map, GETEVENTUSERS); + + std::this_thread::sleep_for(std::chrono::seconds(20)); + + /* Test stopping timer */ + set_test(TIMERSTOP, false); + set_test(TIMERSTOP, bot.stop_timer(th)); + + set_test(USERCACHE, false); + if (!offline) { + dpp::user *u = dpp::find_user(TEST_USER_ID); + set_test(USERCACHE, u); + } + set_test(CHANNELCACHE, false); + set_test(CHANNELTYPES, false); + if (!offline) { + dpp::channel *c = dpp::find_channel(TEST_TEXT_CHANNEL_ID); + dpp::channel *c2 = dpp::find_channel(TEST_VC_ID); + set_test(CHANNELCACHE, c && c2); + set_test(CHANNELTYPES, c && c->is_text_channel() && !c->is_voice_channel() && c2 && c2->is_voice_channel() && !c2->is_text_channel()); + } + + wait_for_tests(); + + delete c; + delete c2; + delete c3; + } - set_test(CHANNELCACHE, false); - set_test(CHANNELTYPES, false); - if (!offline) { - dpp::channel* c = dpp::find_channel(TEST_TEXT_CHANNEL_ID); - dpp::channel* c2 = dpp::find_channel(TEST_VC_ID); - set_test(CHANNELCACHE, c && c2); - set_test(CHANNELTYPES, c && c->is_text_channel() && !c->is_voice_channel() && c2 && c2->is_voice_channel() && !c2->is_text_channel()); + catch (const std::exception &e) { + set_status(CLUSTER, ts_failed, e.what()); } - wait_for_tests(); - - delete c; - delete c2; - delete c3; + /* Return value = number of failed tests, exit code 0 = success */ + return test_summary(); } - catch (const std::exception &e) { - set_status(CLUSTER, ts_failed, e.what()); + catch (const std::exception& e) { + std::cerr << "Unhandled std::exception thrown: " << std::string(e.what()) << "\n"; + exit(99); + } + catch (const std::string& e) { + /* This is a safety net, none of our stuff throws strings! */ + std::cerr << "Unhandled std::string thrown: " << e << "\n"; + exit(99); + } + catch (...) { + std::cerr << "Unhandled '...' thrown\n"; + exit(99); } - - /* Return value = number of failed tests, exit code 0 = success */ - return test_summary(); }