diff --git a/deps/iprogsjson.hpp b/deps/iprogsjson.hpp new file mode 100644 index 00000000..eb9e21ce --- /dev/null +++ b/deps/iprogsjson.hpp @@ -0,0 +1,1954 @@ +// +// iProgramInCpp's JSON Parser +// +// Copyright (C) 2025 iProgramInCpp. Licensed under the MIT License. +// + +#pragma once +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +// This is iProgramInCpp's JSON parser, which hopes to be faster than nlohmann::json while being +// semi-compatible with it. +// +// This was designed for Purplecord which is an iOS 3 compatible Discord client, based on Discord +// Messenger. +// +// NOTE: This is not going to be very standards-compliant, it'll only support a small subset of JSON. + +// Disable exceptions. Use if using exceptions is not allowed - the program will terminate on error instead. +//#define IPROG_JSON_DISABLE_EXCEPTIONS + +// Enable keeping the original string that determines a JSON object. +// This substantially increases the memory usage, but allows for easier debugging. +//#define IPROG_JSON_ENABLE_ORIGINAL_STRING + +namespace iprog +{ + template + using TEnableIf = typename std::enable_if::type; + + enum class eType { + Null, + Number, + Decimal, + Boolean, + StringShort, + String, + Struct, + Array + }; + + class JsonUtil + { + public: + static uint32_t decode_utf8(const char* byteSeq, int& sizeOut) + { + sizeOut = 1; + + const uint8_t * chars = (const uint8_t*)byteSeq; + uint8_t char0 = chars[0]; + + if (char0 < 0x80) + { + // 1 byte ASCII character. Fine + sizeOut = 1; + return char0; + } + + uint8_t char1 = chars[1]; + if (char1 == 0) return 0xFFFD; // this character is broken. + if (char0 < 0xE0) + { + // 2 byte character. + uint32_t codepoint = ((char0 & 0x1F) << 6) | (char1 & 0x3F); + sizeOut = 2; + return codepoint; + } + + uint8_t char2 = chars[2]; + if (char2 == 0) return 0xFFFD; // this character is broken. + if (char0 < 0xF0) + { + // 3 byte character. + uint32_t codepoint = ((char0 & 0xF) << 12) | ((char1 & 0x3F) << 6) | (char2 & 0x3F); + sizeOut = 3; + return codepoint; + } + + uint8_t char3 = chars[3]; + // 4 byte character. + if (char3 == 0) return 0xFFFD; + + uint32_t codepoint = ((char0 & 0x07) << 18) | ((char1 & 0x3F) << 12) | ((char2 & 0x3F) << 6) | (char3 & 0x3F); + sizeOut = 4; + return codepoint; + } + }; + + class JsonObject; + + // This struct defines a name. Usually it's shorter than 28 characters, so we will be able to + // avoid an expensive heap operation by storing the data directly inside. + class JsonName + { + public: + JsonName() + { + length = 0; + } + + JsonName(const char* name, size_t len = 0) + { + if (len == 0) + len = strlen(name); + + // cut off + if (len > INT_MAX) + len = INT_MAX; + + length = (int) len; + if (len > sizeof(data.nameShort)) { + data.nameLong = new char[len]; + memcpy(data.nameLong, name, len); + } + else { + memcpy(data.nameShort, name, len); + } + } + + JsonName(const std::string& name) : JsonName(name.c_str(), name.size()) {} + + // move constructor can remain simple + JsonName(JsonName&& other) + { + memcpy((void*) this, (void*) &other, sizeof(JsonName)); + memset((void*) &other, 0, sizeof(JsonName)); + } + + // but not the copy constructor + JsonName(const JsonName& other) : JsonName(other.RawData(), other.Length()) {} + + ~JsonName() + { + if (IsLong()) + delete[] data.nameLong; + } + + JsonName& operator=(const JsonName& other) + { + if (this != &other) { + JsonName temp(other); + Swap(temp); + } + return *this; + } + + JsonName& operator=(JsonName&& other) + { + if (this != &other) { + JsonName temp(std::move(other)); + Swap(temp); + } + return *this; + } + + // NOTE: Does NOT return a null terminated string! You must call Length() separately. + const char* RawData() const + { + return IsLong() ? data.nameLong : data.nameShort; + } + + std::string Data() const + { + if (IsLong()) + return std::string(data.nameLong, length); + else + return std::string(data.nameShort, length); + } + + int Length() const { return length; } + + bool Equals(const char* data, size_t len = 0) const noexcept + { + if (len == 0) + len = strlen(data); + + if (Length() != len) + return false; + + return memcmp(RawData(), data, Length()) == 0; + } + + private: + void Swap(JsonName& other) noexcept + { + std::swap(length, other.length); + std::swap(data, other.data); + } + + bool IsLong() const { return length > sizeof(data.nameShort); } + + int length; + union { + char nameShort[28]; + char* nameLong; + } + data; + }; + + class JsonObject + { + public: + // Constructors + + // Default constructor + JsonObject() : type(eType::Null) + { +#ifdef IPROG_JSON_ENABLE_ORIGINAL_STRING + fullParsedString = std::make_shared("Null created from constructor"); +#endif + } + + // Move constructor - can be kept trivial + JsonObject(JsonObject&& other) noexcept + { + type = other.type; + data = other.data; + memset(&other.data, 0, sizeof(other.data)); + other.type = eType::Null; + +#ifdef IPROG_JSON_ENABLE_ORIGINAL_STRING + originalString = std::move(other.originalString); + fullParsedString = other.fullParsedString; +#endif + } + + // Copy constructor - a bit more complicated + JsonObject(const JsonObject& other) + { + type = other.type; + +#ifdef IPROG_JSON_ENABLE_ORIGINAL_STRING + originalString = other.originalString; + fullParsedString = other.fullParsedString; +#endif + + switch (other.type) + { + case eType::Null: + // Do Nothing + break; + + case eType::Number: + data.number = other.data.number; + break; + + case eType::Decimal: + data.decimal = other.data.decimal; + break; + + case eType::Boolean: + data.boolean = other.data.boolean; + break; + + case eType::StringShort: + memcpy(&data.stringShort, &other.data.stringShort, sizeof data.stringShort); + break; + + case eType::String: + data.string.data = new char[other.data.string.size]; + data.string.size = other.data.string.size; + memcpy(data.string.data, other.data.string.data, other.data.string.size); + break; + + case eType::Struct: + // copy the names + data.structured.names = (JsonName*) ::operator new[](other.data.structured.itemCount * sizeof(JsonName)); + for (size_t i = 0; i < other.data.structured.itemCount; i++) { + new (&data.structured.names[i]) JsonName(other.data.structured.names[i]); + } + + // fall through + case eType::Array: + // copy the objects + data.structured.array = new JsonObject*[other.data.structured.itemCount]; + for (size_t i = 0; i < other.data.structured.itemCount; i++) + data.structured.array[i] = new JsonObject(*other.data.structured.array[i]); + + data.structured.itemCount = other.data.structured.itemCount; + break; + } + } + + // Destructor + ~JsonObject() + { + switch (type) + { + case eType::String: + delete[] data.string.data; + break; + + case eType::Struct: + case eType::Array: + internal_clear(); + break; + + default: + // Do Nothing + break; + } + } + + // Copy equals + JsonObject& operator=(const JsonObject& other) + { + if (this != &other) { + JsonObject temp(other); + swap(temp); + } + + return *this; + } + + // Move equals + JsonObject& operator=(JsonObject&& other) noexcept + { + if (this != &other) { + this->~JsonObject(); + new (this) JsonObject(std::move(other)); + } + + return *this; + } + + // Template assignment equals + template::value && !std::is_same::value) || std::is_enum::value>> + JsonObject& operator=(T a) + { + *this = JsonObject(static_cast(a)); +#ifdef IPROG_JSON_ENABLE_ORIGINAL_STRING + fullParsedString = std::make_shared("Number created from equals operator"); +#endif + return *this; + } + + template::value || std::is_same::value>> + JsonObject& operator=(T (&arr)[N]) + { + *this = JsonObject::array(); + this->internal_resize(N); + + for (size_t i = 0; i < N; ++i) + (*this)[i] = JsonObject(arr[i]); + +#ifdef IPROG_JSON_ENABLE_ORIGINAL_STRING + fullParsedString = std::make_shared("Array created from equals operator"); +#endif + return *this; + } + + // Assignment equals + JsonObject& operator=(std::nullptr_t a) { *this = JsonObject(); return *this; } + JsonObject& operator=(bool a) { *this = JsonObject(a); return *this; } + JsonObject& operator=(double a) { *this = JsonObject(a); return *this; } + JsonObject& operator=(const char* a) { *this = JsonObject(a); return *this; } + JsonObject& operator=(const std::string& a) { *this = JsonObject(a); return *this; } + + // Swap + void swap(JsonObject& other) noexcept + { + std::swap(type, other.type); + std::swap(data, other.data); + } + + // Initialize Number + template::value && !std::is_same::value, int> = 0> + JsonObject(T number) + { + type = eType::Number; + data.number = static_cast(number); +#ifdef IPROG_JSON_ENABLE_ORIGINAL_STRING + fullParsedString = std::make_shared("Number created from constructor"); +#endif + } + + // Initialize Decimal + template::value, int> = 0> + JsonObject(T decimal) + { + type = eType::Decimal; + data.decimal = static_cast(decimal); +#ifdef IPROG_JSON_ENABLE_ORIGINAL_STRING + fullParsedString = std::make_shared("Decimal created from constructor"); +#endif + } + + // Initialize Boolean + template::value, int> = 0> + JsonObject(T boolean) + { + type = eType::Boolean; + data.boolean = static_cast(boolean); +#ifdef IPROG_JSON_ENABLE_ORIGINAL_STRING + fullParsedString = std::make_shared("Boolean created from constructor"); +#endif + } + + // Initialize String + JsonObject(const char* str, size_t len = 0) + { + if (len == 0) + len = strlen(str); + + if (len > sizeof(data.stringShort.data)) + { + type = eType::String; + data.string.size = len; + data.string.data = new char[len]; + memcpy(data.string.data, str, len); +#ifdef IPROG_JSON_ENABLE_ORIGINAL_STRING + fullParsedString = std::make_shared("String created from constructor"); +#endif + } + else + { + type = eType::StringShort; + data.stringShort.size = (char)len; + memcpy(data.stringShort.data, str, len); +#ifdef IPROG_JSON_ENABLE_ORIGINAL_STRING + fullParsedString = std::make_shared("StringShort created from constructor"); +#endif + } + } + + JsonObject(const std::string& str) : JsonObject(str.c_str(), str.size()) {} + + // Initialize Array + static JsonObject array() + { + JsonObject obj; + obj.type = eType::Array; + obj.data.structured.itemCount = 0; + obj.data.structured.array = nullptr; + obj.data.structured.names = nullptr; +#ifdef IPROG_JSON_ENABLE_ORIGINAL_STRING + obj.fullParsedString = std::make_shared("Array created from array()"); +#endif + return obj; + } + + // Initialize Struct + static JsonObject object() + { + JsonObject obj; + obj.type = eType::Struct; + obj.data.structured.itemCount = 0; + obj.data.structured.array = nullptr; + obj.data.structured.names = nullptr; +#ifdef IPROG_JSON_ENABLE_ORIGINAL_STRING + obj.fullParsedString = std::make_shared("Struct created from object()"); +#endif + return obj; + } + + // Type Checks + constexpr bool is_structured() const { return type == eType::Struct || type == eType::Array; } + + constexpr bool is_null() const { return type == eType::Null; } + + constexpr bool is_primitive() const { return type == eType::Boolean || type == eType::Decimal || type == eType::Number || type == eType::Null; } + + constexpr bool is_array() const { return type == eType::Array; } + + constexpr bool is_object() const { return type == eType::Struct; } + + constexpr bool is_boolean() const { return type == eType::Boolean; } + + constexpr bool is_number() const { return type == eType::Decimal || type == eType::Number; } + + constexpr bool is_number_float() const { return type == eType::Decimal; } + + constexpr bool is_string() const { return type == eType::String || type == eType::StringShort; } + + // INCOMPATIBILITY: is_number_integer() and is_number_unsigned() can be true at the same time! + // Nlohmann::json does not support this by default, but for my use case it's fine. + constexpr bool is_number_integer() const { return type == eType::Number; } + + constexpr bool is_number_unsigned() const { return type == eType::Number; } + + // Equality + bool operator==(const JsonObject& other) const noexcept + { + if (type != other.type) return false; + + switch (type) + { + case eType::Number: return data.number == other.data.number; + case eType::Decimal: return data.decimal == other.data.decimal; + case eType::Boolean: return data.boolean == other.data.boolean; + + case eType::StringShort: + if (data.stringShort.size != other.data.stringShort.size) + return false; + + return memcmp(data.stringShort.data, other.data.stringShort.data, data.stringShort.size) == 0; + + case eType::String: + if (data.string.size != other.data.string.size) + return false; + + return memcmp(data.string.data, other.data.string.data, data.string.size) == 0; + + case eType::Array: + if (data.structured.itemCount != other.data.structured.itemCount) + return false; + + for (size_t i = 0; i < data.structured.itemCount; i++) + if (data.structured.array[i] != other.data.structured.array[i]) + return false; + + break; + + case eType::Struct: + { + if (data.structured.itemCount != other.data.structured.itemCount) + return false; + + // Big Expensive Check + for (size_t i = 0; i < data.structured.itemCount; i++) + { + int nameLength = data.structured.names[i].Length(); + + for (size_t j = i + 1; j < other.data.structured.itemCount; j++) + { + int otherNameLength = other.data.structured.names[j].Length(); + if (otherNameLength != nameLength) + continue; + + // name length same, compare data + if (memcmp(data.structured.names[i].RawData(), other.data.structured.names[j].RawData(), nameLength) != 0) + continue; + + // names same, check equality + if (data.structured.array[i] != other.data.structured.array[j]) + return false; + } + } + + return true; + } + } + + return false; + } + + bool operator!=(const JsonObject& other) const noexcept { return !(*this == other); } + + // Contains + bool contains(const char* key, size_t keyLen = 0) const + { + if (is_null()) + // null objects CAN be placeholders for missing structures, so handle this case. + // however, they are null, so obviously they can't have references to anything + return false; + + if (!is_object()) + throw_error("json object is not a structure"); + + if (keyLen == 0) + keyLen = strlen(key); + + // If there are no names but there are items in the list + if (!data.structured.names && data.structured.itemCount) + { + fprintf(stderr, "ERROR: No data.structured.names and yet the object has %zu items!\n", data.structured.itemCount); + fflush(stderr); + +#ifdef IPROG_JSON_ENABLE_ORIGINAL_STRING + if (fullParsedString) { + fprintf(stderr, "Printing full string that this was parsed from: %s\n", fullParsedString->c_str()); + fflush(stderr); + } + else { + fprintf(stderr, "NO full parsed string was assigned :(\n"); + } + + fprintf(stderr, "Printing original string: %s\n", originalString.c_str()); + fflush(stderr); +#endif + + fprintf(stderr, "Dumping object:\n"); + fflush(stderr); + + const std::string& obj = dump(); + fprintf(stderr, "%s\n", obj.c_str()); + fflush(stderr); + } + + assert(data.structured.names || !data.structured.itemCount); + for (size_t i = 0; i < data.structured.itemCount; i++) + { + if (data.structured.names[i].Equals(key, keyLen)) + return true; + } + + return false; + } + + bool contains(const std::string& str) const { return contains(str.c_str(), str.size()); } + + // Array Access + template::value && !std::is_same ::value && !std::is_pointer::value>> + JsonObject& operator[](SizeType index) + { + // note: can't be adapted into an array here since any access + // performed would be out of bounds + if (!is_array()) + throw_error("json object is not an array"); + + if (index >= data.structured.itemCount) + throw_error("index out of bounds"); + + return *data.structured.array[index]; + } + + // Struct Access + JsonObject& get_key(const char* key, size_t keyLen = 0) + { + if (is_null()) + // can be re-adapted into an array + *this = JsonObject::object(); + else if (!is_object()) + throw_error("json object is not a structure"); + + if (keyLen == 0) + keyLen = strlen(key); + + for (size_t i = 0; i < data.structured.itemCount; i++) + { + if (data.structured.names[i].Equals(key, keyLen)) + return *data.structured.array[i]; + } + + internal_resize(data.structured.itemCount + 1); + + data.structured.names[data.structured.itemCount - 1] = JsonName(key); + return *data.structured.array[data.structured.itemCount - 1]; + } + + const JsonObject& get_key_cst(const char* key, size_t keyLen = 0) const + { + if (!is_object()) + throw_error("json object is not a structure"); + + if (keyLen == 0) + keyLen = strlen(key); + + for (size_t i = 0; i < data.structured.itemCount; i++) + { + if (data.structured.names[i].Equals(key, keyLen)) + return *data.structured.array[i]; + } + + throw_error("key does not exist"); + } + + JsonObject& operator[](const char* key) { return get_key(key, 0); } + JsonObject& operator[](const std::string& key) { return get_key(key.c_str(), key.size()); } + const JsonObject& operator[](const char* key) const { return get_key_cst(key, 0); } + const JsonObject& operator[](const std::string& key) const { return get_key_cst(key.c_str(), key.size()); } + + // Push Back + void push_back(const JsonObject& object) + { + if (is_null()) + // can be re-adapted into an array + *this = JsonObject::array(); + else if (!is_array()) + throw_error("push_back() json object must be array"); + + size_t sz = data.structured.itemCount; + internal_resize(sz + 1); + internal_set_at(sz, object); + } + + // Empty + bool empty() const + { + if (is_null()) + return true; + + if (!is_structured()) + throw_error("empty() json object must be structured"); + + return data.structured.itemCount == 0; + } + + // Getters + template ::value || std::is_enum::value) + && !std::is_same::value + && !std::is_same::value, int> = 0> + operator T() const + { + if (!is_number_integer()) + throw_error("json object not a number"); + + return static_cast(data.number); + } + + template ::value, int> = 0> + operator T() const + { + if (!is_boolean()) + throw_error("json object not a boolean"); + + return static_cast(data.boolean); + } + + explicit operator double() const + { + if (!is_number_float()) + throw_error("json object not a decimal"); + + return data.decimal; + } + + operator std::string() const + { + if (!is_string()) + throw_error("json object not a string"); + + if (type == eType::StringShort) + return std::string(data.stringShort.data, data.stringShort.size); + else + return std::string(data.string.data, data.string.size); + } + + // Size + size_t size() const noexcept + { + switch (type) + { + case eType::Null: + return 0; + case eType::Struct: + case eType::Array: + return data.structured.itemCount; + default: + return 1; + } + } + + // Iterators + class iterator + { + /// iterator_category, value_type, difference_type, pointer, and reference. + using iterator_category = std::bidirectional_iterator_tag; + using value_type = JsonObject; + using pointer = JsonObject*; + using reference = JsonObject&; + using difference_type = size_t; + + public: + iterator(JsonName* namePtr, JsonObject** objectPtr) : name(namePtr), object(objectPtr) {} + + reference operator*() const { return **object; } + pointer operator->() const { return *object; } + + // increments and decrements + iterator& operator++() + { + if (name) + name++; + object++; + return *this; + } + + iterator operator++(int) + { + iterator tmp = *this; + ++(*this); + return tmp; + } + + iterator& operator--() + { + if (name) + name--; + object--; + return *this; + } + + iterator operator--(int) + { + iterator tmp = *this; + --(*this); + return tmp; + } + + friend bool operator==(const iterator& a, const iterator& b) { return a.object == b.object; } + friend bool operator!=(const iterator& a, const iterator& b) { return a.object != b.object; } + + std::string key() const + { + if (!name) + throw_error("cannot get key from array iterator"); + + return name->Data(); + } + + reference value() const { return **object; } + + private: + JsonName* name; + JsonObject** object; + }; + + class const_iterator + { + /// iterator_category, value_type, difference_type, pointer, and reference. + using iterator_category = std::bidirectional_iterator_tag; + using value_type = JsonObject; + using pointer = const JsonObject*; + using reference = const JsonObject&; + using difference_type = size_t; + + public: + const_iterator(JsonName* namePtr, const JsonObject* const* objectPtr) : name(namePtr), object(objectPtr) {} + + reference operator*() const { return **object; } + pointer operator->() const { return *object; } + + // increments and decrements + const_iterator& operator++() + { + if (name) + name++; + object++; + return *this; + } + + const_iterator operator++(int) + { + const_iterator tmp = *this; + ++(*this); + return tmp; + } + + const_iterator& operator--() + { + if (name) + name--; + object--; + return *this; + } + + const_iterator operator--(int) + { + const_iterator tmp = *this; + --(*this); + return tmp; + } + + friend bool operator==(const const_iterator& a, const const_iterator& b) { return a.object == b.object; } + friend bool operator!=(const const_iterator& a, const const_iterator& b) { return a.object != b.object; } + + std::string key() const + { + if (!name) + throw_error("cannot get key from array iterator"); + + return name->Data(); + } + + reference value() const { return **object; } + + private: + const JsonName* name; + const JsonObject* const* object; + }; + + iterator begin() + { + if (is_null()) + return iterator(nullptr, nullptr); + + if (!is_structured()) + throw_error("json object cannot be iterated"); + + return iterator( + is_object() ? data.structured.names : nullptr, + data.structured.array + ); + } + + iterator end() + { + if (is_null()) + return iterator(nullptr, nullptr); + + // I will cheat by omitting the structured checks here, because we use iterator::end() + // significantly more times than iterator::begin(). Do not use ::end() if this is not + // an array or object, otherwise this is UB. + return iterator( + is_object() ? data.structured.names + data.structured.itemCount : nullptr, + data.structured.array + data.structured.itemCount + ); + } + + const_iterator begin() const + { + if (!is_structured()) + throw_error("json object cannot be iterated"); + + return const_iterator( + is_object() ? data.structured.names : nullptr, + data.structured.array + ); + } + + const_iterator end() const + { + // I will cheat by omitting the structured checks here, because we use iterator::end() + // significantly more times than iterator::begin(). Do not use ::end() if this is not + // an array or object, otherwise this is UB. + return const_iterator( + is_object() ? data.structured.names + data.structured.itemCount : nullptr, + data.structured.array + data.structured.itemCount + ); + } + + iterator find(const char* key, size_t keyLen = 0) + { + if (!is_object()) + throw_error("find() json object must be structured object"); + + if (keyLen == 0) + keyLen = strlen(key); + + for (size_t i = 0; i < data.structured.itemCount; i++) + { + if (data.structured.names[i].Equals(key, keyLen)) + { + return iterator( + &data.structured.names[i], + &data.structured.array[i] + ); + } + } + + return end(); + } + + const_iterator find(const char* key, size_t keyLen = 0) const + { + if (!is_object()) + throw_error("find() json object must be structured object"); + + if (keyLen == 0) + keyLen = strlen(key); + + for (size_t i = 0; i < data.structured.itemCount; i++) + { + if (data.structured.names[i].Equals(key, keyLen)) + { + return const_iterator( + &data.structured.names[i], + &data.structured.array[i] + ); + } + } + + return end(); + } + + iterator find(const std::string& key) + { + return find(key.c_str(), key.size()); + } + + const_iterator find(const std::string& key) const + { + return find(key.c_str(), key.size()); + } + + iterator find(const JsonObject& value) + { + if (!is_structured()) + throw_error("find() json object must be structured object"); + + for (size_t i = 0; i < data.structured.itemCount; i++) + { + if (*data.structured.array[i] == value) + { + return iterator( + is_object() ? &data.structured.names[i] : nullptr, + &data.structured.array[i] + ); + } + } + + return end(); + } + + const_iterator find(const JsonObject& value) const + { + if (!is_structured()) + throw_error("find() json object must be structured object"); + + for (size_t i = 0; i < data.structured.itemCount; i++) + { + if (*data.structured.array[i] == value) + { + return const_iterator( + is_object() ? &data.structured.names[i] : nullptr, + &data.structured.array[i] + ); + } + } + + return end(); + } + + // JSON Generation + std::string dump() const + { + switch (type) + { + case eType::Null: return "null"; + case eType::Boolean: return data.boolean ? "true" : "false"; + case eType::Number: return std::to_string(data.number); + case eType::Decimal: return std::to_string(data.decimal); + case eType::String: return "\"" + escape_string(std::string(data.string.data, data.string.size)) + "\""; + case eType::StringShort: return "\"" + escape_string(std::string(data.stringShort.data, data.stringShort.size)) + "\""; + } + + std::string dump = "["; + switch (type) + { + case eType::Array: + { + dumpAsArray: + bool first = true; + + for (size_t i = 0; i < data.structured.itemCount; i++) + { + if (!first) + dump += ","; + + first = false; + + dump += data.structured.array[i]->dump(); + } + + dump += "]"; + return dump; + } + + case eType::Struct: + { + if (!data.structured.names) + { + dump = "??[ERROR ERROR CORRUPTED - Struct missing names, dumping as array]??["; + goto dumpAsArray; + } + + dump = "{"; + bool first = true; + + for (size_t i = 0; i < data.structured.itemCount; i++) + { + if (!first) + dump += ","; + + first = false; + + dump += "\"" + data.structured.names[i].Data() + "\":"; + dump += data.structured.array[i]->dump(); + } + + dump += "}"; + return dump; + } + + default: return "??"; + } + } + + // Debug + const char* get_type() const + { + switch (type) + { + case eType::Null: return "Null"; + case eType::String: return "String"; + case eType::StringShort: return "StringShort"; + case eType::Number: return "Number"; + case eType::Decimal: return "Decimal"; + case eType::Boolean: return "Boolean"; + case eType::Array: return "Array"; + case eType::Struct: return "Struct"; + default: return "???"; + } + } + + static std::string escape_string(const std::string& str) + { + std::string new_string; + new_string.reserve(str.size() * 2); + + for (size_t i = 0; i < str.size(); i++) + { + char chr = str[i]; + + // common escapes + if (chr == 0) new_string += "\0"; + else if (chr == '\a') new_string += "\\a"; + else if (chr == '\b') new_string += "\\b"; + else if (chr == '\x1F') new_string += "\\e"; + else if (chr == '\n') new_string += "\\n"; + else if (chr == '\t') new_string += "\\t"; + else if (chr == '\v') new_string += "\\v"; + else if (chr == '\f') new_string += "\\f"; + else if (chr == '"') new_string += "\\\""; + else if (chr == '\\') new_string += "\\\\"; + // UTF-8 + else if (chr < 0) { + new_string += utf8_to_unicode_escape(str.c_str() + i, i); + i--; + } + // default + else new_string += chr; + } + + return new_string; + } + + static std::string utf8_to_unicode_escape(const char* ptr, size_t& i) + { + int szOut = 0; + uint32_t code = JsonUtil::decode_utf8(ptr, szOut); + + if (szOut == 0) szOut = 1; + i += szOut; + + char buff[16]; + if (code < 0x10000) + { + snprintf(buff, sizeof buff, "\\u%04X", code); + } + else + { + uint32_t n = code - 0x10000; + uint32_t high = 0xD800 + (n >> 10); + uint32_t low = 0xDC00 + (n & 0x3FF); + snprintf(buff, sizeof buff, "\\u%04X\\u%04X", high, low); + } + + return std::string(buff); + } + + protected: + friend class JsonParser; + + // Exception + [[noreturn]] + static void throw_error(const char* message, ...) + { + va_list vl; + va_start(vl, message); + +#ifdef IPROG_JSON_DISABLE_EXCEPTIONS + fprintf(stderr, "JSON error: "); + vfprintf(stderr, message, vl); + fflush(stderr); + std::terminate(); +#else + char buffer[512]; + vsnprintf(buffer, sizeof buffer, message, vl); + buffer[sizeof buffer - 1] = 0; + + throw std::runtime_error(buffer); +#endif + } + + // Array Manipulation + void internal_clear() + { + assert(is_structured()); + + bool isObject = is_object(); + + if (isObject && data.structured.names) + { + // since we created the names array using placement new, we must use placement delete + for (size_t i = 0; i < data.structured.itemCount; i++) + data.structured.names[i].~JsonName(); + + ::operator delete[](data.structured.names); + } + + if (data.structured.array) + { + for (size_t i = 0; i < data.structured.itemCount; i++) + delete data.structured.array[i]; + + delete[] data.structured.array; + } + + data.structured.array = nullptr; + data.structured.names = nullptr; + data.structured.itemCount = 0; + } + + void internal_resize(size_t newSize) + { + // TODO: Exception safety. It's neglected for speed right now. + // The only exception that can be thrown here is std::bad_alloc, which is bad news anyway. + assert(is_structured()); + + if (newSize == 0) + { + internal_clear(); + return; + } + + JsonObject** newObjects = nullptr; + JsonName* newNames = nullptr; + + if (newSize > 50000 || newSize == 0) { + fprintf(stderr, "ATOMIC NUCLEAR ALERT: Trying to allocate %zu items", newSize); + fflush(stderr); + } + + newObjects = new JsonObject*[newSize]; + if (is_object()) + newNames = (JsonName*) ::operator new[](newSize * sizeof(JsonName)); + + size_t objectsToCopy = data.structured.itemCount; + if (objectsToCopy > newSize) + objectsToCopy = newSize; + + // copy the objects we have to copy + for (size_t i = 0; i < objectsToCopy; i++) + { + newObjects[i] = data.structured.array[i]; + + data.structured.array[i] = nullptr; + + if (newNames) + new (&newNames[i]) JsonName(std::move(data.structured.names[i])); + } + + // and default initialize the rest + for (size_t i = objectsToCopy; i < newSize; i++) + { + newObjects[i] = new JsonObject(); + + if (newNames) + new (&newNames[i]) JsonName(); + } + + // destroy the old data + internal_clear(); + + // replace it with the new data + data.structured.array = newObjects; + data.structured.names = newNames; + data.structured.itemCount = newSize; + } + + void internal_set_at(size_t index, const JsonObject& object) + { + *data.structured.array[index] = std::move(object); + } + + void internal_set_at(size_t index, JsonObject&& object) + { + *data.structured.array[index] = std::move(object); + } + + void internal_set_at(size_t index, const std::string& name, JsonObject&& object) + { + *data.structured.array[index] = std::move(object); + data.structured.names[index] = JsonName(name); + } + + private: + eType type = eType::Null; + + union + { + // valid for eType::Number + int64_t number; + + // valid for eType::Decimal + double decimal; + + // valid for eType::Boolean + bool boolean; + + // valid for eType::StringShort + struct { + char data[31]; + char size; + } + stringShort; + + // valid for eType::String + struct { + char* data; + size_t size; + } + string; + + // structured.names is only valid for eType::Struct, + // structured.array and structured.itemCount are valid for eType::Array too + struct { + size_t itemCount; + JsonObject** array; + JsonName* names; + } + structured; + } + data; + +#ifdef IPROG_JSON_ENABLE_ORIGINAL_STRING + std::string originalString; + std::shared_ptr fullParsedString; + + public: + void set_original_string(const char* str, size_t len = 0) + { + if (len == 0) + len = strlen(str); + + originalString = std::string(str, len); + } + + void set_original_string(const std::string& str) { set_original_string(str.c_str(), str.size()); } + + void set_full_parsed_string(std::shared_ptr str) + { + fullParsedString = str; + } +#endif + }; + + class JsonParser + { + public: + JsonParser() {} + ~JsonParser() {} + + public: + // Public Interfaces + static JsonObject parse(const char* data, size_t len = 0) + { + if (len == 0) + len = strlen(data); + + JsonParser parser; + parser.set_data_input(data, len); + return parser.parse(); + } + static JsonObject parse(const std::string& data) + { + return parse(data.c_str(), data.size()); + } + + public: +#ifdef IPROG_JSON_ENABLE_ORIGINAL_STRING + JsonObject _parse() +#else + JsonObject parse() +#endif + { + if (reached_end()) + JsonObject::throw_error("no object"); + + // what kind of object is this? + char firstCharacter = get_skip(); + + if (firstCharacter == '{') + return parse_object(); + + if (firstCharacter == '[') + return parse_array(); + + if (firstCharacter == '"') + return parse_string(); + + if (firstCharacter == 'f' || firstCharacter == 't' || firstCharacter == 'n') + { + unget(); + return parse_boolean_or_null(); + } + + if (firstCharacter == '-' || firstCharacter == '.' || (firstCharacter >= '0' && firstCharacter <= '9')) + { + unget(); + return parse_number(); + } + + JsonObject::throw_error("unexpected character '%c'", firstCharacter); + } + +#ifdef IPROG_JSON_ENABLE_ORIGINAL_STRING + JsonObject parse() + { + size_t cursorBeg = cursor; + JsonObject object = _parse(); + size_t cursorEnd = cursor; + + if (cursorBeg != 0) + cursorBeg--; // seems like original string cuts off 1 character?? not sure why. + + object.set_original_string(data + cursorBeg, cursorEnd - cursorBeg); + object.set_full_parsed_string(fullString); + return object; + } + + void set_full_parsed_string(std::shared_ptr ptr) + { + fullString = ptr; + } +#endif + + void set_data_input(const char* in_data, size_t in_size) + { + data = in_data; + size = in_size; + +#ifdef IPROG_JSON_ENABLE_ORIGINAL_STRING + if (!fullString) + fullString = std::make_shared(data, size); +#endif + } + + private: + JsonObject parse_object() + { + // estimate the amount of items in the array by pre-scanning it for commas. + size_t itemCount = 1; + bool isEmpty = false; + + int braceLevel = 0, curlyLevel = 0; + for (size_t i = cursor; i != size; i++) + { + switch (data[i]) + { + case '}': + // if we aren't in any braces other than the main ones, + // then we've reached the end of this array object. + if (curlyLevel == 0) + { + if (i == cursor) + isEmpty = true; + i = size - 1; // stop scanning + } + else + { + curlyLevel--; + } + break; + + case ',': + if (braceLevel == 0 && curlyLevel == 0) + itemCount++; + break; + + case '{': curlyLevel++; break; + case '[': braceLevel++; break; + case ']': braceLevel--; break; + } + } + + JsonObject mainObject = JsonObject::object(); + mainObject.internal_resize(isEmpty ? 0 : itemCount); + + char pk = peek_skip(); + + size_t itemPos = 0; + while (pk != '}' && !reached_end()) + { + // parse the name + std::string name = parse_string_2(true); + + char colon = get_skip(); + if (colon != ':') + JsonObject::throw_error("expected ':'"); + + // parse the object + JsonParser parser; +#ifdef IPROG_JSON_ENABLE_ORIGINAL_STRING + parser.set_full_parsed_string(fullString); +#endif + parser.set_data_input(data + cursor, size - cursor); + JsonObject object = JsonParser::parse(); + +#ifdef IPROG_JSON_ENABLE_ORIGINAL_STRING + object.set_full_parsed_string(fullString); +#endif + + // skip its size + cursor += parser.cursor; + + // insert it into our array + if (itemPos >= itemCount) + { + // THIS SHOULD NOT HAPPEN! Severe performance penalty otherwise! + fprintf(stderr, "THIS SHOULD NOT HAPPEN! parse_object() expanding from %zu to %zu", itemCount, itemPos + 1); + fflush(stderr); + itemCount = itemPos + 1; + mainObject.internal_resize(itemCount); + } + + mainObject.internal_set_at(itemPos, name, std::move(object)); + itemPos++; + + // comma incoming, skip it too + pk = peek_skip(); + if (pk == ',') { + get(); + pk = peek_skip(); + } + } + + if (!isEmpty && itemPos != itemCount) + { + // TODO: performance penalty here! Should just update the capacity! + // This edge case is hit for trailing commas + mainObject.internal_resize(itemPos); + } + + if (reached_end()) + JsonObject::throw_error("unterminated struct"); + + pk = get(); + assert(pk == '}'); + + return mainObject; + } + + JsonObject parse_array() + { + // estimate the amount of items in the array by pre-scanning it for commas. + size_t itemCount = 1; + bool isEmpty = false; + + int curlyLevel = 0, braceLevel = 0; + for (size_t i = cursor; i != size; i++) + { + switch (data[i]) + { + case ']': + // if we aren't in any braces other than the main ones, + // then we've reached the end of this array object. + if (braceLevel == 0) + { + if (i == cursor) + isEmpty = true; + i = size - 1; // stop scanning + } + else + { + braceLevel--; + } + break; + + case ',': + if (curlyLevel == 0 && braceLevel == 0) + itemCount++; + break; + + case '[': braceLevel++; break; + case '{': curlyLevel++; break; + case '}': curlyLevel--; break; + } + } + + JsonObject array = JsonObject::array(); + array.internal_resize(isEmpty ? 0 : itemCount); + + char pk = peek_skip(); + + size_t itemPos = 0; + while (pk != ']' && !reached_end()) + { + // parse the object + JsonParser parser; +#ifdef IPROG_JSON_ENABLE_ORIGINAL_STRING + parser.set_full_parsed_string(fullString); +#endif + parser.set_data_input(data + cursor, size - cursor); + JsonObject object = JsonParser::parse(); + +#ifdef IPROG_JSON_ENABLE_ORIGINAL_STRING + object.set_full_parsed_string(fullString); +#endif + + // skip its size + cursor += parser.cursor; + + // insert it into our array + if (itemPos >= itemCount) + { + // THIS SHOULD NOT HAPPEN! Severe performance penalty otherwise! + fprintf(stderr, "THIS SHOULD NOT HAPPEN! parse_array() expanding from %zu to %zu", itemCount, itemPos + 1); + fflush(stderr); + itemCount = itemPos + 1; + array.internal_resize(itemCount); + } + + array.internal_set_at(itemPos, std::move(object)); + itemPos++; + + // comma incoming, skip it too + pk = peek_skip(); + if (pk == ',') { + get(); + pk = peek_skip(); + } + } + + if (!isEmpty && itemPos != itemCount) + { + // TODO: performance penalty here! Should just update the capacity! + // This edge case is hit for trailing commas + array.internal_resize(itemPos); + } + + if (reached_end()) + JsonObject::throw_error("unterminated array"); + + pk = get(); + assert(pk == ']'); + + return array; + } + + JsonObject parse_number() + { + size_t current = cursor; + size_t end; + bool hasDot = false; + + // TODO: Decimal support with exponents + for (end = current + 1; end != size; end++) + { + char chr = data[end]; + if (chr >= '0' && chr <= '9') continue; + + if (chr == '.') + { + if (hasDot) + JsonObject::throw_error("two or more dots in number"); + hasDot = true; + continue; + } + + // unrecognized character, so break + break; + } + + cursor = end; + + if (hasDot) + { + // OPTIMIZE: todo, optimize this + double dbl; + std::stringstream ss = std::stringstream(std::string(&data[current], end - current)); + ss >> dbl; + + return JsonObject(dbl); + } + else + { + // parse manually + int64_t t = 0; + bool neg = false; + size_t i = current; + if (i != end && data[i] == '-') { + neg = true; + i++; + } + + for (; i != end; i++) + t = t * 10 + (data[i] - '0'); + + if (neg) + t = -t; + + return JsonObject(t); + } + } + + JsonObject parse_boolean_or_null() + { + char chr = get(); + + switch (chr) + { + case 'f': // false + cursor += 4; + return JsonObject(false); + + case 't': // true + cursor += 3; + return JsonObject(true); + + case 'n': // null + cursor += 3; + return JsonObject(); + } + + JsonObject::throw_error("unexpected character '%c'", chr); + } + + JsonObject parse_string() + { + return JsonObject(parse_string_2()); + } + + std::string parse_string_2(bool parseWithBeginningQuote = false) + { + if (parseWithBeginningQuote) + { + char chr = get_skip(); + if (chr != '"') + JsonObject::throw_error("expected '\"'"); + } + + // NOTE: we have to parse the string because we need to solve escape characters + std::string str; + str.reserve(256); + + do + { + if (reached_end()) + JsonObject::throw_error("malformed string"); + + char chr = get(); + + if (chr == '"') break; + + if (chr == '\\') + { + // escape character + if (reached_end()) + JsonObject::throw_error("malformed string"); + + char nextChr = get(); + switch (nextChr) + { + default: + // escaped character + str += nextChr; + break; + + // known escapes + case '0': str += '\0'; break; + case 'a': str += '\a'; break; + case 'b': str += '\b'; break; + case 'e': str += '\x1F'; break; + case 'f': str += '\f'; break; + case 'n': str += '\n'; break; + case 't': str += '\t'; break; + case 'v': str += '\v'; break; + + case 'x': + JsonObject::throw_error("NYI: hex escapes"); + break; + + case 'u': + { + // parse this hex code + uint32_t codePoint1 = parse_hex_char_escape(); + str += become_utf8(codePoint1); + break; + } + } + continue; + } + + str += chr; + } + while (true); + + // string was parsed + return str; + } + + size_t get_cursor() const + { + return cursor; + } + + char get() + { + if (cursor >= size) + return -1; + + return data[cursor++]; + } + + void unget() + { + if (cursor == 0) + return; + + cursor--; + } + + char peek() const + { + if (cursor >= size) + return -1; + + return data[cursor]; + } + + bool reached_end() const + { + return cursor >= size; + } + + // gets a character that isn't whitespace + char get_skip() + { + while (isspace(peek()) && !reached_end()) get(); + return get(); + } + + char peek_skip() + { + while (isspace(peek()) && !reached_end()) get(); + return peek(); + } + + uint32_t parse_hex_char_escape() + { + uint32_t codePoint1 = 0; + + for (int i = 0; i < 4; i++) + { + if (reached_end()) + JsonObject::throw_error("malformed \\u escape code"); + + char chr = get(); + codePoint1 <<= 4; + codePoint1 += from_hex(chr); + } + + // N.B. A surrogate pair must start with a high surrogate codepoint + // and must end with a low surrogate codepoint. This means that we + // can get away with only checking for high surrogates, any low surrogates + // should've been preceded by high surrogates + if (codePoint1 >= 0xD800 && codePoint1 <= 0xDBFF) + { + // Surrogate pair + size_t cursorBackup = cursor; + char backslashExpected = get(); + char uExpected = get(); + + if (backslashExpected == '\\' && uExpected == 'u') + { + // parse the second code + uint32_t codePoint2 = 0; + for (int i = 0; i < 4; i++) + { + if (reached_end()) + JsonObject::throw_error("malformed \\u escape code"); + + char chr = get(); + codePoint2 <<= 4; + codePoint2 += from_hex(chr); + } + + // combine it with code point 1 to get a full 32-bit unicode character + codePoint1 = 0x10000 + ((codePoint1 - 0xD800) << 10) + (codePoint2 - 0xDC00); + } + else + { + // undo the consumption of two characters + cursor = cursorBackup; + } + } + + return codePoint1; + } + + int from_hex(char chr) + { + if (chr >= '0' && chr <= '9') + return chr - '0'; + else if (chr >= 'A' && chr <= 'F') + return chr - 'A' + 0xA; + else if (chr >= 'a' && chr <= 'f') + return chr - 'a' + 0xA; + else + JsonObject::throw_error("invalid hex digit"); + } + + static std::string become_utf8(uint32_t codePoint) + { + std::string str; + if (codePoint < 0x80) + { + str += char(codePoint); + return str; + } + + if (codePoint < 0x800) + { + str += char(0b11000000 | (codePoint >> 6)); + str += char(0b10000000 | (codePoint & 0x3F)); + return str; + } + + if (codePoint < 0x10000) + { + str += char(0b11100000 | (codePoint >> 12)); + str += char(0b10000000 | ((codePoint >> 6) & 0x3F)); + str += char(0b10000000 | (codePoint & 0x3F)); + return str; + } + + if (codePoint < 0x110000) + { + str += char(0b11110000 | (codePoint >> 18)); + str += char(0b10000000 | ((codePoint >> 12) & 0x3F)); + str += char(0b10000000 | ((codePoint >> 6) & 0x3F)); + str += char(0b10000000 | (codePoint & 0x3F)); + return str; + } + + return become_utf8(0xFFFD); + } + + private: +#ifdef IPROG_JSON_ENABLE_ORIGINAL_STRING + std::shared_ptr fullString; +#endif + const char* data = nullptr; + size_t cursor = 0; + size_t size = 0; + }; +}; diff --git a/deps/iprogsthreads b/deps/iprogsthreads index abaaa60e..9fcb90a9 160000 --- a/deps/iprogsthreads +++ b/deps/iprogsthreads @@ -1 +1 @@ -Subproject commit abaaa60e7d9b6d289c692341902bff36d37448cb +Subproject commit 9fcb90a9e540ea6f31028f41e8f50a0a30634f2d diff --git a/deps/mwas b/deps/mwas index 9f622498..894a0df3 160000 --- a/deps/mwas +++ b/deps/mwas @@ -1 +1 @@ -Subproject commit 9f6224982ce9048df0485d2f1edcd17db75eeb5e +Subproject commit 894a0df3e8ba47c45da873d59575fe60c1e2387b diff --git a/src/discord/Attachment.hpp b/src/discord/Attachment.hpp index e3231c1e..974c45e6 100644 --- a/src/discord/Attachment.hpp +++ b/src/discord/Attachment.hpp @@ -24,7 +24,7 @@ class Attachment ContentType::eType m_contentType = ContentType::BLOB; public: - void Load(nlohmann::json& j); + void Load(iprog::JsonObject& j); void UpdatePreviewSize() { diff --git a/src/discord/Channel.cpp b/src/discord/Channel.cpp index cdd54145..f2a175a8 100644 --- a/src/discord/Channel.cpp +++ b/src/discord/Channel.cpp @@ -1,4 +1,4 @@ -#include +#include #include "Channel.hpp" #include "ProfileCache.hpp" #include "DiscordInstance.hpp" diff --git a/src/discord/DiscordClientConfig.cpp b/src/discord/DiscordClientConfig.cpp index 7b3c4024..b0c92da9 100644 --- a/src/discord/DiscordClientConfig.cpp +++ b/src/discord/DiscordClientConfig.cpp @@ -48,7 +48,7 @@ DiscordClientConfig::DiscordClientConfig() m_secChUa = "\"Not?A_Brand\";v=\"99\", \"Chromium\";v=\"130\""; // Ok, now serialize it - nlohmann::json j = Serialize(); + iprog::JsonObject j = Serialize(); std::string str = j.dump(); m_serializedJsonBlob = str; @@ -61,9 +61,9 @@ DiscordClientConfig::DiscordClientConfig() m_serializedBase64Blob = dataToSend; } -nlohmann::json DiscordClientConfig::Serialize() const +iprog::JsonObject DiscordClientConfig::Serialize() const { - nlohmann::json j; + iprog::JsonObject j; j["app_arch"] = m_appArch; j["browser"] = m_browser; j["browser_user_agent"] = m_browserUserAgent; diff --git a/src/discord/DiscordClientConfig.hpp b/src/discord/DiscordClientConfig.hpp index 7b798457..0d6f1404 100644 --- a/src/discord/DiscordClientConfig.hpp +++ b/src/discord/DiscordClientConfig.hpp @@ -1,6 +1,6 @@ #pragma once -#include +#include #include class DiscordClientConfig @@ -14,7 +14,7 @@ class DiscordClientConfig const std::string& GetTimezone() const; const std::string& GetOS() const; - nlohmann::json Serialize() const; + iprog::JsonObject Serialize() const; const std::string& GetSerializedJsonBlob() const; const std::string& GetSerializedBase64Blob() const; diff --git a/src/discord/DiscordInstance.cpp b/src/discord/DiscordInstance.cpp index 4c253086..f9170c64 100644 --- a/src/discord/DiscordInstance.cpp +++ b/src/discord/DiscordInstance.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include "DiscordInstance.hpp" #include "WebsocketClient.hpp" @@ -12,13 +12,13 @@ #ifndef _DEBUG #define TRY try -#define CATCH catch (Json::exception& ex) { OnJsonException(ex); } +#define CATCH catch (std::exception& ex) { OnJsonException(ex); } #else #define TRY do #define CATCH while (0) #endif -using Json = nlohmann::json; +using Json = iprog::JsonObject; // Creates a temporary snowflake based on time. Snowflake CreateTemporarySnowflake() @@ -65,14 +65,14 @@ void DebugResponse(NetRequest* pReq) //OutputDebugStringA(str.c_str()); } -void OnJsonException(Json::exception & ex) +void OnJsonException(std::exception & ex) { GetFrontend()->OnJsonException(ex.what()); } void DiscordInstance::OnFetchedChannels(Guild* pGld, const std::string& content) { - Json j = Json::parse(content); + Json j = iprog::JsonParser::parse(content); pGld->m_channels.clear(); @@ -649,7 +649,7 @@ void DiscordInstance::HandleRequest(NetRequest* pRequest) if (pRequest->itype != IMAGE && pRequest->itype != IMAGE_ATTACHMENT) { - j = Json::parse(pRequest->response); + j = iprog::JsonParser::parse(pRequest->response); } switch (pRequest->itype) @@ -779,7 +779,7 @@ void DiscordInstance::HandleRequest(NetRequest* pRequest) } } #ifndef _DEBUG - catch (Json::exception& ex) + catch (std::exception& ex) { OnJsonException(ex); } @@ -1159,7 +1159,7 @@ void DiscordInstance::HandleGatewayMessage(const std::string& payload) { DbgPrintF("Got Payload: %s [PAYLOAD ENDS HERE]", payload.c_str()); - Json j = Json::parse(payload); + Json j = iprog::JsonParser::parse(payload); int op = j["op"]; using namespace GatewayOp; @@ -1716,7 +1716,7 @@ void DiscordInstance::UpdateSettingsInfo() SetActivityStatus(GetSettingsManager()->GetOnlineIndicator(), false); } -void DiscordInstance::ParsePermissionOverwrites(Channel& channel, nlohmann::json& j) +void DiscordInstance::ParsePermissionOverwrites(Channel& channel, iprog::JsonObject& j) { if (!j.contains("permission_overwrites")) return; @@ -1737,7 +1737,7 @@ void DiscordInstance::ParsePermissionOverwrites(Channel& channel, nlohmann::json } } -void DiscordInstance::ParseReadStateObject(nlohmann::json& readState, bool bAlternate) +void DiscordInstance::ParseReadStateObject(iprog::JsonObject& readState, bool bAlternate) { Snowflake id = GetSnowflake(readState, bAlternate ? "channel_id" : "id"); Snowflake lastMessageId = GetSnowflake(readState, bAlternate ? "message_id" : "last_message_id"); @@ -1770,7 +1770,7 @@ void DiscordInstance::ParseReadStateObject(nlohmann::json& readState, bool bAlte GetFrontend()->UpdateChannelAcknowledge(id, lastMessageId); } -void DiscordInstance::ParseChannel(Channel& c, nlohmann::json& chan, int& num) +void DiscordInstance::ParseChannel(Channel& c, iprog::JsonObject& chan, int& num) { c.m_snowflake = GetSnowflake(chan, "id"); c.m_channelType = Channel::eChannelType(int(chan["type"])); @@ -2001,7 +2001,7 @@ bool DiscordInstance::SortGuilds() #endif } -void DiscordInstance::ParseAndAddGuild(nlohmann::json& elem) +void DiscordInstance::ParseAndAddGuild(iprog::JsonObject& elem) { std::string uu = elem.dump(); @@ -2165,9 +2165,9 @@ void DiscordInstance::HandleREADY_SUPPLEMENTAL(Json& j) } // Look for any activities -- TODO: Server specific activities - if (guildPres.contains("game") && !guildPres["game"].is_null()) - pf->m_status = GetStatusStringFromGameJsonObject(guildPres["game"]); - else if (guildPres.contains("activities") && !memPres["activities"].is_null()) + if (memPres.contains("game") && !memPres["game"].is_null()) + pf->m_status = GetStatusStringFromGameJsonObject(memPres["game"]); + else if (memPres.contains("activities") && !memPres["activities"].is_null()) pf->m_status = GetStatusFromActivities(memPres["activities"]); else pf->m_status = ""; @@ -2458,7 +2458,7 @@ void DiscordInstance::HandleMESSAGE_DELETE(Json& j) GetFrontend()->OnDeleteMessage(messageId); } -void DiscordInstance::HandleMESSAGE_ACK(nlohmann::json& j) +void DiscordInstance::HandleMESSAGE_ACK(iprog::JsonObject& j) { Json& data = j["d"]; @@ -2467,7 +2467,7 @@ void DiscordInstance::HandleMESSAGE_ACK(nlohmann::json& j) ParseReadStateObject(data, true); } -void DiscordInstance::HandleUSER_GUILD_SETTINGS_UPDATE(nlohmann::json& j) +void DiscordInstance::HandleUSER_GUILD_SETTINGS_UPDATE(iprog::JsonObject& j) { Json& data = j["d"]; Snowflake guildID = GetSnowflake(data, "guild_id"); @@ -2476,7 +2476,7 @@ void DiscordInstance::HandleUSER_GUILD_SETTINGS_UPDATE(nlohmann::json& j) pSettings->Load(data); } -void DiscordInstance::HandleUSER_NOTE_UPDATE(nlohmann::json& j) +void DiscordInstance::HandleUSER_NOTE_UPDATE(iprog::JsonObject& j) { Json& data = j["d"]; Snowflake uid = GetSnowflake(data, "id"); @@ -2686,7 +2686,7 @@ void DiscordInstance::HandleGUILD_MEMBER_LIST_UPDATE(Json& j) GetFrontend()->UpdateMemberList(); } -Snowflake DiscordInstance::ParseGuildMember(Snowflake guild, nlohmann::json& memb, Snowflake userID) +Snowflake DiscordInstance::ParseGuildMember(Snowflake guild, iprog::JsonObject& memb, Snowflake userID) { Json& pres = memb["presence"], &user = memb["user"], & roles = memb["roles"]; Profile* pf = nullptr; @@ -2745,7 +2745,7 @@ Snowflake DiscordInstance::ParseGuildMember(Snowflake guild, nlohmann::json& mem return userID; } -void DiscordInstance::HandlePRESENCE_UPDATE(nlohmann::json& j) +void DiscordInstance::HandlePRESENCE_UPDATE(iprog::JsonObject& j) { Json& data = j["d"]; @@ -2775,7 +2775,7 @@ void DiscordInstance::HandlePRESENCE_UPDATE(nlohmann::json& j) GetFrontend()->UpdateUserData(userID); } -void DiscordInstance::HandlePASSIVE_UPDATE_V1(nlohmann::json& j) +void DiscordInstance::HandlePASSIVE_UPDATE_V1(iprog::JsonObject& j) { Json& data = j["d"]; Snowflake guildId = GetSnowflake(data, "guild_id"); @@ -2801,7 +2801,7 @@ void DiscordInstance::HandlePASSIVE_UPDATE_V1(nlohmann::json& j) } } -void DiscordInstance::HandleGUILD_MEMBERS_CHUNK(nlohmann::json& j) +void DiscordInstance::HandleGUILD_MEMBERS_CHUNK(iprog::JsonObject& j) { Json& data = j["d"]; Snowflake guildId = GetSnowflake(data, "guild_id"); @@ -2828,7 +2828,7 @@ void DiscordInstance::HandleGUILD_MEMBERS_CHUNK(nlohmann::json& j) GetFrontend()->RefreshMembers(memsToRefresh); } -void DiscordInstance::HandleTYPING_START(nlohmann::json& j) +void DiscordInstance::HandleTYPING_START(iprog::JsonObject& j) { Json& data = j["d"]; Snowflake guildID = 0; @@ -2850,7 +2850,7 @@ void DiscordInstance::HandleTYPING_START(nlohmann::json& j) GetFrontend()->OnStartTyping(userID, guildID, chanID, startTime); } -Snowflake DiscordInstance::ParseGuildMemberOrGroup(Snowflake guild, nlohmann::json& item) +Snowflake DiscordInstance::ParseGuildMemberOrGroup(Snowflake guild, iprog::JsonObject& item) { if (item.contains("group")) { Json& grp = item["group"]; @@ -2876,7 +2876,7 @@ Snowflake DiscordInstance::ParseGuildMemberOrGroup(Snowflake guild, nlohmann::js } } -void DiscordInstance::HandleGuildMemberListUpdate_Sync(Snowflake guild, nlohmann::json& jx) +void DiscordInstance::HandleGuildMemberListUpdate_Sync(Snowflake guild, iprog::JsonObject& jx) { Guild* pGld = GetGuild(guild); assert(pGld); @@ -2891,7 +2891,7 @@ void DiscordInstance::HandleGuildMemberListUpdate_Sync(Snowflake guild, nlohmann pGld->m_members.push_back(ParseGuildMemberOrGroup(guild, item)); } -void DiscordInstance::HandleGuildMemberListUpdate_Insert(Snowflake guild, nlohmann::json& j) +void DiscordInstance::HandleGuildMemberListUpdate_Insert(Snowflake guild, iprog::JsonObject& j) { Guild* pGld = GetGuild(guild); assert(pGld); @@ -2909,7 +2909,7 @@ void DiscordInstance::HandleGuildMemberListUpdate_Insert(Snowflake guild, nlohma pGld->m_members.insert(pGld->m_members.begin() + index, sf); } -void DiscordInstance::HandleGuildMemberListUpdate_Delete(Snowflake guild, nlohmann::json& j) +void DiscordInstance::HandleGuildMemberListUpdate_Delete(Snowflake guild, iprog::JsonObject& j) { Guild* pGld = GetGuild(guild); assert(pGld); @@ -2933,7 +2933,7 @@ void DiscordInstance::HandleGuildMemberListUpdate_Delete(Snowflake guild, nlohma pGld->m_members.erase(pGld->m_members.begin() + index); } -void DiscordInstance::HandleGuildMemberListUpdate_Update(Snowflake guild, nlohmann::json& j) +void DiscordInstance::HandleGuildMemberListUpdate_Update(Snowflake guild, iprog::JsonObject& j) { Guild* pGld = GetGuild(guild); assert(pGld); @@ -2965,7 +2965,7 @@ void DiscordInstance::OnUploadAttachmentFirst(NetRequest* pReq) return; } - Json j = Json::parse(pReq->response); + Json j = iprog::JsonParser::parse(pReq->response); assert(j["attachments"].size() == 1); for (auto& att : j["attachments"]) diff --git a/src/discord/DiscordInstance.hpp b/src/discord/DiscordInstance.hpp index 7bb6e02e..f5edf4f6 100644 --- a/src/discord/DiscordInstance.hpp +++ b/src/discord/DiscordInstance.hpp @@ -5,7 +5,7 @@ #include #include #include -#include +#include #include "DiscordAPI.hpp" #include "Snowflake.hpp" #include "SettingsManager.hpp" @@ -490,60 +490,60 @@ class DiscordInstance public: // returns user's id. The user parameter is used only if j["user"] doesn't exist - Snowflake ParseGuildMember(Snowflake guild, nlohmann::json& j, Snowflake user = 0); - Snowflake ParseGuildMemberOrGroup(Snowflake guild, nlohmann::json& j); + Snowflake ParseGuildMember(Snowflake guild, iprog::JsonObject& j, Snowflake user = 0); + Snowflake ParseGuildMemberOrGroup(Snowflake guild, iprog::JsonObject& j); private: void InitDispatchFunctions(); void UpdateSettingsInfo(); bool SortGuilds(); - void ParseChannel(Channel& c, nlohmann::json& j, int& num); - void ParseAndAddGuild(nlohmann::json& j); - void ParsePermissionOverwrites(Channel& c, nlohmann::json& j); - void ParseReadStateObject(nlohmann::json& j, bool bAlternate); + void ParseChannel(Channel& c, iprog::JsonObject& j, int& num); + void ParseAndAddGuild(iprog::JsonObject& j); + void ParsePermissionOverwrites(Channel& c, iprog::JsonObject& j); + void ParseReadStateObject(iprog::JsonObject& j, bool bAlternate); void OnUploadAttachmentFirst(NetRequest* pReq); void OnUploadAttachmentSecond(NetRequest* pReq); void SearchSubGuild(std::vector& matches, Guild* pGuild, int matchFlags, const char* query); std::string TransformMention(const std::string& source, Snowflake guild, Snowflake channel); // handle functions - void HandleREADY(nlohmann::json& j); - void HandleREADY_SUPPLEMENTAL(nlohmann::json& j); - void HandleMESSAGE_CREATE(nlohmann::json& j); - void HandleMESSAGE_DELETE(nlohmann::json& j); - void HandleMESSAGE_UPDATE(nlohmann::json& j); - void HandleMESSAGE_ACK(nlohmann::json& j); - void HandleUSER_GUILD_SETTINGS_UPDATE(nlohmann::json& j); - void HandleUSER_SETTINGS_PROTO_UPDATE(nlohmann::json& j); - void HandleUSER_NOTE_UPDATE(nlohmann::json& j); - void HandleGUILD_CREATE(nlohmann::json& j); - void HandleGUILD_DELETE(nlohmann::json& j); - void HandleCHANNEL_CREATE(nlohmann::json& j); - void HandleCHANNEL_DELETE(nlohmann::json& j); - void HandleCHANNEL_UPDATE(nlohmann::json& j); - void HandleGUILD_MEMBER_LIST_UPDATE(nlohmann::json& j); - void HandleGUILD_MEMBERS_CHUNK(nlohmann::json& j); - void HandleTYPING_START(nlohmann::json& j); - void HandlePRESENCE_UPDATE(nlohmann::json& j); - void HandlePASSIVE_UPDATE_V1(nlohmann::json& j); + void HandleREADY(iprog::JsonObject& j); + void HandleREADY_SUPPLEMENTAL(iprog::JsonObject& j); + void HandleMESSAGE_CREATE(iprog::JsonObject& j); + void HandleMESSAGE_DELETE(iprog::JsonObject& j); + void HandleMESSAGE_UPDATE(iprog::JsonObject& j); + void HandleMESSAGE_ACK(iprog::JsonObject& j); + void HandleUSER_GUILD_SETTINGS_UPDATE(iprog::JsonObject& j); + void HandleUSER_SETTINGS_PROTO_UPDATE(iprog::JsonObject& j); + void HandleUSER_NOTE_UPDATE(iprog::JsonObject& j); + void HandleGUILD_CREATE(iprog::JsonObject& j); + void HandleGUILD_DELETE(iprog::JsonObject& j); + void HandleCHANNEL_CREATE(iprog::JsonObject& j); + void HandleCHANNEL_DELETE(iprog::JsonObject& j); + void HandleCHANNEL_UPDATE(iprog::JsonObject& j); + void HandleGUILD_MEMBER_LIST_UPDATE(iprog::JsonObject& j); + void HandleGUILD_MEMBERS_CHUNK(iprog::JsonObject& j); + void HandleTYPING_START(iprog::JsonObject& j); + void HandlePRESENCE_UPDATE(iprog::JsonObject& j); + void HandlePASSIVE_UPDATE_V1(iprog::JsonObject& j); private: - void HandleGuildMemberListUpdate_Sync(Snowflake guild, nlohmann::json& j); - void HandleGuildMemberListUpdate_Insert(Snowflake guild, nlohmann::json& j); - void HandleGuildMemberListUpdate_Delete(Snowflake guild, nlohmann::json& j); - void HandleGuildMemberListUpdate_Update(Snowflake guild, nlohmann::json& j); - void HandleMessageInsertOrUpdate(nlohmann::json& j, bool bIsUpdate); + void HandleGuildMemberListUpdate_Sync(Snowflake guild, iprog::JsonObject& j); + void HandleGuildMemberListUpdate_Insert(Snowflake guild, iprog::JsonObject& j); + void HandleGuildMemberListUpdate_Delete(Snowflake guild, iprog::JsonObject& j); + void HandleGuildMemberListUpdate_Update(Snowflake guild, iprog::JsonObject& j); + void HandleMessageInsertOrUpdate(iprog::JsonObject& j, bool bIsUpdate); }; DiscordInstance* GetDiscordInstance(); int64_t GetIntFromString(const std::string& str); -Snowflake GetSnowflake(const nlohmann::json& j, const std::string& key); +Snowflake GetSnowflake(const iprog::JsonObject& j, const std::string& key); // Fetches a key -std::string GetFieldSafe(const nlohmann::json& j, const std::string& key); +std::string GetFieldSafe(const iprog::JsonObject& j, const std::string& key); -int GetFieldSafeInt(const nlohmann::json& j, const std::string& key); +int GetFieldSafeInt(const iprog::JsonObject& j, const std::string& key); #define TYPING_INTERVAL 10000 // 10 sec diff --git a/src/discord/Emoji.cpp b/src/discord/Emoji.cpp index 1b260da3..8c50fded 100644 --- a/src/discord/Emoji.cpp +++ b/src/discord/Emoji.cpp @@ -2,7 +2,7 @@ #include "Util.hpp" #include "ProfileCache.hpp" -void Emoji::Load(const nlohmann::json& j) +void Emoji::Load(const iprog::JsonObject& j) { m_id = GetSnowflake(j, "id"); m_name = GetFieldSafe(j, "name"); diff --git a/src/discord/Emoji.hpp b/src/discord/Emoji.hpp index f9cb1005..883ea08b 100644 --- a/src/discord/Emoji.hpp +++ b/src/discord/Emoji.hpp @@ -1,13 +1,13 @@ #pragma once #include -#include +#include #include "Snowflake.hpp" class Emoji { public: - void Load(const nlohmann::json& j); + void Load(const iprog::JsonObject& j); public: Snowflake m_id = 0; diff --git a/src/discord/Frontend.hpp b/src/discord/Frontend.hpp index d8cc94ad..7bd968ce 100644 --- a/src/discord/Frontend.hpp +++ b/src/discord/Frontend.hpp @@ -2,7 +2,7 @@ #include #include -#include +#include #include #include "Snowflake.hpp" #include "ScrollDir.hpp" diff --git a/src/discord/Guild.cpp b/src/discord/Guild.cpp index 843a7851..d3adacf2 100644 --- a/src/discord/Guild.cpp +++ b/src/discord/Guild.cpp @@ -5,7 +5,7 @@ #include "DiscordRequest.hpp" #include "DiscordInstance.hpp" -void GuildRole::Load(nlohmann::json& j) +void GuildRole::Load(iprog::JsonObject& j) { m_id = GetSnowflake(j, "id"); m_permissions = GetIntFromString(j["permissions"]); diff --git a/src/discord/Guild.hpp b/src/discord/Guild.hpp index e66acdaa..16d0f8a0 100644 --- a/src/discord/Guild.hpp +++ b/src/discord/Guild.hpp @@ -4,7 +4,7 @@ #include #include #include -#include +#include #include "Snowflake.hpp" #include "Channel.hpp" #include "Emoji.hpp" @@ -40,7 +40,7 @@ struct GuildRole return false; } - void Load(nlohmann::json& j); + void Load(iprog::JsonObject& j); }; struct GuildMemberGroup diff --git a/src/discord/LocalSettings.cpp b/src/discord/LocalSettings.cpp index bbd55259..f341cdf9 100644 --- a/src/discord/LocalSettings.cpp +++ b/src/discord/LocalSettings.cpp @@ -1,10 +1,10 @@ #include -#include +#include #include "LocalSettings.hpp" #include "Util.hpp" #include "DiscordAPI.hpp" #include "Frontend.hpp" -using nlohmann::json; +using iprog::JsonObject; static LocalSettings* g_pInstance; @@ -53,7 +53,7 @@ bool LocalSettings::Load() else m_messageStyle = MS_3DFACE; - json j = json::parse(data); + JsonObject j = iprog::JsonParser::parse(data); // Load properties from the json object. if (j.contains("Token")) @@ -164,8 +164,8 @@ bool LocalSettings::Load() bool LocalSettings::Save() { - json j; - json trustedDomains; + JsonObject j; + JsonObject trustedDomains; for (auto& dom : m_trustedDomains) trustedDomains.push_back(dom); diff --git a/src/discord/Message.cpp b/src/discord/Message.cpp index 0489d1a8..553feb59 100644 --- a/src/discord/Message.cpp +++ b/src/discord/Message.cpp @@ -2,7 +2,7 @@ #include "DiscordInstance.hpp" #include "Util.hpp" -using Json = nlohmann::json; +using Json = iprog::JsonObject; void Message::SetDate(const std::string & dateStr) { @@ -68,7 +68,7 @@ bool Message::CheckWasMentioned(Snowflake user, Snowflake guild, bool bSuppressE return false; } -void ReferenceMessage::Load(nlohmann::json& data, Snowflake guild) +void ReferenceMessage::Load(iprog::JsonObject& data, Snowflake guild) { // Cheap clone Json& author = data["author"]; diff --git a/src/discord/Message.hpp b/src/discord/Message.hpp index 5d92406f..c7f89861 100644 --- a/src/discord/Message.hpp +++ b/src/discord/Message.hpp @@ -4,7 +4,7 @@ #include #include #include -#include +#include #include "Snowflake.hpp" #include "Attachment.hpp" #include "MessageType.hpp" @@ -28,7 +28,7 @@ struct ReferenceMessage bool m_bIsAuthorBot = false; std::set m_userMentions; - void Load(nlohmann::json& msgData, Snowflake guild); + void Load(iprog::JsonObject& msgData, Snowflake guild); }; struct RichEmbedField @@ -85,7 +85,7 @@ struct RichEmbed // list of fields std::vector m_fields; - void Load(nlohmann::json& j); + void Load(iprog::JsonObject& j); }; class Message @@ -149,7 +149,7 @@ class Message bool CheckWasMentioned(Snowflake user, Snowflake guild, bool bSuppressEveryone = false, bool bSuppressRoles = false) const; - void Load(nlohmann::json& j, Snowflake guild); + void Load(iprog::JsonObject& j, Snowflake guild); }; typedef std::shared_ptr MessagePtr; diff --git a/src/discord/MessageCache.cpp b/src/discord/MessageCache.cpp index d7540caa..10100093 100644 --- a/src/discord/MessageCache.cpp +++ b/src/discord/MessageCache.cpp @@ -5,7 +5,7 @@ constexpr int MESSAGES_PER_REQUEST = 50; -using nlohmann::json; +using iprog::JsonObject; static MessageCache g_MCSingleton; MessageCache::MessageCache() @@ -21,7 +21,7 @@ void MessageCache::GetLoadedMessages(Snowflake channel, Snowflake guild, std::li out.push_back(msg.second); } -void MessageCache::ProcessRequest(Snowflake channel, ScrollDir::eScrollDir sd, Snowflake anchor, nlohmann::json& j, const std::string& channelName) +void MessageCache::ProcessRequest(Snowflake channel, ScrollDir::eScrollDir sd, Snowflake anchor, iprog::JsonObject& j, const std::string& channelName) { MessageChunkList& lst = m_mapMessages[channel]; lst.ProcessRequest(sd, anchor, j, channelName); @@ -87,7 +87,7 @@ MessageChunkList::MessageChunkList() m_messages[msg->m_snowflake] = msg; } -void MessageChunkList::ProcessRequest(ScrollDir::eScrollDir sd, Snowflake gap, json& j, const std::string& channelName) +void MessageChunkList::ProcessRequest(ScrollDir::eScrollDir sd, Snowflake gap, JsonObject& j, const std::string& channelName) { Snowflake lowestMsg = (Snowflake) -1LL, highestMsg = 0; @@ -103,7 +103,7 @@ void MessageChunkList::ProcessRequest(ScrollDir::eScrollDir sd, Snowflake gap, j // for each message int receivedMessages = 0; - for (json& data : j) + for (auto& data : j) { auto msg = MakeMessage(); msg->Load(data, m_guild); diff --git a/src/discord/MessageCache.hpp b/src/discord/MessageCache.hpp index 27c4788c..9c850d58 100644 --- a/src/discord/MessageCache.hpp +++ b/src/discord/MessageCache.hpp @@ -2,7 +2,7 @@ #include #include -#include +#include #include "Snowflake.hpp" #include "ScrollDir.hpp" #include "Message.hpp" @@ -16,7 +16,7 @@ struct MessageChunkList Snowflake m_guild = 0; MessageChunkList(); - void ProcessRequest(ScrollDir::eScrollDir sd, Snowflake anchor, nlohmann::json& j, const std::string& channelName); + void ProcessRequest(ScrollDir::eScrollDir sd, Snowflake anchor, iprog::JsonObject& j, const std::string& channelName); void AddMessage(const Message& msg); void EditMessage(const Message& msg); void DeleteMessage(Snowflake message); @@ -32,7 +32,7 @@ class MessageCache void GetLoadedMessages(Snowflake channel, Snowflake guild, std::list& out); // note: scroll dir used to add gap message - void ProcessRequest(Snowflake channel, ScrollDir::eScrollDir sd, Snowflake anchor, nlohmann::json& j, const std::string& channelName); + void ProcessRequest(Snowflake channel, ScrollDir::eScrollDir sd, Snowflake anchor, iprog::JsonObject& j, const std::string& channelName); void AddMessage(Snowflake channel, const Message& msg); void EditMessage(Snowflake channel, const Message& msg); diff --git a/src/discord/MessagePoll.cpp b/src/discord/MessagePoll.cpp index 3af801d6..68d9e2a5 100644 --- a/src/discord/MessagePoll.cpp +++ b/src/discord/MessagePoll.cpp @@ -1,7 +1,7 @@ #include "MessagePoll.hpp" #include "Util.hpp" -MessagePoll::MessagePoll(const nlohmann::json& j) +MessagePoll::MessagePoll(const iprog::JsonObject& j) { // Encapsulating JSON decomposition in a try-catch block because // I fear exceptions inside of constructors. diff --git a/src/discord/MessagePoll.hpp b/src/discord/MessagePoll.hpp index 53def2b1..1ddf6c32 100644 --- a/src/discord/MessagePoll.hpp +++ b/src/discord/MessagePoll.hpp @@ -1,6 +1,6 @@ #pragma once -#include +#include #include #include #include @@ -27,7 +27,7 @@ class MessagePoll bool m_bIsFinalized = false; public: - MessagePoll(const nlohmann::json&); + MessagePoll(const iprog::JsonObject&); MessagePoll(const MessagePoll& oth); }; diff --git a/src/discord/Profile.cpp b/src/discord/Profile.cpp index 7d150d0b..eb369cdb 100644 --- a/src/discord/Profile.cpp +++ b/src/discord/Profile.cpp @@ -1,3 +1,4 @@ +#include #include "Profile.hpp" #include "ProfileCache.hpp" #include "Util.hpp" diff --git a/src/discord/ProfileCache.cpp b/src/discord/ProfileCache.cpp index f4de8813..d48027f7 100644 --- a/src/discord/ProfileCache.cpp +++ b/src/discord/ProfileCache.cpp @@ -41,7 +41,7 @@ Profile* ProfileCache::LookupProfile(Snowflake user, const std::string& username return pProf; } -Profile* ProfileCache::LoadProfile(Snowflake user, const nlohmann::json& jx) +Profile* ProfileCache::LoadProfile(Snowflake user, const iprog::JsonObject& jx) { if (!user) { user = GetSnowflake(jx, "id"); @@ -170,7 +170,7 @@ void ProfileCache::PutNote(Snowflake user, const std::string& note) const { // NOTE: Strange how the official discord client just sends a PUT request for blank notes // instead of having a DELETE request to do that. - nlohmann::json j; + iprog::JsonObject j; j["note"] = note; GetHTTPClient()->PerformRequest( diff --git a/src/discord/ProfileCache.hpp b/src/discord/ProfileCache.hpp index 96b70a2f..456c25fb 100644 --- a/src/discord/ProfileCache.hpp +++ b/src/discord/ProfileCache.hpp @@ -1,7 +1,7 @@ #pragma once #include -#include +#include #include "Profile.hpp" #include "Guild.hpp" @@ -14,7 +14,7 @@ class ProfileCache // Username and globalname are filled in to create a default profile if the profile is not cached. // Returns NULL if the Discord servers have reported that the user does not exist. Profile* LookupProfile(Snowflake user, const std::string& username, const std::string& globalName, const std::string& avatarLink, bool bRequestServer = true); - Profile* LoadProfile(Snowflake user, const nlohmann::json& j); + Profile* LoadProfile(Snowflake user, const iprog::JsonObject& j); void ClearAll(); diff --git a/src/discord/Relationship.cpp b/src/discord/Relationship.cpp index a62147d5..d6ca2283 100644 --- a/src/discord/Relationship.cpp +++ b/src/discord/Relationship.cpp @@ -1,7 +1,7 @@ #include "Relationship.hpp" #include "Util.hpp" -void Relationship::Load(const nlohmann::json& j) +void Relationship::Load(const iprog::JsonObject& j) { m_id = GetSnowflake(j, "id"); m_userID = GetSnowflake(j, "user_id"); diff --git a/src/discord/Relationship.hpp b/src/discord/Relationship.hpp index 7ba01543..acd122f5 100644 --- a/src/discord/Relationship.hpp +++ b/src/discord/Relationship.hpp @@ -1,7 +1,7 @@ #pragma once #include -#include +#include #include "Snowflake.hpp" enum eRelationshipType @@ -22,5 +22,5 @@ class Relationship std::string m_nickname; // Friend nickname public: - void Load(const nlohmann::json& j); + void Load(const iprog::JsonObject& j); }; diff --git a/src/discord/UpdateChecker.cpp b/src/discord/UpdateChecker.cpp index 62e36a6a..e57e6c51 100644 --- a/src/discord/UpdateChecker.cpp +++ b/src/discord/UpdateChecker.cpp @@ -1,6 +1,6 @@ #include "UpdateChecker.hpp" #include "Util.hpp" -#include +#include std::string UpdateChecker::GetUpdateAPIURL() { @@ -25,7 +25,7 @@ void UpdateChecker::OnRequestDone(NetRequest* pReq) return; } - nlohmann::json j = nlohmann::json::parse(pReq->response); + iprog::JsonObject j = iprog::JsonParser::parse(pReq->response); // This is specific to the GitHub API. If moving away from GitHub, redo this: std::string tagName = j["tag_name"]; diff --git a/src/discord/UserGuildSettings.cpp b/src/discord/UserGuildSettings.cpp index 60f7c58f..54b24ef2 100644 --- a/src/discord/UserGuildSettings.cpp +++ b/src/discord/UserGuildSettings.cpp @@ -1,13 +1,13 @@ #include "UserGuildSettings.hpp" #include "Util.hpp" -void MuteConfig::Load(const nlohmann::json& j) +void MuteConfig::Load(const iprog::JsonObject& j) { m_endTime = ParseTime(GetFieldSafe(j, "end_time")); m_selectedTimeWindow = GetFieldSafeInt(j, "selected_time_window"); } -void MuteConfig::Load(const nlohmann::json& parent, const std::string& key) +void MuteConfig::Load(const iprog::JsonObject& parent, const std::string& key) { auto it = parent.find(key); if (it == parent.end() || !it->is_structured()) @@ -16,7 +16,7 @@ void MuteConfig::Load(const nlohmann::json& parent, const std::string& key) Load(*it); } -void ChannelOverride::Load(const nlohmann::json& j) +void ChannelOverride::Load(const iprog::JsonObject& j) { m_channelId = GetSnowflake(j, "channel_id"); m_bCollapsed = GetFieldSafeBool(j, "collapsed", false); @@ -51,7 +51,7 @@ bool GuildSettings::IsMuted() const ); } -void GuildSettings::Load(const nlohmann::json& j) +void GuildSettings::Load(const iprog::JsonObject& j) { m_guildID = GetSnowflake(j, "guild_id"); m_flags = GetFieldSafeInt(j, "flags"); @@ -78,7 +78,7 @@ void GuildSettings::Load(const nlohmann::json& j) } } -void UserGuildSettings::Load(const nlohmann::json& j) +void UserGuildSettings::Load(const iprog::JsonObject& j) { bool partial = GetFieldSafeBool(j, "partial", false); if (!partial) diff --git a/src/discord/UserGuildSettings.hpp b/src/discord/UserGuildSettings.hpp index 2b536c0c..f992e7c6 100644 --- a/src/discord/UserGuildSettings.hpp +++ b/src/discord/UserGuildSettings.hpp @@ -2,7 +2,7 @@ #include #include -#include +#include #include "Snowflake.hpp" enum eMessageNotifications @@ -23,8 +23,8 @@ struct MuteConfig time_t m_endTime = 0; int m_selectedTimeWindow = 0; - void Load(const nlohmann::json& j); - void Load(const nlohmann::json& parent, const std::string& key); + void Load(const iprog::JsonObject& j); + void Load(const iprog::JsonObject& parent, const std::string& key); }; struct ChannelOverride @@ -36,7 +36,7 @@ struct ChannelOverride bool m_bMuted = false; MuteConfig m_muteConfig; - void Load(const nlohmann::json& j); + void Load(const iprog::JsonObject& j); bool IsMuted() const; }; @@ -56,7 +56,7 @@ struct GuildSettings eMessageNotifications m_messageNotifications = NOTIF_ALL_MESSAGES; std::map m_channelOverride; - void Load(const nlohmann::json& j); + void Load(const iprog::JsonObject& j); const ChannelOverride* GetOverride(Snowflake channel) const; bool IsMuted() const; }; @@ -67,7 +67,7 @@ struct UserGuildSettings std::map m_guildSettings; int m_version = 0; - void Load(const nlohmann::json& j); + void Load(const iprog::JsonObject& j); void Clear(); const GuildSettings* GetSettings(Snowflake guild) const; diff --git a/src/discord/Util.cpp b/src/discord/Util.cpp index 936857fe..57c02269 100644 --- a/src/discord/Util.cpp +++ b/src/discord/Util.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include "Util.hpp" #include "Frontend.hpp" @@ -219,7 +220,7 @@ int64_t ExtractTimestamp(Snowflake sf) return (sf >> 22) + 1420070400000; } -std::string GetFieldSafe(const nlohmann::json& j, const std::string& key) +std::string GetFieldSafe(const iprog::JsonObject& j, const std::string& key) { if (j.contains(key) && !j[key].is_null()) return j[key]; @@ -227,7 +228,7 @@ std::string GetFieldSafe(const nlohmann::json& j, const std::string& key) return ""; } -int GetFieldSafeInt(const nlohmann::json& j, const std::string& key) +int GetFieldSafeInt(const iprog::JsonObject& j, const std::string& key) { if (j.contains(key) && j[key].is_number_integer()) return j[key]; @@ -242,7 +243,7 @@ std::string FormatDiscrim(int discrim) return std::string(chr); } -std::string GetGlobalName(const nlohmann::json& j) +std::string GetGlobalName(const iprog::JsonObject& j) { if (j.contains("global_name") && !j["global_name"].is_null()) return GetFieldSafe(j, "global_name"); @@ -250,7 +251,7 @@ std::string GetGlobalName(const nlohmann::json& j) return GetFieldSafe(j, "username"); } -std::string GetUsername(const nlohmann::json& j) +std::string GetUsername(const iprog::JsonObject& j) { std::string username = GetFieldSafe(j, "username"); int discrim = int(GetIntFromString(GetFieldSafe(j, "discriminator"))); @@ -270,14 +271,14 @@ int64_t GetIntFromString(const std::string& str) return t; } -bool GetFieldSafeBool(const nlohmann::json& j, const std::string& key, bool default1) +bool GetFieldSafeBool(const iprog::JsonObject& j, const std::string& key, bool default1) { if (j.contains(key) && j[key].is_boolean()) return j[key]; return default1; } -Snowflake GetSnowflakeFromJsonObject(const nlohmann::json& j) { +Snowflake GetSnowflakeFromJsonObject(const iprog::JsonObject& j) { if (j.is_number_integer()) return Snowflake(int64_t(j)); if (j.is_number_unsigned()) @@ -287,7 +288,7 @@ Snowflake GetSnowflakeFromJsonObject(const nlohmann::json& j) { return 0; } -Snowflake GetSnowflake(const nlohmann::json& j, const std::string& key) +Snowflake GetSnowflake(const iprog::JsonObject& j, const std::string& key) { auto ji = j.find(key); if (ji == j.end()) @@ -296,7 +297,7 @@ Snowflake GetSnowflake(const nlohmann::json& j, const std::string& key) return GetSnowflakeFromJsonObject(ji.value()); } -using Json = nlohmann::json; +using Json = iprog::JsonObject; const uint64_t GetTimeMs() noexcept { diff --git a/src/discord/Util.hpp b/src/discord/Util.hpp index 8076e168..7d08f757 100644 --- a/src/discord/Util.hpp +++ b/src/discord/Util.hpp @@ -1,7 +1,7 @@ #pragma once #include -#include +#include #include "Snowflake.hpp" #ifdef _DEBUG @@ -28,15 +28,15 @@ bool IsPotentiallyDangerousDownload(const std::string& filename); int64_t ExtractTimestamp(Snowflake sf); const uint64_t GetTimeMs() noexcept; const uint64_t GetTimeUs() noexcept; -Snowflake GetSnowflakeFromJsonObject(const nlohmann::json& j); -Snowflake GetSnowflake(const nlohmann::json& j, const std::string& key); +Snowflake GetSnowflakeFromJsonObject(const iprog::JsonObject& j); +Snowflake GetSnowflake(const iprog::JsonObject& j, const std::string& key); int64_t GetIntFromString(const std::string& str); std::string FormatDiscrim(int discrim); -std::string GetGlobalName(const nlohmann::json& j); -std::string GetUsername(const nlohmann::json& j); -int GetFieldSafeInt(const nlohmann::json& j, const std::string& key); -bool GetFieldSafeBool(const nlohmann::json& j, const std::string& key, bool default1); -std::string GetFieldSafe(const nlohmann::json& j, const std::string& key); +std::string GetGlobalName(const iprog::JsonObject& j); +std::string GetUsername(const iprog::JsonObject& j); +int GetFieldSafeInt(const iprog::JsonObject& j, const std::string& key); +bool GetFieldSafeBool(const iprog::JsonObject& j, const std::string& key, bool default1); +std::string GetFieldSafe(const iprog::JsonObject& j, const std::string& key); std::string GetMonthName(int mon); const char* GetDaySuffix(int day); time_t ParseTime(const std::string& iso8601); diff --git a/src/windows/PinList.cpp b/src/windows/PinList.cpp index 6a57c606..11d9afc1 100644 --- a/src/windows/PinList.cpp +++ b/src/windows/PinList.cpp @@ -1,4 +1,4 @@ -#include +#include #include "PinList.hpp" #include "MessageList.hpp" @@ -82,7 +82,7 @@ void PinList::Initialize(HWND hWnd) void PinList::OnLoadedPins(Snowflake channelID, const std::string& data) { - nlohmann::json j = nlohmann::json::parse(data); + iprog::JsonObject j = iprog::JsonParser::parse(data); if (!j.is_array()) { assert(!"uh oh"); diff --git a/src/windows/QRCodeDialog.cpp b/src/windows/QRCodeDialog.cpp index 270fde1a..ba285c04 100644 --- a/src/windows/QRCodeDialog.cpp +++ b/src/windows/QRCodeDialog.cpp @@ -1,6 +1,6 @@ #include #include -#include +#include #include #include "QRCodeDialog.hpp" #include "Main.hpp" @@ -11,7 +11,7 @@ #define C_RSA_BITS (2048) #define C_RSA_EXPONENT RSA_F4 // Node.js default exponent value -using Json = nlohmann::json; +using Json = iprog::JsonObject; // singleton static QRCodeDialog g_qcd; @@ -45,7 +45,7 @@ void CheckError() void QRCodeDialog::HandleGatewayMessage(const std::string& payload) { - Json j = Json::parse(payload); + Json j = iprog::JsonParser::parse(payload); std::string op = j["op"]; if (op == "heartbeat") diff --git a/src/windows/UploadDialog.cpp b/src/windows/UploadDialog.cpp index 1cd23a83..7c618602 100644 --- a/src/windows/UploadDialog.cpp +++ b/src/windows/UploadDialog.cpp @@ -2,8 +2,8 @@ #include "Main.hpp" #include #include -#include -using Json = nlohmann::json; +#include +using Json = iprog::JsonObject; #define C_FILE_MAX_SIZE (25*1024*1024) diff --git a/vs/DiscordMessenger.vcxproj b/vs/DiscordMessenger.vcxproj index 827534d4..cd829b6e 100644 --- a/vs/DiscordMessenger.vcxproj +++ b/vs/DiscordMessenger.vcxproj @@ -579,6 +579,7 @@ +