/************************************************************************************ * * D++, A Lightweight C++ library for Discord * * Copyright 2021 Craig Edwards and D++ contributors * (https://github.com/brainboxdotcc/DPP/graphs/contributors) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ************************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef _WIN32 #include #include #define popen _popen #define pclose _pclose #endif #ifdef HAVE_PRCTL #include #endif using namespace std::literals; namespace dpp { namespace utility { double time_f() { using namespace std::chrono; auto tp = system_clock::now() + 0ns; return tp.time_since_epoch().count() / 1000000000.0; } bool has_voice() { #if HAVE_VOICE return true; #else return false; #endif } std::string current_date_time() { #ifdef _WIN32 std::time_t curr_time = time(nullptr); return trim(std::ctime(&curr_time)); #else auto t = std::time(nullptr); struct tm timedata; localtime_r(&t, &timedata); std::stringstream s; s << std::put_time(&timedata, "%Y-%m-%d %H:%M:%S"); return trim(s.str()); #endif } std::string loglevel(dpp::loglevel in) { switch (in) { case dpp::ll_trace: return "TRACE"; case dpp::ll_debug: return "DEBUG"; case dpp::ll_info: return "INFO"; case dpp::ll_warning: return "WARN"; case dpp::ll_error: return "ERROR"; case dpp::ll_critical: return "CRIT"; default: return "???"; } } uptime::uptime() : days(0), hours(0), mins(0), secs(0) { } uptime::uptime(double diff) : uptime((time_t)diff) { } uptime::uptime(time_t diff) : uptime() { days = (uint16_t)(diff / (3600 * 24)); hours = (uint8_t)(diff % (3600 * 24) / 3600); mins = (uint8_t)(diff % 3600 / 60); secs = (uint8_t)(diff % 60); } std::string uptime::to_string() const { char print_buffer[64]; if (hours == 0 && days == 0) { snprintf(print_buffer, 64, "%02d:%02d", mins, secs); return print_buffer; } else { char print_buffer[64]; std::string daystr; if (days) { daystr = std::to_string(days) + " day" + (days > 1 ? "s, " : ", "); } snprintf(print_buffer, 64, "%s%02d:%02d:%02d", daystr.c_str(), hours, mins, secs); return print_buffer; } } uint64_t uptime::to_secs() const { return secs + (mins * 60) + (hours * 60 * 60) + (days * 60 * 60 * 24); } uint64_t uptime::to_msecs() const { return to_secs() * 1000; } iconhash::iconhash(uint64_t _first, uint64_t _second) : first(_first), second(_second) { } iconhash::iconhash(const iconhash&) = default; iconhash::~iconhash() = default; void iconhash::set(const std::string &hash) { std::string clean_hash(hash); if (hash.empty()) { // Clear values if empty hash first = second = 0; return; } if (hash.length() == 34 && hash.substr(0, 2) == "a_") { /* Someone passed in an animated icon. numpty. * Clean that mess up! */ clean_hash = hash.substr(2); } if (clean_hash.length() != 32) throw std::length_error("iconhash must be exactly 32 characters in length, passed value is: '" + clean_hash + "'"); this->first = from_string(clean_hash.substr(0, 16), std::hex); this->second = from_string(clean_hash.substr(16, 16), std::hex); } iconhash::iconhash(const std::string &hash) { set(hash); } iconhash& iconhash::operator=(const std::string &assignment) { set(assignment); return *this; } bool iconhash::operator==(const iconhash& other) const { return other.first == first && other.second == second; } std::string iconhash::to_string() const { if (first == 0 && second == 0) { return ""; } else { return to_hex(this->first) + to_hex(this->second); } } std::string debug_dump(uint8_t* data, size_t length) { std::ostringstream out; size_t addr = (size_t)data; size_t extra = addr % 16; if (extra != 0) { addr -= extra; out << to_hex(addr); } for (size_t n = 0; n < extra; ++n) { out << "-- "; } std::string ascii; for (uint8_t* ptr = data; ptr < data + length; ++ptr) { if (((size_t)ptr % 16) == 0) { out << ascii << "\n[" << to_hex((size_t)ptr) << "] : "; ascii.clear(); } ascii.push_back(*ptr >= ' ' && *ptr <= '~' ? *ptr : '.'); out << to_hex(*ptr); } out << " " << ascii; out << "\n"; return out.str(); } std::string bytes(uint64_t c) { char print_buffer[64] = { 0 }; if (c > 1099511627776) { // 1TB snprintf(print_buffer, 64, "%.2fT", (c / 1099511627776.0)); } else if (c > 1073741824) { // 1GB snprintf(print_buffer, 64, "%.2fG", (c / 1073741824.0)); } else if (c > 1048576) { // 1MB snprintf(print_buffer, 64, "%.2fM", (c / 1048576.0)); } else if (c > 1024) { // 1KB snprintf(print_buffer, 64, "%.2fK", (c / 1024.0)); } else { // Bytes return std::to_string(c); } return print_buffer; } uint32_t rgb(float red, float green, float blue) { return (((uint32_t)(red * 255)) << 16) | (((uint32_t)(green * 255)) << 8) | ((uint32_t)(blue * 255)); } /* NOTE: Parameters here are `int` instead of `uint32_t` or `uint8_t` to prevent ambiguity error with rgb(float, float, float) */ uint32_t rgb(int red, int green, int blue) { return ((uint32_t)red << 16) | ((uint32_t)green << 8) | (uint32_t)blue; } 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); } }); t.detach(); } size_t utf8len(const std::string &str) { size_t i = 0, iBefore = 0, count = 0; const char* s = str.c_str(); if (*s == 0) return 0; while (s[i] > 0) { ascii: i++; } count += i - iBefore; while (s[i]) { if (s[i] > 0) { iBefore = i; goto ascii; } else { switch (0xF0 & s[i]) { case 0xE0: i += 3; break; case 0xF0: i += 4; break; default: i += 2; break; } } count++; } return count; } std::string utf8substr(const std::string& str, std::string::size_type start, std::string::size_type leng) { if (leng == 0) { return ""; } if (start == 0 && leng >= utf8len(str)) { return str; } std::string::size_type i, ix, q, min = std::string::npos, max = std::string::npos; for (q = 0, i = 0, ix = str.length(); i < ix; i++, q++) { if (q == start) min = i; if (q <= start + leng || leng == std::string::npos) max = i; unsigned char c = (unsigned char)str[i]; if (c < 0x80) i += 0; else if ((c & 0xE0) == 0xC0) i += 1; else if ((c & 0xF0) == 0xE0) i += 2; else if ((c & 0xF8) == 0xF0) i += 3; else return ""; //invalid utf8 } if (q <= start + leng || leng == std::string::npos) max = i; if (min == std::string::npos || max == std::string::npos) return ""; return str.substr(min, max); } std::string read_file(const std::string& filename) { try { std::ifstream ifs(filename, std::ios::binary); return std::string((std::istreambuf_iterator(ifs)), (std::istreambuf_iterator())); } catch (const std::exception& e) { /* Rethrow as dpp::file_exception */ throw dpp::file_exception(e.what()); } } std::string validate(const std::string& value, size_t _min, size_t _max, const std::string& exception_message) { if (utf8len(value) < _min) { throw dpp::length_exception(exception_message); } else if (utf8len(value) > _max) { return utf8substr(value, 0, _max); } return value; } std::string timestamp(time_t ts, time_format tf) { char format[2] = { (char)tf, 0 }; return ""; } std::string avatar_size(uint32_t size) { if (size) { return "?size=" + std::to_string(size); } return std::string(); } std::vector tokenize(std::string const &in, const char* sep) { std::string::size_type b = 0; std::vector result; while ((b = in.find_first_not_of(sep, b)) != std::string::npos) { auto e = in.find(sep, b); result.push_back(in.substr(b, e-b)); b = e; } return result; } std::string bot_invite_url(const snowflake bot_id, const uint64_t permissions, const std::vector& scopes) { std::string scope; if (scopes.size()) { for (auto& s : scopes) { scope += s + "+"; } scope = scope.substr(0, scope.length() - 1); } return "https://discord.com/oauth2/authorize?client_id=" + std::to_string(bot_id) + "&permissions=" + std::to_string(permissions) + "&scope=" + scope; } std::function cout_logger() { return [](const dpp::log_t& event) { if (event.severity > dpp::ll_trace) { std::cout << "[" << dpp::utility::current_date_time() << "] " << dpp::utility::loglevel(event.severity) << ": " << event.message << "\n"; } }; } std::function log_error() { return [](const dpp::confirmation_callback_t& detail) { if (detail.is_error()) { if (detail.bot) { detail.bot->log( dpp::ll_error, "Error " + std::to_string(detail.get_error().code) + " [" + detail.get_error().message + "] on API request, returned content was: " + detail.http_info.body ); } } }; } /* Hexadecimal sequence for URL encoding */ static const char* hex = "0123456789ABCDEF"; std::string url_encode(const std::string &value) { // Reserve worst-case encoded length of string, input length * 3 std::string escaped(value.length() * 3, '\0'); char* data = escaped.data(); for (auto i = value.begin(); i != value.end(); ++i) { unsigned char c = (unsigned char)(*i); if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') { // Keep alphanumeric and other accepted characters intact *data++ = c; } else { // Any other characters are percent-encoded *data++ = '%'; *data++ = hex[c >> 4]; *data++ = hex[c & 0x0f]; } } *data = 0; return escaped.data(); } std::string slashcommand_mention(snowflake command_id, const std::string &command_name, const std::string &subcommand) { return ""; } std::string slashcommand_mention(snowflake command_id, const std::string &command_name, const std::string &subcommand_group, const std::string &subcommand) { return ""; } std::string make_url_parameters(const std::map& parameters) { std::string output; for(auto& [k, v] : parameters) { if (!k.empty() && !v.empty()) { output.append("&").append(k).append("=").append(url_encode(v)); } } if (!output.empty()) { output[0] = '?'; } return output; } std::string make_url_parameters(const std::map& parameters) { std::map params; for(auto& [k, v] : parameters) { if (v != 0) { params[k] = std::to_string(v); } } return make_url_parameters(params); } std::string markdown_escape(const std::string& text, bool escape_code_blocks) { /** * @brief Represents the current state of the finite state machine * for the markdown_escape function. */ enum md_state { /// normal text md_normal = 0, /// a paragraph code block, represented by three backticks md_big_code_block = 1, /// an inline code block, represented by one backtick md_small_code_block = 2, }; md_state state = md_normal; std::string output; const std::string markdown_chars("\\*_|~[]()>"); for (size_t n = 0; n < text.length(); ++n) { if (text.substr(n, 3) == "```") { /* Start/end a paragraph code block */ output += (escape_code_blocks ? "\\`\\`\\`" : "```"); n += 2; state = (state == md_normal) ? md_big_code_block : md_normal; } else if (text[n] == '`' && (escape_code_blocks || state != md_big_code_block)) { /* Start/end of an inline code block */ output += (escape_code_blocks ? "\\`" : "`"); state = (state == md_normal) ? md_small_code_block : md_normal; } else { /* Normal text */ if (escape_code_blocks || state == md_normal) { /* Markdown sequence characters */ if (markdown_chars.find(text[n]) != std::string::npos) { output += "\\"; } } output += text[n]; } } return output; } std::string version() { return DPP_VERSION_TEXT; } void set_thread_name(const std::string& name) { #ifdef HAVE_PRCTL prctl(PR_SET_NAME, reinterpret_cast(name.substr(0, 15).c_str()), NULL, NULL, NULL); #else #if HAVE_PTHREAD_SETNAME_NP #if HAVE_SINGLE_PARAMETER_SETNAME_NP pthread_setname_np(name.substr(0, 15).c_str()); #endif #if HAVE_TWO_PARAMETER_SETNAME_NP pthread_setname_np(pthread_self(), name.substr(0, 15).c_str()); #endif #endif #endif } }; };