diff --git a/MinecraftClient/Json.cs b/MinecraftClient/Json.cs index 494e1142..da3aa838 100644 --- a/MinecraftClient/Json.cs +++ b/MinecraftClient/Json.cs @@ -1,377 +1,55 @@ -using System; -using System.Collections.Generic; -using System.Text; +using System.Text.Encodings.Web; +using System.Text.Json; +using System.Text.Json.Nodes; -namespace MinecraftClient +namespace MinecraftClient; + +/// +/// JSON utilities backed by System.Text.Json. +/// +public static class Json +{ + private static readonly JsonSerializerOptions s_escapeOptions = new() + { + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping + }; + + /// + /// Parse a JSON string into a mutable DOM. + /// Returns null for null, empty, or whitespace-only input. + /// Returns a wrapping the raw string when the input + /// is not valid JSON (e.g. a plain-text Minecraft MOTD or chat message). + /// + public static JsonNode? ParseJson(string? json) + { + if (string.IsNullOrWhiteSpace(json)) return null; + try { return JsonNode.Parse(json); } + catch (JsonException) { return JsonValue.Create(json); } + } + + /// + /// Escape a string for embedding inside a JSON string literal. + /// Uses System.Text.Json serialization and strips the surrounding quotes. + /// + public static string EscapeString(string src) => + JsonSerializer.Serialize(src, s_escapeOptions)[1..^1]; +} + +/// +/// Extension helpers for that replicate the access patterns +/// of the former JSONData.StringValue property. +/// +public static class JsonNodeExtensions { /// - /// This class parses JSON data and returns an object describing that data. - /// Really lightweight JSON handling by ORelio - (c) 2013 - 2020 + /// Return the string representation of any JSON value. + /// Strings are returned without quotes; numbers, booleans, and null + /// are returned as their text representation. /// - public static class Json + public static string GetStringValue(this JsonNode? node) => node switch { - /// - /// Parse some JSON and return the corresponding JSON object - /// - public static JSONData ParseJson(string json) - { - int cursorpos = 0; - return String2Data(json, ref cursorpos); - } - - /// - /// The class storing unserialized JSON data - /// The data can be an object, an array or a string - /// - public class JSONData - { - public enum DataType - { - Object, - Array, - String - }; - - private readonly DataType type; - - public DataType Type - { - get { return type; } - } - - public Dictionary Properties; - public List DataArray; - public string StringValue; - - public JSONData(DataType datatype) - { - type = datatype; - Properties = new Dictionary(); - DataArray = new List(); - StringValue = String.Empty; - } - } - - /// - /// Parse a JSON string to build a JSON object - /// - /// String to parse - /// Cursor start (set to 0 for function init) - private static JSONData String2Data(string toparse, ref int cursorpos) - { - try - { - JSONData data; - SkipSpaces(toparse, ref cursorpos); - switch (toparse[cursorpos]) - { - //Object - case '{': - data = new JSONData(JSONData.DataType.Object); - cursorpos++; - SkipSpaces(toparse, ref cursorpos); - while (toparse[cursorpos] != '}') - { - if (toparse[cursorpos] == '"') - { - JSONData propertyname = String2Data(toparse, ref cursorpos); - if (toparse[cursorpos] == ':') - { - cursorpos++; - } - else - { - /* parse error ? */ - } - - JSONData propertyData = String2Data(toparse, ref cursorpos); - data.Properties[propertyname.StringValue] = propertyData; - } - else cursorpos++; - } - - cursorpos++; - break; - - //Array - case '[': - data = new JSONData(JSONData.DataType.Array); - cursorpos++; - SkipSpaces(toparse, ref cursorpos); - while (toparse[cursorpos] != ']') - { - if (toparse[cursorpos] == ',') - { - cursorpos++; - } - - JSONData arrayItem = String2Data(toparse, ref cursorpos); - data.DataArray.Add(arrayItem); - } - - cursorpos++; - break; - - //String - case '"': - data = new JSONData(JSONData.DataType.String); - cursorpos++; - while (toparse[cursorpos] != '"') - { - if (toparse[cursorpos] == '\\') - { - try //Unicode character \u0123 - { - if (toparse[cursorpos + 1] == 'u' - && IsHex(toparse[cursorpos + 2]) - && IsHex(toparse[cursorpos + 3]) - && IsHex(toparse[cursorpos + 4]) - && IsHex(toparse[cursorpos + 5])) - { - //"abc\u0123abc" => "0123" => 0123 => Unicode char n°0123 => Add char to string - data.StringValue += char.ConvertFromUtf32(int.Parse( - toparse.Substring(cursorpos + 2, 4), - System.Globalization.NumberStyles.HexNumber)); - cursorpos += 6; - continue; - } - else if (toparse[cursorpos + 1] == 'n') - { - data.StringValue += '\n'; - cursorpos += 2; - continue; - } - else if (toparse[cursorpos + 1] == 'r') - { - data.StringValue += '\r'; - cursorpos += 2; - continue; - } - else if (toparse[cursorpos + 1] == 't') - { - data.StringValue += '\t'; - cursorpos += 2; - continue; - } - else cursorpos++; //Normal character escapement \" - } - catch (IndexOutOfRangeException) - { - cursorpos++; - } // \u01 - catch (ArgumentOutOfRangeException) - { - cursorpos++; - } // Unicode index 0123 was invalid - } - - data.StringValue += toparse[cursorpos]; - cursorpos++; - } - - cursorpos++; - break; - - //Number - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - case '.': - case '-': - data = new JSONData(JSONData.DataType.String); - StringBuilder sb = new(); - while ((toparse[cursorpos] >= '0' && toparse[cursorpos] <= '9') || toparse[cursorpos] == '.' || - toparse[cursorpos] == '-') - { - sb.Append(toparse[cursorpos]); - cursorpos++; - } - - data.StringValue = sb.ToString(); - break; - - //Boolean : true - case 't': - data = new JSONData(JSONData.DataType.String); - cursorpos++; - if (toparse[cursorpos] == 'r') - { - cursorpos++; - } - - if (toparse[cursorpos] == 'u') - { - cursorpos++; - } - - if (toparse[cursorpos] == 'e') - { - cursorpos++; - data.StringValue = "true"; - } - - break; - - //Boolean : false - case 'f': - data = new JSONData(JSONData.DataType.String); - cursorpos++; - if (toparse[cursorpos] == 'a') - { - cursorpos++; - } - - if (toparse[cursorpos] == 'l') - { - cursorpos++; - } - - if (toparse[cursorpos] == 's') - { - cursorpos++; - } - - if (toparse[cursorpos] == 'e') - { - cursorpos++; - data.StringValue = "false"; - } - - break; - - //Null field - case 'n': - data = new JSONData(JSONData.DataType.String); - cursorpos++; - if (toparse[cursorpos] == 'u') - { - cursorpos++; - } - - if (toparse[cursorpos] == 'l') - { - cursorpos++; - } - - if (toparse[cursorpos] == 'l') - { - cursorpos++; - data.StringValue = "null"; - } - - break; - - //Unknown data - default: - cursorpos++; - return String2Data(toparse, ref cursorpos); - } - - SkipSpaces(toparse, ref cursorpos); - return data; - } - catch (IndexOutOfRangeException) - { - return new JSONData(JSONData.DataType.String); - } - } - - /// - /// Check if a char is an hexadecimal char (0-9 A-F a-f) - /// - /// Char to test - /// True if hexadecimal - private static bool IsHex(char c) - { - return ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f')); - } - - /// - /// Advance the cursor to skip white spaces and line breaks - /// - /// String to parse - /// Cursor position to update - private static void SkipSpaces(string toparse, ref int cursorpos) - { - while (cursorpos < toparse.Length - && (char.IsWhiteSpace(toparse[cursorpos]) - || toparse[cursorpos] == '\r' - || toparse[cursorpos] == '\n')) - cursorpos++; - } - - // Original: https://github.com/mono/mono/blob/master/mcs/class/System.Json/System.Json/JsonValue.cs - private static bool NeedEscape(string src, int i) - { - var c = src[i]; - return c < 32 || c == '"' || c == '\\' - // Broken lead surrogate - || (c is >= '\uD800' and <= '\uDBFF' && - (i == src.Length - 1 || src[i + 1] < '\uDC00' || src[i + 1] > '\uDFFF')) - // Broken tail surrogate - || (c is >= '\uDC00' and <= '\uDFFF' && - (i == 0 || src[i - 1] < '\uD800' || src[i - 1] > '\uDBFF')) - // To produce valid JavaScript - || c == '\u2028' || c == '\u2029' - // Escape " tags - || (c == '/' && i > 0 && src[i - 1] == '<'); - } - - public static string EscapeString(string src) - { - var sb = new StringBuilder(); - var start = 0; - - for (var i = 0; i < src.Length; i++) - { - if (!NeedEscape(src, i)) continue; - sb.Append(src, start, i - start); - - switch (src[i]) - { - case '\b': - sb.Append("\\b"); - break; - case '\f': - sb.Append("\\f"); - break; - case '\n': - sb.Append("\\n"); - break; - case '\r': - sb.Append("\\r"); - break; - case '\t': - sb.Append("\\t"); - break; - case '\"': - sb.Append("\\\""); - break; - case '\\': - sb.Append("\\\\"); - break; - case '/': - sb.Append("\\/"); - break; - - default: - sb.Append("\\u"); - sb.Append(((int)src[i]).ToString("x04")); - break; - } - - start = i + 1; - } - - sb.Append(src, start, src.Length - start); - return sb.ToString(); - } - } + null => "null", + JsonValue val when val.TryGetValue(out var s) => s, + _ => node.ToJsonString() + }; } \ No newline at end of file diff --git a/MinecraftClient/Mapping/BlockPalettes/BlockPaletteGenerator.cs b/MinecraftClient/Mapping/BlockPalettes/BlockPaletteGenerator.cs index a1f7b307..1c835892 100644 --- a/MinecraftClient/Mapping/BlockPalettes/BlockPaletteGenerator.cs +++ b/MinecraftClient/Mapping/BlockPalettes/BlockPaletteGenerator.cs @@ -51,8 +51,8 @@ namespace MinecraftClient.Mapping.BlockPalettes HashSet knownStates = new(); Dictionary> blocks = new(); - Json.JSONData palette = Json.ParseJson(File.ReadAllText(blocksJsonFile, Encoding.UTF8)); - foreach (KeyValuePair item in palette.Properties) + var palette = Json.ParseJson(File.ReadAllText(blocksJsonFile, Encoding.UTF8))!.AsObject(); + foreach (var item in palette) { //minecraft:item_name => ItemName string blockType = String.Concat( @@ -65,9 +65,9 @@ namespace MinecraftClient.Mapping.BlockPalettes throw new InvalidDataException("Duplicate block type " + blockType + "!?"); blocks[blockType] = new HashSet(); - foreach (Json.JSONData state in item.Value.Properties["states"].DataArray) + foreach (var state in item.Value!["states"]!.AsArray()) { - int id = int.Parse(state.Properties["id"].StringValue, NumberStyles.Any, CultureInfo.CurrentCulture); + int id = int.Parse(state!["id"].GetStringValue(), NumberStyles.Any, CultureInfo.CurrentCulture); if (knownStates.Contains(id)) throw new InvalidDataException("Duplicate state id " + id + "!?"); diff --git a/MinecraftClient/Protocol/DataTypeGenerator.cs b/MinecraftClient/Protocol/DataTypeGenerator.cs index af5adce8..d8315b81 100644 --- a/MinecraftClient/Protocol/DataTypeGenerator.cs +++ b/MinecraftClient/Protocol/DataTypeGenerator.cs @@ -21,13 +21,13 @@ namespace MinecraftClient.Protocol /// private static Dictionary LoadRegistry(string registriesJsonFile, string jsonRegistryName) { - Json.JSONData rawJson = Json.ParseJson(File.ReadAllText(registriesJsonFile)); - Json.JSONData rawRegistry = rawJson.Properties[jsonRegistryName].Properties["entries"]; + var rawJson = Json.ParseJson(File.ReadAllText(registriesJsonFile)); + var rawRegistry = rawJson![jsonRegistryName]!["entries"]!.AsObject(); Dictionary registry = new(); - foreach (KeyValuePair entry in rawRegistry.Properties) + foreach (var entry in rawRegistry) { - int entryId = int.Parse(entry.Value.Properties["protocol_id"].StringValue, NumberStyles.Any, CultureInfo.CurrentCulture); + int entryId = int.Parse(entry.Value!["protocol_id"].GetStringValue(), NumberStyles.Any, CultureInfo.CurrentCulture); //minecraft:item_name => ItemName string entryName = String.Concat( diff --git a/MinecraftClient/Protocol/Handlers/Forge/ForgeInfo.cs b/MinecraftClient/Protocol/Handlers/Forge/ForgeInfo.cs index e781aa18..bbe2110e 100755 --- a/MinecraftClient/Protocol/Handlers/Forge/ForgeInfo.cs +++ b/MinecraftClient/Protocol/Handlers/Forge/ForgeInfo.cs @@ -63,7 +63,7 @@ namespace MinecraftClient.Protocol.Handlers.Forge /// /// The modinfo JSON tag. /// Forge protocol version - internal ForgeInfo(Json.JSONData data, FMLVersion fmlVersion) + internal ForgeInfo(System.Text.Json.Nodes.JsonObject data, FMLVersion fmlVersion) { Mods = new List(); Version = fmlVersion; @@ -91,10 +91,10 @@ namespace MinecraftClient.Protocol.Handlers.Forge // }] // } - foreach (Json.JSONData mod in data.Properties["modList"].DataArray) + foreach (var mod in data["modList"]!.AsArray()) { - String modid = mod.Properties["modid"].StringValue; - String modversion = mod.Properties["version"].StringValue; + String modid = mod!["modid"]!.GetStringValue(); + String modversion = mod["version"]!.GetStringValue(); Mods.Add(new ForgeMod(modid, modversion)); } @@ -131,10 +131,10 @@ namespace MinecraftClient.Protocol.Handlers.Forge // "fmlNetworkVersion": 2 // } - foreach (Json.JSONData mod in data.Properties["mods"].DataArray) + foreach (var mod in data["mods"]!.AsArray()) { - String modid = mod.Properties["modId"].StringValue; - String modmarker = mod.Properties["modmarker"].StringValue; + String modid = mod!["modId"]!.GetStringValue(); + String modmarker = mod["modmarker"]!.GetStringValue(); Mods.Add(new ForgeMod(modid, modmarker)); } @@ -157,7 +157,7 @@ namespace MinecraftClient.Protocol.Handlers.Forge // - Here is the discussion: // see https://github.com/MinecraftForge/MinecraftForge/pull/8169 - string encodedData = data.Properties["d"].StringValue; + string encodedData = data["d"]!.GetStringValue(); Queue dataPackage = decodeOptimized(encodedData); DataTypes dataTypes = new DataTypes(Protocol18Handler.MC_1_18_1_Version); diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs index 4f036fb7..ab70ae6d 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -1031,10 +1031,10 @@ namespace MinecraftClient.Protocol.Handlers ? dataTypes.ReadNextString(packetData) : null; - var chatInfo = Json.ParseJson(chatName).Properties; - var senderDisplayName = chatInfo != null && chatInfo.Count > 0 + var chatInfo = Json.ParseJson(chatName)?.AsObject(); + var senderDisplayName = chatInfo is not null && chatInfo.Count > 0 ? (chatInfo.ContainsKey("insertion") ? chatInfo["insertion"] : chatInfo["text"]) - .StringValue + .GetStringValue() : ""; string? senderTeamName = null; var messageTypeEnum = @@ -1043,8 +1043,8 @@ namespace MinecraftClient.Protocol.Handlers if (targetName != null && (messageTypeEnum == ChatParser.MessageType.TEAM_MSG_COMMAND_INCOMING || messageTypeEnum == ChatParser.MessageType.TEAM_MSG_COMMAND_OUTGOING)) - senderTeamName = Json.ParseJson(targetName).Properties["with"].DataArray[0] - .Properties["text"].StringValue; + senderTeamName = Json.ParseJson(targetName)!["with"]![0]! + ["text"]!.GetStringValue(); if (string.IsNullOrWhiteSpace(senderDisplayName)) { @@ -3574,22 +3574,22 @@ namespace MinecraftClient.Protocol.Handlers if (string.IsNullOrEmpty(result) || !result.StartsWith("{") || !result.EndsWith("}")) return false; var jsonData = Json.ParseJson(result); - if (jsonData.Type != Json.JSONData.DataType.Object || !jsonData.Properties.ContainsKey("version")) + if (jsonData is not System.Text.Json.Nodes.JsonObject jsonObj || !jsonObj.ContainsKey("version")) return false; - var versionData = jsonData.Properties["version"]; + var versionData = jsonObj["version"]!.AsObject(); //Retrieve display name of the Minecraft version - if (versionData.Properties.TryGetValue("name", out var property)) - version = property.StringValue; + if (versionData["name"] is { } nameNode) + version = nameNode.GetStringValue(); //Retrieve protocol version number for handling this server - if (versionData.Properties.TryGetValue("protocol", out var dataProperty)) - protocolVersion = int.Parse(dataProperty.StringValue, + if (versionData["protocol"] is { } protocolNode) + protocolVersion = int.Parse(protocolNode.GetStringValue(), NumberStyles.Any, CultureInfo.CurrentCulture); // Check for forge on the server. - Protocol18Forge.ServerInfoCheckForge(jsonData, ref forgeInfo); + Protocol18Forge.ServerInfoCheckForge(jsonObj, ref forgeInfo); ConsoleIO.WriteLineFormatted("§8" + string.Format(Translations.mcc_server_protocol, version, protocolVersion + (forgeInfo != null ? Translations.mcc_with_forge : ""))); diff --git a/MinecraftClient/Protocol/Handlers/Protocol18Forge.cs b/MinecraftClient/Protocol/Handlers/Protocol18Forge.cs index cbda452b..dc1d6a12 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18Forge.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18Forge.cs @@ -484,7 +484,7 @@ namespace MinecraftClient.Protocol.Handlers /// JSON data returned by the server /// ForgeInfo to populate /// True if the server is running Forge - public static bool ServerInfoCheckForge(Json.JSONData jsonData, ref ForgeInfo? forgeInfo) + public static bool ServerInfoCheckForge(System.Text.Json.Nodes.JsonObject jsonData, ref ForgeInfo? forgeInfo) { return ServerInfoCheckForgeSub(jsonData, ref forgeInfo, FMLVersion.FML) // MC 1.12 and lower || ServerInfoCheckForgeSub(jsonData, ref forgeInfo, FMLVersion.FML2) // MC 1.13 to 1.17 @@ -530,7 +530,7 @@ namespace MinecraftClient.Protocol.Handlers /// ForgeInfo to populate /// Forge protocol version /// True if the server is running Forge - private static bool ServerInfoCheckForgeSub(Json.JSONData jsonData, ref ForgeInfo? forgeInfo, FMLVersion fmlVersion) + private static bool ServerInfoCheckForgeSub(System.Text.Json.Nodes.JsonObject jsonData, ref ForgeInfo? forgeInfo, FMLVersion fmlVersion) { string forgeDataTag; string versionField; @@ -557,10 +557,9 @@ namespace MinecraftClient.Protocol.Handlers throw new NotImplementedException("FMLVersion '" + fmlVersion + "' not implemented!"); } - if (jsonData.Properties.ContainsKey(forgeDataTag) && jsonData.Properties[forgeDataTag].Type == Json.JSONData.DataType.Object) + if (jsonData[forgeDataTag] is System.Text.Json.Nodes.JsonObject modData) { - Json.JSONData modData = jsonData.Properties[forgeDataTag]; - if (modData.Properties.ContainsKey(versionField) && modData.Properties[versionField].StringValue == versionString) + if (modData[versionField] is not null && modData[versionField]!.GetStringValue() == versionString) { forgeInfo = new ForgeInfo(modData, fmlVersion); if (forgeInfo.Mods.Any()) diff --git a/MinecraftClient/Protocol/Message/ChatParser.cs b/MinecraftClient/Protocol/Message/ChatParser.cs index f907eadd..c0c1e03d 100644 --- a/MinecraftClient/Protocol/Message/ChatParser.cs +++ b/MinecraftClient/Protocol/Message/ChatParser.cs @@ -436,74 +436,70 @@ namespace MinecraftClient.Protocol.Message /// Allow parent color code to affect child elements (set to "" for function init) /// Container for links from JSON serialized text /// returns the Minecraft-formatted string - private static string JSONData2String(Json.JSONData data, string colorcode, List? links) + private static string JSONData2String(System.Text.Json.Nodes.JsonNode? data, string colorcode, List? links) { string extra_result = ""; - switch (data.Type) + switch (data) { - case Json.JSONData.DataType.Object: - if (data.Properties.ContainsKey("color")) + case System.Text.Json.Nodes.JsonObject obj: + if (obj.ContainsKey("color")) { - colorcode = Color2tag(JSONData2String(data.Properties["color"], "", links)); + colorcode = Color2tag(JSONData2String(obj["color"], "", links)); } - if (data.Properties.ContainsKey("clickEvent") && links != null) + if (obj.ContainsKey("clickEvent") && links is not null) { - Json.JSONData clickEvent = data.Properties["clickEvent"]; - if (clickEvent.Properties.ContainsKey("action") - && clickEvent.Properties.ContainsKey("value") - && clickEvent.Properties["action"].StringValue == "open_url" - && !string.IsNullOrEmpty(clickEvent.Properties["value"].StringValue)) + var clickEvent = obj["clickEvent"]!.AsObject(); + if (clickEvent.ContainsKey("action") + && clickEvent.ContainsKey("value") + && clickEvent["action"]!.GetStringValue() == "open_url" + && !string.IsNullOrEmpty(clickEvent["value"]!.GetStringValue())) { - links.Add(clickEvent.Properties["value"].StringValue); + links.Add(clickEvent["value"]!.GetStringValue()); } } - if (data.Properties.ContainsKey("extra")) + if (obj.ContainsKey("extra")) { - Json.JSONData[] extras = data.Properties["extra"].DataArray.ToArray(); - foreach (Json.JSONData item in extras) + foreach (var item in obj["extra"]!.AsArray()) extra_result = extra_result + JSONData2String(item, colorcode, links) + "§r"; } - if (data.Properties.ContainsKey("text")) + if (obj.ContainsKey("text")) { - return colorcode + JSONData2String(data.Properties["text"], colorcode, links) + extra_result; + return colorcode + JSONData2String(obj["text"], colorcode, links) + extra_result; } - else if (data.Properties.ContainsKey("translate")) + else if (obj.ContainsKey("translate")) { List using_data = new(); - if (data.Properties.ContainsKey("using") && !data.Properties.ContainsKey("with")) - data.Properties["with"] = data.Properties["using"]; - if (data.Properties.ContainsKey("with")) + if (obj.ContainsKey("using") && !obj.ContainsKey("with")) + obj["with"] = obj["using"]!.DeepClone(); + if (obj.ContainsKey("with")) { - Json.JSONData[] array = data.Properties["with"].DataArray.ToArray(); - for (int i = 0; i < array.Length; i++) + foreach (var item in obj["with"]!.AsArray()) { - using_data.Add(JSONData2String(array[i], colorcode, links)); + using_data.Add(JSONData2String(item, colorcode, links)); } } return colorcode + - TranslateString(JSONData2String(data.Properties["translate"], "", links), using_data) + + TranslateString(JSONData2String(obj["translate"], "", links), using_data) + extra_result; } else return extra_result; - case Json.JSONData.DataType.Array: + case System.Text.Json.Nodes.JsonArray arr: string result = ""; - foreach (Json.JSONData item in data.DataArray) + foreach (var item in arr) { result += JSONData2String(item, colorcode, links); } return result; - case Json.JSONData.DataType.String: - return colorcode + data.StringValue; + default: + return colorcode + data.GetStringValue(); } - - return ""; } private static string NbtToString(Dictionary nbt) diff --git a/MinecraftClient/Protocol/MicrosoftAuthentication.cs b/MinecraftClient/Protocol/MicrosoftAuthentication.cs index f3f81298..cdd9434c 100644 --- a/MinecraftClient/Protocol/MicrosoftAuthentication.cs +++ b/MinecraftClient/Protocol/MicrosoftAuthentication.cs @@ -68,20 +68,20 @@ namespace MinecraftClient.Protocol var jsonData = Json.ParseJson(response.Body); // Error handling - if (jsonData.Properties.ContainsKey("error")) + if (jsonData?["error"] is not null) { - throw new Exception(jsonData.Properties["error_description"].StringValue); + throw new Exception(jsonData["error_description"].GetStringValue()); } else { - string accessToken = jsonData.Properties["access_token"].StringValue; - string refreshToken = jsonData.Properties["refresh_token"].StringValue; - int expiresIn = int.Parse(jsonData.Properties["expires_in"].StringValue, NumberStyles.Any, CultureInfo.CurrentCulture); + string accessToken = jsonData!["access_token"]!.GetStringValue(); + string refreshToken = jsonData["refresh_token"]!.GetStringValue(); + int expiresIn = int.Parse(jsonData["expires_in"].GetStringValue(), NumberStyles.Any, CultureInfo.CurrentCulture); // Extract email from JWT - string payload = JwtPayloadDecode.GetPayload(jsonData.Properties["id_token"].StringValue); + string payload = JwtPayloadDecode.GetPayload(jsonData["id_token"]!.GetStringValue()); var jsonPayload = Json.ParseJson(payload); - string email = jsonPayload.Properties["email"].StringValue; + string email = jsonPayload!["email"]!.GetStringValue(); return new LoginResponse() { Email = email, @@ -299,9 +299,9 @@ namespace MinecraftClient.Protocol string jsonString = response.Body; //Console.WriteLine(jsonString); - Json.JSONData json = Json.ParseJson(jsonString); - string token = json.Properties["Token"].StringValue; - string userHash = json.Properties["DisplayClaims"].Properties["xui"].DataArray[0].Properties["uhs"].StringValue; + var json = Json.ParseJson(jsonString); + string token = json!["Token"]!.GetStringValue(); + string userHash = json["DisplayClaims"]!["xui"]![0]!["uhs"]!.GetStringValue(); return new XblAuthenticateResponse() { Token = token, @@ -347,9 +347,9 @@ namespace MinecraftClient.Protocol if (response.StatusCode == 200) { string jsonString = response.Body; - Json.JSONData json = Json.ParseJson(jsonString); - string token = json.Properties["Token"].StringValue; - string userHash = json.Properties["DisplayClaims"].Properties["xui"].DataArray[0].Properties["uhs"].StringValue; + var json = Json.ParseJson(jsonString); + string token = json!["Token"]!.GetStringValue(); + string userHash = json["DisplayClaims"]!["xui"]![0]!["uhs"]!.GetStringValue(); return new XSTSAuthenticateResponse() { Token = token, @@ -360,16 +360,16 @@ namespace MinecraftClient.Protocol { if (response.StatusCode == 401) { - Json.JSONData json = Json.ParseJson(response.Body); - if (json.Properties["XErr"].StringValue == "2148916233") + var json = Json.ParseJson(response.Body); + if (json!["XErr"]!.GetStringValue() == "2148916233") { throw new Exception("The account doesn't have an Xbox account"); } - else if (json.Properties["XErr"].StringValue == "2148916238") + else if (json["XErr"]!.GetStringValue() == "2148916238") { throw new Exception("The account is a child (under 18) and cannot proceed unless the account is added to a Family by an adult"); } - else throw new Exception("Unknown XSTS error code: " + json.Properties["XErr"].StringValue); + else throw new Exception("Unknown XSTS error code: " + json["XErr"]!.GetStringValue()); } else { @@ -426,9 +426,9 @@ namespace MinecraftClient.Protocol } string jsonString = response.Body; - Json.JSONData json = Json.ParseJson(jsonString); + var json = Json.ParseJson(jsonString); - return json.Properties["access_token"].StringValue; + return json!["access_token"]!.GetStringValue(); } /// @@ -448,8 +448,8 @@ namespace MinecraftClient.Protocol } string jsonString = response.Body; - Json.JSONData json = Json.ParseJson(jsonString); - return json.Properties["items"].DataArray.Count > 0; + var json = Json.ParseJson(jsonString); + return json!["items"]!.AsArray().Count > 0; } public static UserProfile GetUserProfile(string accessToken) @@ -464,11 +464,11 @@ namespace MinecraftClient.Protocol } string jsonString = response.Body; - Json.JSONData json = Json.ParseJson(jsonString); + var json = Json.ParseJson(jsonString); return new UserProfile() { - UUID = json.Properties["id"].StringValue, - UserName = json.Properties["name"].StringValue + UUID = json!["id"]!.GetStringValue(), + UserName = json["name"]!.GetStringValue() }; } diff --git a/MinecraftClient/Protocol/MojangAPI.cs b/MinecraftClient/Protocol/MojangAPI.cs index 8ee28b31..ab1c32bb 100644 --- a/MinecraftClient/Protocol/MojangAPI.cs +++ b/MinecraftClient/Protocol/MojangAPI.cs @@ -121,7 +121,7 @@ namespace MinecraftClient.Protocol { Task fetchTask = httpClient.GetStringAsync("https://api.mojang.com/users/profiles/minecraft/" + name); fetchTask.Wait(); - string result = Json.ParseJson(fetchTask.Result).Properties["id"].StringValue; + string result = Json.ParseJson(fetchTask.Result)!["id"]!.GetStringValue(); fetchTask.Dispose(); return result; } @@ -140,11 +140,11 @@ namespace MinecraftClient.Protocol { Task fetchTask = httpClient.GetStringAsync("https://api.mojang.com/user/profiles/" + uuid + "/names"); fetchTask.Wait(); - var nameChanges = Json.ParseJson(fetchTask.Result).DataArray; + var nameChanges = Json.ParseJson(fetchTask.Result)!.AsArray(); fetchTask.Dispose(); // Names are sorted from past to most recent. We need to get the last name in the list - return nameChanges[^1].Properties["name"].StringValue; + return nameChanges[^1]!["name"]!.GetStringValue(); } catch (Exception) { return string.Empty; } } @@ -157,40 +157,32 @@ namespace MinecraftClient.Protocol public static Dictionary UuidToNameHistory(string uuid) { Dictionary tempDict = new(); - List jsonDataList; + System.Text.Json.Nodes.JsonArray jsonDataList; // Perform web request try { Task fetchTask = httpClient.GetStringAsync("https://api.mojang.com/user/profiles/" + uuid + "/names"); fetchTask.Wait(); - jsonDataList = Json.ParseJson(fetchTask.Result).DataArray; + jsonDataList = Json.ParseJson(fetchTask.Result)!.AsArray(); fetchTask.Dispose(); } catch (Exception) { return tempDict; } - foreach (Json.JSONData jsonData in jsonDataList) + foreach (var jsonData in jsonDataList) { - if (jsonData.Properties.Count > 1) + var obj = jsonData!.AsObject(); + if (obj.Count > 1) { - // Time is saved as long in the Unix format. - // Convert it to normal time, before adding it to the dictionary. - // - // !! FromUnixTimeMilliseconds does not exist in the current version. !! - // DateTimeOffset creationDate = DateTimeOffset.FromUnixTimeMilliseconds(Convert.ToInt64(jsonData.Properties["changedToAt"].StringValue)); - // + DateTimeOffset creationDate = UnixTimeStampToDateTime(Convert.ToDouble(jsonData["changedToAt"].GetStringValue())); - // Workaround for converting Unix time to normal time. - DateTimeOffset creationDate = UnixTimeStampToDateTime(Convert.ToDouble(jsonData.Properties["changedToAt"].StringValue)); - - // Add Keyvaluepair to dict. - tempDict.Add(jsonData.Properties["name"].StringValue, creationDate.DateTime); + tempDict.Add(jsonData["name"]!.GetStringValue(), creationDate.DateTime); } // The first entry does not contain a change date. - else if (jsonData.Properties.Count > 0) + else if (obj.Count > 0) { // Add an undefined time to it. - tempDict.Add(jsonData.Properties["name"].StringValue, new DateTime()); + tempDict.Add(jsonData["name"]!.GetStringValue(), new DateTime()); } } @@ -203,14 +195,14 @@ namespace MinecraftClient.Protocol /// Dictionary of the Mojang services public static MojangServiceStatus GetMojangServiceStatus() { - List jsonDataList; + System.Text.Json.Nodes.JsonArray jsonDataList; // Perform web request try { Task fetchTask = httpClient.GetStringAsync("https://status.mojang.com/check"); fetchTask.Wait(); - jsonDataList = Json.ParseJson(fetchTask.Result).DataArray; + jsonDataList = Json.ParseJson(fetchTask.Result)!.AsArray(); fetchTask.Dispose(); } catch (Exception) @@ -219,14 +211,14 @@ namespace MinecraftClient.Protocol } // Convert string to enum values and store them inside a MojangeServiceStatus object. - return new MojangServiceStatus(minecraftNet: StringToServiceStatus(jsonDataList[0].Properties["minecraft.net"].StringValue), - sessionMinecraftNet: StringToServiceStatus(jsonDataList[1].Properties["session.minecraft.net"].StringValue), - accountMojangCom: StringToServiceStatus(jsonDataList[2].Properties["account.mojang.com"].StringValue), - authserverMojangCom: StringToServiceStatus(jsonDataList[3].Properties["authserver.mojang.com"].StringValue), - sessionserverMojangCom: StringToServiceStatus(jsonDataList[4].Properties["sessionserver.mojang.com"].StringValue), - apiMojangCom: StringToServiceStatus(jsonDataList[5].Properties["api.mojang.com"].StringValue), - texturesMinecraftNet: StringToServiceStatus(jsonDataList[6].Properties["textures.minecraft.net"].StringValue), - mojangCom: StringToServiceStatus(jsonDataList[7].Properties["mojang.com"].StringValue) + return new MojangServiceStatus(minecraftNet: StringToServiceStatus(jsonDataList[0]!["minecraft.net"]!.GetStringValue()), + sessionMinecraftNet: StringToServiceStatus(jsonDataList[1]!["session.minecraft.net"]!.GetStringValue()), + accountMojangCom: StringToServiceStatus(jsonDataList[2]!["account.mojang.com"]!.GetStringValue()), + authserverMojangCom: StringToServiceStatus(jsonDataList[3]!["authserver.mojang.com"]!.GetStringValue()), + sessionserverMojangCom: StringToServiceStatus(jsonDataList[4]!["sessionserver.mojang.com"]!.GetStringValue()), + apiMojangCom: StringToServiceStatus(jsonDataList[5]!["api.mojang.com"]!.GetStringValue()), + texturesMinecraftNet: StringToServiceStatus(jsonDataList[6]!["textures.minecraft.net"]!.GetStringValue()), + mojangCom: StringToServiceStatus(jsonDataList[7]!["mojang.com"]!.GetStringValue()) ); } @@ -237,9 +229,9 @@ namespace MinecraftClient.Protocol /// Dictionary with a link to the skin and cape of a player. public static SkinInfo GetSkinInfo(string uuid) { - Dictionary textureDict; + System.Text.Json.Nodes.JsonObject textureObj; string base64SkinInfo; - Json.JSONData decodedJsonSkinInfo; + System.Text.Json.Nodes.JsonNode? decodedJsonSkinInfo; // Perform web request try @@ -247,7 +239,7 @@ namespace MinecraftClient.Protocol Task fetchTask = httpClient.GetStringAsync("https://sessionserver.mojang.com/session/minecraft/profile/" + uuid); fetchTask.Wait(); // Obtain the Base64 encoded skin information from the API. Discard the rest, since it can be obtained easier through other requests. - base64SkinInfo = Json.ParseJson(fetchTask.Result).Properties["properties"].DataArray[0].Properties["value"].StringValue; + base64SkinInfo = Json.ParseJson(fetchTask.Result)!["properties"]![0]!["value"]!.GetStringValue(); fetchTask.Dispose(); } catch (Exception) { return new SkinInfo(); } @@ -257,23 +249,18 @@ namespace MinecraftClient.Protocol // Assert temporary variable for readablity. // Contains skin and cape information. - textureDict = decodedJsonSkinInfo.Properties["textures"].Properties; + textureObj = decodedJsonSkinInfo!["textures"]!.AsObject(); // Can apparently be missing, if no custom skin is set. - // Probably for completely new accounts. - // (Still exists after changing back to Steve or Alex skin.) - if (textureDict.ContainsKey("SKIN")) + if (textureObj.ContainsKey("SKIN")) { - return new SkinInfo(skinUrl: textureDict["SKIN"].Properties.ContainsKey("url") ? textureDict["SKIN"].Properties["url"].StringValue : string.Empty, - capeUrl: textureDict.ContainsKey("CAPE") ? textureDict["CAPE"].Properties["url"].StringValue : string.Empty, - skinModel: textureDict["SKIN"].Properties.ContainsKey("metadata") ? "Alex" : "Steve"); + return new SkinInfo(skinUrl: textureObj["SKIN"]!["url"] is not null ? textureObj["SKIN"]!["url"]!.GetStringValue() : string.Empty, + capeUrl: textureObj.ContainsKey("CAPE") ? textureObj["CAPE"]!["url"]!.GetStringValue() : string.Empty, + skinModel: textureObj["SKIN"]!["metadata"] is not null ? "Alex" : "Steve"); } - // Tested it on several players, this case never occured. else { - // This player has assumingly never changed their skin. - // Probably a completely new account. - return new SkinInfo(capeUrl: textureDict.ContainsKey("CAPE") ? textureDict["CAPE"].Properties["url"].StringValue : string.Empty, + return new SkinInfo(capeUrl: textureObj.ContainsKey("CAPE") ? textureObj["CAPE"]!["url"]!.GetStringValue() : string.Empty, skinModel: DefaultModelAlex(uuid) ? "Alex" : "Steve"); } } diff --git a/MinecraftClient/Protocol/ProfileKey/KeyUtils.cs b/MinecraftClient/Protocol/ProfileKey/KeyUtils.cs index 708e99d9..ba5e5610 100644 --- a/MinecraftClient/Protocol/ProfileKey/KeyUtils.cs +++ b/MinecraftClient/Protocol/ProfileKey/KeyUtils.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Security.Cryptography; using System.Text; +using System.Text.Json.Nodes; using MinecraftClient.Protocol.Handlers; using MinecraftClient.Protocol.Message; using static MinecraftClient.Protocol.Message.LastSeenMessageList; @@ -37,17 +38,17 @@ namespace MinecraftClient.Protocol.ProfileKey // see https://github.com/yushijinhun/authlib-injector/blob/da910956eaa30d2f6c2c457222d188aeb53b0d1f/src/main/java/moe/yushi/authlibinjector/httpd/ProfileKeyFilter.java#L49 // POST to "https://api.minecraftservices.com/player/certificates" with authlib-injector will get a dummy response - Json.JSONData json = isYggdrasil ? MakeDummyResponse() : Json.ParseJson(response!.Body); + var json = isYggdrasil ? MakeDummyResponse() : Json.ParseJson(response!.Body); // Error here - PublicKey publicKey = new(pemKey: json.Properties["keyPair"].Properties["publicKey"].StringValue, - sig: json.Properties["publicKeySignature"].StringValue, - sigV2: json.Properties["publicKeySignatureV2"].StringValue); + PublicKey publicKey = new(pemKey: json!["keyPair"]!["publicKey"]!.GetStringValue(), + sig: json["publicKeySignature"]!.GetStringValue(), + sigV2: json["publicKeySignatureV2"]!.GetStringValue()); - PrivateKey privateKey = new(pemKey: json.Properties["keyPair"].Properties["privateKey"].StringValue); + PrivateKey privateKey = new(pemKey: json["keyPair"]!["privateKey"]!.GetStringValue()); return new PlayerKeyPair(publicKey, privateKey, - expiresAt: json.Properties["expiresAt"].StringValue, - refreshedAfter: json.Properties["refreshedAfter"].StringValue); + expiresAt: json["expiresAt"]!.GetStringValue(), + refreshedAfter: json["refreshedAfter"]!.GetStringValue()); } catch (Exception e) { @@ -191,51 +192,10 @@ namespace MinecraftClient.Protocol.ProfileKey return data.ToArray(); } - // https://github.com/mono/mono/blob/master/mcs/class/System.Json/System.Json/JsonValue.cs - public static string EscapeString(string src) - { - StringBuilder sb = new(); + // Delegate to the shared Json.EscapeString backed by System.Text.Json + public static string EscapeString(string src) => Json.EscapeString(src); - int start = 0; - for (int i = 0; i < src.Length; i++) - { - char c = src[i]; - bool needEscape = c < 32 || c == '"' || c == '\\'; - // Broken lead surrogate - needEscape = needEscape || c >= '\uD800' && c <= '\uDBFF' && - (i == src.Length - 1 || src[i + 1] < '\uDC00' || src[i + 1] > '\uDFFF'); - // Broken tail surrogate - needEscape = needEscape || c >= '\uDC00' && c <= '\uDFFF' && - (i == 0 || src[i - 1] < '\uD800' || src[i - 1] > '\uDBFF'); - // To produce valid JavaScript - needEscape = needEscape || c == '\u2028' || c == '\u2029'; - - if (needEscape) - { - sb.Append(src, start, i - start); - switch (src[i]) - { - case '\b': sb.Append("\\b"); break; - case '\f': sb.Append("\\f"); break; - case '\n': sb.Append("\\n"); break; - case '\r': sb.Append("\\r"); break; - case '\t': sb.Append("\\t"); break; - case '\"': sb.Append("\\\""); break; - case '\\': sb.Append("\\\\"); break; - default: - sb.Append("\\u"); - sb.Append(((int)src[i]).ToString("x04")); - break; - } - start = i + 1; - } - - } - sb.Append(src, start, src.Length - start); - return sb.ToString(); - } - - public static Json.JSONData MakeDummyResponse() + public static JsonNode MakeDummyResponse() { RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(2048); var mimePublicKey = Convert.ToBase64String(rsa.ExportSubjectPublicKeyInfo()); @@ -245,19 +205,20 @@ namespace MinecraftClient.Protocol.ProfileKey DateTime now = DateTime.UtcNow; DateTime expiresAt = now.AddHours(48); DateTime refreshedAfter = now.AddHours(36); - Json.JSONData response = new(Json.JSONData.DataType.Object); - Json.JSONData keyPairObj = new(Json.JSONData.DataType.Object); - keyPairObj.Properties["privateKey"] = new(Json.JSONData.DataType.String){ StringValue = privateKeyPEM }; - keyPairObj.Properties["publicKey"] = new(Json.JSONData.DataType.String){ StringValue = publicKeyPEM }; - - response.Properties["keyPair"] = keyPairObj; - response.Properties["publicKeySignature"] = new(Json.JSONData.DataType.String){ StringValue = "AA==" }; - response.Properties["publicKeySignatureV2"] = new(Json.JSONData.DataType.String){ StringValue = "AA==" }; string format = "yyyy-MM-ddTHH:mm:ss.ffffffZ"; - response.Properties["expiresAt"] = new(Json.JSONData.DataType.String){ StringValue = expiresAt.ToString(format) }; - response.Properties["refreshedAfter"] = new(Json.JSONData.DataType.String){ StringValue = refreshedAfter.ToString(format) }; - return response; + return new JsonObject + { + ["keyPair"] = new JsonObject + { + ["privateKey"] = privateKeyPEM, + ["publicKey"] = publicKeyPEM + }, + ["publicKeySignature"] = "AA==", + ["publicKeySignatureV2"] = "AA==", + ["expiresAt"] = expiresAt.ToString(format), + ["refreshedAfter"] = refreshedAfter.ToString(format) + }; } } } diff --git a/MinecraftClient/Protocol/ProtocolHandler.cs b/MinecraftClient/Protocol/ProtocolHandler.cs index c2fc7cef..32edcf6b 100644 --- a/MinecraftClient/Protocol/ProtocolHandler.cs +++ b/MinecraftClient/Protocol/ProtocolHandler.cs @@ -581,16 +581,15 @@ namespace MinecraftClient.Protocol } else { - Json.JSONData loginResponse = Json.ParseJson(result); - if (loginResponse.Properties.ContainsKey("accessToken") - && loginResponse.Properties.ContainsKey("selectedProfile") - && loginResponse.Properties["selectedProfile"].Properties.ContainsKey("id") - && loginResponse.Properties["selectedProfile"].Properties.ContainsKey("name")) + var loginResponse = Json.ParseJson(result); + if (loginResponse?["accessToken"] is not null + && loginResponse["selectedProfile"]?["id"] is not null + && loginResponse["selectedProfile"]?["name"] is not null) { - session.ID = loginResponse.Properties["accessToken"].StringValue; - session.PlayerID = loginResponse.Properties["selectedProfile"].Properties["id"].StringValue; - session.PlayerName = loginResponse.Properties["selectedProfile"].Properties["name"] - .StringValue; + session.ID = loginResponse["accessToken"]!.GetStringValue(); + session.PlayerID = loginResponse["selectedProfile"]!["id"]!.GetStringValue(); + session.PlayerName = loginResponse["selectedProfile"]!["name"]! + .GetStringValue(); return LoginResult.Success; } else return LoginResult.InvalidResponse; @@ -667,27 +666,25 @@ namespace MinecraftClient.Protocol } else { - Json.JSONData loginResponse = Json.ParseJson(result); - if (loginResponse.Properties.ContainsKey("accessToken")) + var loginResponse = Json.ParseJson(result); + if (loginResponse?["accessToken"] is not null) { - session.ID = loginResponse.Properties["accessToken"].StringValue; - if (loginResponse.Properties.ContainsKey("selectedProfile") - && loginResponse.Properties["selectedProfile"].Properties.ContainsKey("id") - && loginResponse.Properties["selectedProfile"].Properties.ContainsKey("name")) + session.ID = loginResponse["accessToken"]!.GetStringValue(); + if (loginResponse["selectedProfile"]?["id"] is not null + && loginResponse["selectedProfile"]?["name"] is not null) { - session.PlayerID = loginResponse.Properties["selectedProfile"].Properties["id"] - .StringValue; - session.PlayerName = loginResponse.Properties["selectedProfile"].Properties["name"] - .StringValue; + session.PlayerID = loginResponse["selectedProfile"]!["id"]! + .GetStringValue(); + session.PlayerName = loginResponse["selectedProfile"]!["name"]! + .GetStringValue(); return LoginResult.Success; } else { string availableProfiles = ""; - foreach (Json.JSONData profile in loginResponse.Properties["availableProfiles"] - .DataArray) + foreach (var profile in loginResponse["availableProfiles"]!.AsArray()) { - availableProfiles += " " + profile.Properties["name"].StringValue; + availableProfiles += " " + profile!["name"]!.GetStringValue(); } ConsoleIO.WriteLine(Translations.mcc_avaliable_profiles + availableProfiles); @@ -703,19 +700,18 @@ namespace MinecraftClient.Protocol ConsoleIO.WriteLine(Translations.mcc_selected_profile + " " + selectedProfileName); - Json.JSONData? selectedProfile = null; - foreach (Json.JSONData profile in loginResponse.Properties["availableProfiles"] - .DataArray) + System.Text.Json.Nodes.JsonNode? selectedProfile = null; + foreach (var profile in loginResponse["availableProfiles"]!.AsArray()) { - selectedProfile = profile.Properties["name"].StringValue == selectedProfileName + selectedProfile = profile!["name"]!.GetStringValue() == selectedProfileName ? profile : selectedProfile; } - if (selectedProfile != null) + if (selectedProfile is not null) { - session.PlayerID = selectedProfile.Properties["id"].StringValue; - session.PlayerName = selectedProfile.Properties["name"].StringValue; + session.PlayerID = selectedProfile["id"]!.GetStringValue(); + session.PlayerName = selectedProfile["name"]!.GetStringValue(); SessionToken currentsession = session; return GetNewYggdrasilToken(currentsession, out session); } @@ -889,7 +885,7 @@ namespace MinecraftClient.Protocol { var payload = JwtPayloadDecode.GetPayload(session.ID); var json = Json.ParseJson(payload); - var expTimestamp = long.Parse(json.Properties["exp"].StringValue, NumberStyles.Any, + var expTimestamp = long.Parse(json!["exp"]!.GetStringValue(), NumberStyles.Any, CultureInfo.CurrentCulture); var now = DateTime.Now; var tokenExp = UnixTimeStampToDateTime(expTimestamp); @@ -935,16 +931,15 @@ namespace MinecraftClient.Protocol } else { - Json.JSONData loginResponse = Json.ParseJson(result); - if (loginResponse.Properties.ContainsKey("accessToken") - && loginResponse.Properties.ContainsKey("selectedProfile") - && loginResponse.Properties["selectedProfile"].Properties.ContainsKey("id") - && loginResponse.Properties["selectedProfile"].Properties.ContainsKey("name")) + var loginResponse = Json.ParseJson(result); + if (loginResponse?["accessToken"] is not null + && loginResponse["selectedProfile"]?["id"] is not null + && loginResponse["selectedProfile"]?["name"] is not null) { - session.ID = loginResponse.Properties["accessToken"].StringValue; - session.PlayerID = loginResponse.Properties["selectedProfile"].Properties["id"].StringValue; - session.PlayerName = loginResponse.Properties["selectedProfile"].Properties["name"] - .StringValue; + session.ID = loginResponse["accessToken"]!.GetStringValue(); + session.PlayerID = loginResponse["selectedProfile"]!["id"]!.GetStringValue(); + session.PlayerName = loginResponse["selectedProfile"]!["name"]! + .GetStringValue(); return LoginResult.Success; } else return LoginResult.InvalidResponse; @@ -986,16 +981,15 @@ namespace MinecraftClient.Protocol } else { - Json.JSONData loginResponse = Json.ParseJson(result); - if (loginResponse.Properties.ContainsKey("accessToken") - && loginResponse.Properties.ContainsKey("selectedProfile") - && loginResponse.Properties["selectedProfile"].Properties.ContainsKey("id") - && loginResponse.Properties["selectedProfile"].Properties.ContainsKey("name")) + var loginResponse = Json.ParseJson(result); + if (loginResponse?["accessToken"] is not null + && loginResponse["selectedProfile"]?["id"] is not null + && loginResponse["selectedProfile"]?["name"] is not null) { - session.ID = loginResponse.Properties["accessToken"].StringValue; - session.PlayerID = loginResponse.Properties["selectedProfile"].Properties["id"].StringValue; - session.PlayerName = loginResponse.Properties["selectedProfile"].Properties["name"] - .StringValue; + session.ID = loginResponse["accessToken"]!.GetStringValue(); + session.PlayerID = loginResponse["selectedProfile"]!["id"]!.GetStringValue(); + session.PlayerName = loginResponse["selectedProfile"]!["name"]! + .GetStringValue(); return LoginResult.Success; } else return LoginResult.InvalidResponse; @@ -1065,28 +1059,27 @@ namespace MinecraftClient.Protocol string cookies = String.Format("sid=token:{0}:{1};user={2};version={3}", accesstoken, uuid, username, Program.MCHighestVersion); DoHTTPSGet("pc.realms.minecraft.net", 443, "/worlds", cookies, ref result); - Json.JSONData realmsWorlds = Json.ParseJson(result); - if (realmsWorlds.Properties.ContainsKey("servers") - && realmsWorlds.Properties["servers"].Type == Json.JSONData.DataType.Array - && realmsWorlds.Properties["servers"].DataArray.Count > 0) + var realmsWorlds = Json.ParseJson(result); + if (realmsWorlds?["servers"] is System.Text.Json.Nodes.JsonArray serversArray + && serversArray.Count > 0) { List availableWorlds = new(); // Store string to print int index = 0; - foreach (Json.JSONData realmsServer in realmsWorlds.Properties["servers"].DataArray) + foreach (var realmsServer in serversArray) { - if (realmsServer.Properties.ContainsKey("name") - && realmsServer.Properties.ContainsKey("owner") - && realmsServer.Properties.ContainsKey("id") - && realmsServer.Properties.ContainsKey("expired")) + if (realmsServer?["name"] is not null + && realmsServer["owner"] is not null + && realmsServer["id"] is not null + && realmsServer["expired"] is not null) { - if (realmsServer.Properties["expired"].StringValue == "false") + if (realmsServer["expired"].GetStringValue() == "false") { availableWorlds.Add(String.Format("[{0}] {2} ({3}) - {1}", index++, - realmsServer.Properties["id"].StringValue, - realmsServer.Properties["name"].StringValue, - realmsServer.Properties["owner"].StringValue)); - realmsWorldsResult.Add(realmsServer.Properties["id"].StringValue); + realmsServer["id"]!.GetStringValue(), + realmsServer["name"]!.GetStringValue(), + realmsServer["owner"]!.GetStringValue())); + realmsWorldsResult.Add(realmsServer["id"]!.GetStringValue()); } } } @@ -1132,9 +1125,9 @@ namespace MinecraftClient.Protocol cookies, ref result); if (statusCode == 200) { - Json.JSONData serverAddress = Json.ParseJson(result); - if (serverAddress.Properties.ContainsKey("address")) - return serverAddress.Properties["address"].StringValue; + var serverAddress = Json.ParseJson(result); + if (serverAddress?["address"] is not null) + return serverAddress["address"]!.GetStringValue(); else { ConsoleIO.WriteLine(Translations.error_realms_ip_error); diff --git a/MinecraftClient/Protocol/ProxiedWebRequest.cs b/MinecraftClient/Protocol/ProxiedWebRequest.cs index 06e69a9d..16724b71 100644 --- a/MinecraftClient/Protocol/ProxiedWebRequest.cs +++ b/MinecraftClient/Protocol/ProxiedWebRequest.cs @@ -1,413 +1,206 @@ -using System; -using System.Collections.Generic; +using System; using System.Collections.Specialized; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Net.Security; -using System.Net.Sockets; -using System.Security.Authentication; +using System.Net; +using System.Net.Http; using System.Text; -using System.Threading; using MinecraftClient.Proxy; namespace MinecraftClient.Protocol { /// - /// Create a new http request and optionally with proxy according to setting + /// HTTP client with automatic proxy support based on application settings. + /// Backed by System.Net.Http.HttpClient with SocketsHttpHandler. /// public class ProxiedWebRequest { - public interface ITcpFactory + private const int DefaultConnectTimeoutSeconds = 30; + + private readonly Uri _uri; + + public NameValueCollection Headers { get; } = new(); + + public string UserAgent { - TcpClient CreateTcpClient(string host, int port); - }; + get => Headers.Get("User-Agent") ?? string.Empty; + set => Headers.Set("User-Agent", value); + } - private readonly string httpVersion = "HTTP/1.1"; + public string Accept + { + get => Headers.Get("Accept") ?? string.Empty; + set => Headers.Set("Accept", value); + } - private ITcpFactory? tcpFactory; - private bool isProxied = false; // Send absolute Url in request if true + public string Cookie + { + set => Headers.Set("Cookie", value); + } - private readonly Uri uri; - private string Host { get { return uri.Host; } } - private int Port { get { return uri.Port; } } - private string Path { get { return uri.PathAndQuery; } } - private string AbsoluteUrl { get { return uri.AbsoluteUri; } } - private bool IsSecure { get { return uri.Scheme == "https"; } } - - public NameValueCollection Headers = new(); - - public string UserAgent { get { return Headers.Get("User-Agent") ?? String.Empty; } set { Headers.Set("User-Agent", value); } } - public string Accept { get { return Headers.Get("Accept") ?? String.Empty; } set { Headers.Set("Accept", value); } } - public string Cookie { set { Headers.Set("Cookie", value); } } + public bool Debug => Settings.Config.Logging.DebugMessages; /// - /// Set to true to tell the http client proxy is enabled - /// - public bool IsProxy { get { return isProxied; } set { isProxied = value; } } - public bool Debug { get { return Settings.Config.Logging.DebugMessages; } } - - /// - /// Create a new http request + /// Create a new HTTP request /// /// Target URL public ProxiedWebRequest(string url) { - uri = new Uri(url); + _uri = new Uri(url); SetupBasicHeaders(); } /// - /// Create a new http request with cookies + /// Create a new HTTP request with cookies /// /// Target URL - /// Cookies to use + /// Cookies to include in the request public ProxiedWebRequest(string url, NameValueCollection cookies) { - uri = new Uri(url); + _uri = new Uri(url); Headers.Add("Cookie", GetCookieString(cookies)); SetupBasicHeaders(); } - /// - /// Create a new http request with custom tcp client - /// - /// Tcp factory to be used - /// Target URL - public ProxiedWebRequest(ITcpFactory tcpFactory, string url) : this(url) - { - this.tcpFactory = tcpFactory; - } - - /// - /// Create a new http request with custom tcp client and cookies - /// - /// Tcp factory to be used - /// Target URL - /// Cookies to use - public ProxiedWebRequest(ITcpFactory tcpFactory, string url, NameValueCollection cookies) : this(url, cookies) - { - this.tcpFactory = tcpFactory; - } - - /// - /// Setup some basic headers - /// private void SetupBasicHeaders() { - Headers.Add("Host", Host); + Headers.Add("Host", _uri.Host); Headers.Add("User-Agent", "MCC/1.0"); Headers.Add("Accept", "*/*"); - Headers.Add("Connection", "close"); } /// - /// Perform GET request and get the response. Proxy is handled automatically + /// Perform GET request. Proxy is handled automatically. /// - /// - public Response Get() - { - return Send("GET"); - } + public Response Get() => Send(HttpMethod.Get); /// - /// Perform POST request and get the response. Proxy is handled automatically + /// Perform POST request. Proxy is handled automatically. /// /// The content type of request body /// Request body - /// - public Response Post(string contentType, string body) - { - Headers.Add("Content-Type", contentType); - // Calculate length - Headers.Add("Content-Length", Encoding.UTF8.GetBytes(body).Length.ToString()); - return Send("POST", body); - } + public Response Post(string contentType, string body) => Send(HttpMethod.Post, contentType, body); /// - /// Send a http request to the server. Proxy is handled automatically + /// Send an HTTP request. Proxy is configured automatically from Settings. /// - /// Method in string representation - /// Optional request body - /// - private Response Send(string method, string body = "") + private Response Send(HttpMethod method, string? contentType = null, string? body = null) { - List requestMessage = new() + using var handler = CreateHandler(); + using var client = new HttpClient(handler); + + using var request = new HttpRequestMessage(method, _uri); + + // Apply custom headers (skip content-level headers) + foreach (string key in Headers) { - string.Format("{0} {1} {2}", method.ToUpper(), isProxied ? AbsoluteUrl : Path, httpVersion) // Request line - }; - foreach (string key in Headers) // Headers - { - var value = Headers[key]; - requestMessage.Add(string.Format("{0}: {1}", key, value)); + if (key.Equals("Content-Type", StringComparison.OrdinalIgnoreCase) || + key.Equals("Content-Length", StringComparison.OrdinalIgnoreCase) || + key.Equals("Host", StringComparison.OrdinalIgnoreCase)) + continue; + + request.Headers.TryAddWithoutValidation(key, Headers[key]); } - requestMessage.Add(""); // - if (body != "") + + if (body is not null) { - requestMessage.Add(body); + request.Content = new StringContent(body, Encoding.UTF8, contentType ?? "text/plain"); } - else requestMessage.Add(""); // + if (Debug) { - foreach (string l in requestMessage) - { - ConsoleIO.WriteLine("< " + l); - } + ConsoleIO.WriteLine($"< {method} {_uri}"); + foreach (string key in Headers) + ConsoleIO.WriteLine($"< {key}: {Headers[key]}"); } - Response response = Response.Empty(); - - // FIXME: Use TcpFactory interface to avoid direct usage of the ProxyHandler class - // TcpClient client = tcpFactory.CreateTcpClient(Host, Port); - TcpClient client = ProxyHandler.NewTcpClient(Host, Port, true); - Stream stream; - if (IsSecure) - { - stream = new SslStream(client.GetStream()); - ((SslStream)stream).AuthenticateAsClient(Host, null, SslProtocols.Tls12, true); // Enable TLS 1.2. Hotfix for #1774 - } - else - { - stream = client.GetStream(); - } - string h = string.Join("\r\n", requestMessage.ToArray()); - byte[] data = Encoding.ASCII.GetBytes(h); - stream.Write(data, 0, data.Length); - stream.Flush(); - - // Read response - int statusCode = ReadHttpStatus(stream); - var headers = ReadHeader(stream); - string? rbody; - if (headers.Get("transfer-encoding") == "chunked") - { - rbody = ReadBodyChunked(stream); - } - else - { - rbody = ReadBody(stream, int.Parse(headers.Get("content-length") ?? "0")); - } - if (headers.Get("set-cookie") != null) - { - response.Cookies = ParseSetCookie(headers.GetValues("set-cookie") ?? Array.Empty()); - } - response.Body = rbody ?? ""; - response.StatusCode = statusCode; - response.Headers = headers; try { - stream.Close(); - client.Close(); - } - catch { } + using var httpResponse = client.Send(request); + using var stream = httpResponse.Content.ReadAsStream(); + using var reader = new System.IO.StreamReader(stream); + string responseBody = reader.ReadToEnd(); - return response; - } + var responseHeaders = new NameValueCollection(); + foreach (var header in httpResponse.Headers) + foreach (var val in header.Value) + responseHeaders.Add(header.Key.ToLowerInvariant(), val); + foreach (var header in httpResponse.Content.Headers) + foreach (var val in header.Value) + responseHeaders.Add(header.Key.ToLowerInvariant(), val); - /// - /// Read HTTP response line from a Stream - /// - /// Stream to read - /// - /// If server return unknown data - private static int ReadHttpStatus(Stream s) - { - var httpHeader = ReadLine(s); // http header line - if (httpHeader.StartsWith("HTTP/1.1") || httpHeader.StartsWith("HTTP/1.0")) - { - return int.Parse(httpHeader.Split(' ')[1], NumberStyles.Any, CultureInfo.CurrentCulture); - } - else - { - throw new InvalidDataException("Unexpect data from server"); - } - } - - /// - /// Read HTTP headers from a Stream - /// - /// Stream to read - /// Headers in lower-case - private static NameValueCollection ReadHeader(Stream s) - { - var headers = new NameValueCollection(); - // Read headers - string header; - do - { - header = ReadLine(s); - if (!String.IsNullOrEmpty(header)) + var cookies = new NameValueCollection(); + foreach (System.Net.Cookie cookie in handler.CookieContainer.GetCookies(_uri)) { - var tmp = header.Split(new char[] { ':' }, 2); - var name = tmp[0].ToLower(); - var value = tmp[1].Trim(); - headers.Add(name, value); + if (!cookie.Expired) + cookies.Add(cookie.Name, cookie.Value); } + + return new Response((int)httpResponse.StatusCode, responseBody, responseHeaders, cookies); + } + catch (HttpRequestException ex) + { + if (Debug) + ConsoleIO.WriteLine("HTTP error: " + ex.Message); + return Response.Empty(); } - while (!String.IsNullOrEmpty(header)); - return headers; } /// - /// Read HTTP body from a Stream + /// Create a SocketsHttpHandler with proxy support from ProxyHandler settings. /// - /// Stream to read - /// Length of the body (the Content-Length header) - /// Body or null if length is zero - private static string? ReadBody(Stream s, int length) + private static SocketsHttpHandler CreateHandler() { - if (length > 0) + var handler = new SocketsHttpHandler { - byte[] buffer = new byte[length]; - int r = 0; - while (r < length) + UseCookies = true, + CookieContainer = new CookieContainer(), + AllowAutoRedirect = false, + ConnectTimeout = TimeSpan.FromSeconds(DefaultConnectTimeoutSeconds), + }; + + if (ProxyHandler.Config.Enabled_Login) + { + string proxyScheme = ProxyHandler.Config.Proxy_Type switch { - var read = s.Read(buffer, r, length - r); - r += read; - Thread.Sleep(50); + ProxyHandler.Configs.ProxyType.SOCKS4 => "socks4", + ProxyHandler.Configs.ProxyType.SOCKS4a => "socks4a", + ProxyHandler.Configs.ProxyType.SOCKS5 => "socks5", + _ => "http" + }; + + var proxyUri = new Uri($"{proxyScheme}://{ProxyHandler.Config.Server.Host}:{ProxyHandler.Config.Server.Port}"); + var proxy = new WebProxy(proxyUri); + + if (!string.IsNullOrWhiteSpace(ProxyHandler.Config.Username) && + !string.IsNullOrWhiteSpace(ProxyHandler.Config.Password)) + { + proxy.Credentials = new NetworkCredential( + ProxyHandler.Config.Username, + ProxyHandler.Config.Password); } - return Encoding.UTF8.GetString(buffer); - } - else - { - return null; + + handler.Proxy = proxy; + handler.UseProxy = true; } + + return handler; } /// - /// Read HTTP chunked body from a Stream + /// Build a cookie header value from a NameValueCollection. /// - /// Stream to read - /// Body or empty string if nothing is received - private static string ReadBodyChunked(Stream s) - { - List buffer1 = new(); - while (true) - { - string l = ReadLine(s); - int size = Int32.Parse(l, NumberStyles.HexNumber); - if (size == 0) - break; - byte[] buffer2 = new byte[size]; - int r = 0; - while (r < size) - { - var read = s.Read(buffer2, r, size - r); - r += read; - Thread.Sleep(50); - } - ReadLine(s); - buffer1.AddRange(buffer2); - } - return Encoding.UTF8.GetString(buffer1.ToArray()); - } - - /// - /// Parse the Set-Cookie header value into NameValueCollection. Cookie options are ignored - /// - /// Array of value strings - /// Parsed cookies - private static NameValueCollection ParseSetCookie(IEnumerable headerValue) - { - NameValueCollection cookies = new(); - foreach (var value in headerValue) - { - string[] cookie = value.Split(';'); // cookie options are ignored - string[] tmp = cookie[0].Split(new char[] { '=' }, 2); // Split first '=' only - string[] options = cookie[1..]; - string cname = tmp[0].Trim(); - string cvalue = tmp[1].Trim(); - // Check expire - bool isExpired = false; - foreach (var option in options) - { - var tmp2 = option.Trim().Split(new char[] { '=' }, 2); - // Check for Expires= and Max-Age= - if (tmp2.Length == 2) - { - var optName = tmp2[0].Trim().ToLower(); - var optValue = tmp2[1].Trim(); - switch (optName) - { - case "expires": - { - if (DateTime.TryParse(optValue, out var expDate)) - { - if (expDate < DateTime.Now) - isExpired = true; - } - break; - } - case "max-age": - { - if (int.TryParse(optValue, out var expInt)) - { - if (expInt <= 0) - isExpired = true; - } - break; - } - } - } - if (isExpired) - break; - } - if (!isExpired) - cookies.Add(cname, cvalue); - } - return cookies; - } - - /// - /// Read a line from a Stream - /// - /// - /// Line break by \r\n and they are not included in returned string - /// - /// Stream to read - /// String - private static string ReadLine(Stream s) - { - List buffer = new(); - byte c; - while (true) - { - int b = s.ReadByte(); - if (b == -1) - break; - c = (byte)b; - if (c == '\n') - { - if (buffer.Last() == '\r') - { - buffer.RemoveAt(buffer.Count - 1); - break; - } - } - buffer.Add(c); - } - return Encoding.UTF8.GetString(buffer.ToArray()); - } - - /// - /// Get the cookie string representation to use in header - /// - /// - /// private static string GetCookieString(NameValueCollection cookies) { var sb = new StringBuilder(); foreach (string key in cookies) { - var value = cookies[key]; - sb.Append(string.Format("{0}={1}; ", key, value)); + sb.Append($"{key}={cookies[key]}; "); } string result = sb.ToString(); - return result.Remove(result.Length - 2); // Remove "; " at the end + return result.Length >= 2 ? result[..^2] : result; } /// - /// Basic response object + /// Basic HTTP response object. /// public class Response { @@ -424,14 +217,8 @@ namespace MinecraftClient.Protocol Cookies = cookies; } - /// - /// Get an empty response object - /// - /// - public static Response Empty() - { - return new Response(204 /* No content */, "", new NameValueCollection(), new NameValueCollection()); - } + public static Response Empty() => + new(204, "", new NameValueCollection(), new NameValueCollection()); public override string ToString() { @@ -439,26 +226,18 @@ namespace MinecraftClient.Protocol sb.AppendLine("Status code: " + StatusCode); sb.AppendLine("Headers:"); foreach (string key in Headers) - { - sb.AppendLine(string.Format(" {0}: {1}", key, Headers[key])); - } + sb.AppendLine($" {key}: {Headers[key]}"); if (Cookies.Count > 0) { sb.AppendLine(); sb.AppendLine("Cookies: "); foreach (string key in Cookies) - { - sb.AppendLine(string.Format(" {0}={1}", key, Cookies[key])); - } + sb.AppendLine($" {key}={Cookies[key]}"); } if (Body != "") { sb.AppendLine(); - if (Body.Length > 200) - { - sb.AppendLine("Body: (Truncated to 200 characters)"); - } - else sb.AppendLine("Body: "); + sb.AppendLine(Body.Length > 200 ? "Body: (Truncated to 200 characters)" : "Body: "); sb.AppendLine(Body.Length > 200 ? Body[..200] + "..." : Body); } return sb.ToString(); diff --git a/MinecraftClient/Protocol/Session/SessionCache.cs b/MinecraftClient/Protocol/Session/SessionCache.cs index 1b64fea0..f3d00df9 100644 --- a/MinecraftClient/Protocol/Session/SessionCache.cs +++ b/MinecraftClient/Protocol/Session/SessionCache.cs @@ -123,35 +123,36 @@ namespace MinecraftClient.Protocol.Session { if (Config.Logging.DebugMessages) ConsoleIO.WriteLineFormatted(string.Format(Translations.cache_loading, Path.GetFileName(SessionCacheFileMinecraft))); - Json.JSONData mcSession = new(Json.JSONData.DataType.String); + System.Text.Json.Nodes.JsonNode? mcSession = null; try { mcSession = Json.ParseJson(File.ReadAllText(SessionCacheFileMinecraft)); } catch (IOException) { /* Failed to read file from disk -- ignoring */ } - if (mcSession.Type == Json.JSONData.DataType.Object - && mcSession.Properties.ContainsKey("clientToken") - && mcSession.Properties.ContainsKey("authenticationDatabase")) + if (mcSession is System.Text.Json.Nodes.JsonObject mcSessionObj + && mcSessionObj.ContainsKey("clientToken") + && mcSessionObj.ContainsKey("authenticationDatabase")) { - string clientID = mcSession.Properties["clientToken"].StringValue.Replace("-", ""); - Dictionary sessionItems = mcSession.Properties["authenticationDatabase"].Properties; - foreach (string key in sessionItems.Keys) + string clientID = mcSession["clientToken"]!.GetStringValue().Replace("-", ""); + var sessionItems = mcSession["authenticationDatabase"]!.AsObject(); + foreach (var kvp in sessionItems) { + string key = kvp.Key; if (Guid.TryParseExact(key, "N", out Guid temp)) { - Dictionary sessionItem = sessionItems[key].Properties; + var sessionItem = kvp.Value!.AsObject(); if (sessionItem.ContainsKey("displayName") && sessionItem.ContainsKey("accessToken") && sessionItem.ContainsKey("username") && sessionItem.ContainsKey("uuid")) { - string login = Settings.ToLowerIfNeed(sessionItem["username"].StringValue); + string login = Settings.ToLowerIfNeed(sessionItem["username"]!.GetStringValue()); try { SessionToken session = SessionToken.FromString(String.Join(",", - sessionItem["accessToken"].StringValue, - sessionItem["displayName"].StringValue, - sessionItem["uuid"].StringValue.Replace("-", ""), + sessionItem["accessToken"]!.GetStringValue(), + sessionItem["displayName"]!.GetStringValue(), + sessionItem["uuid"]!.GetStringValue().Replace("-", ""), clientID )); if (Config.Logging.DebugMessages) diff --git a/MinecraftClient/Scripting/ChatBot.cs b/MinecraftClient/Scripting/ChatBot.cs index 651c0eb3..a7dec54c 100644 --- a/MinecraftClient/Scripting/ChatBot.cs +++ b/MinecraftClient/Scripting/ChatBot.cs @@ -146,7 +146,7 @@ namespace MinecraftClient.Scripting /// Any text sent by the server will be sent here by MinecraftCom (extended variant) /// /// - /// You can use Json.ParseJson() to process the JSON string. + /// You can use Json.ParseJson() to obtain a System.Text.Json.Nodes.JsonNode for processing. /// /// Text from the server /// Raw JSON from the server. This parameter will be NULL on MC 1.5 or lower! diff --git a/MinecraftClient/config/ChatBots/DiscordWebhook.cs b/MinecraftClient/config/ChatBots/DiscordWebhook.cs index c3d7f9c2..892f087d 100644 --- a/MinecraftClient/config/ChatBots/DiscordWebhook.cs +++ b/MinecraftClient/config/ChatBots/DiscordWebhook.cs @@ -143,7 +143,7 @@ class SkinAPI var request = new ProxiedWebRequest("https://api.mojang.com/users/profiles/minecraft/" + name); request.Accept = "application/json"; var response = request.Get(); - string uuid = Json.ParseJson(response.Body).Properties["id"].StringValue; + string uuid = Json.ParseJson(response.Body)!["id"]!.GetStringValue(); settings.GetNamesToUuidMojangCache().Add(name, uuid); return uuid; } diff --git a/MinecraftClient/config/ChatBots/VkMessager.cs b/MinecraftClient/config/ChatBots/VkMessager.cs index 448e6840..f57b2efa 100644 --- a/MinecraftClient/config/ChatBots/VkMessager.cs +++ b/MinecraftClient/config/ChatBots/VkMessager.cs @@ -426,9 +426,9 @@ internal class VkLongPoolClient var jsonResult = CallVkMethod("groups.getLongPollServer", "group_id=" + BotCommunityId); var data = Json.ParseJson(jsonResult); - Key = data.Properties["response"].Properties["key"].StringValue; - Server = data.Properties["response"].Properties["server"].StringValue; - LastTs = Convert.ToInt32(data.Properties["response"].Properties["ts"].StringValue); + Key = data!["response"]!["key"]!.GetStringValue(); + Server = data["response"]!["server"]!.GetStringValue(); + LastTs = Convert.ToInt32(data["response"]!["ts"].GetStringValue()); } private void StartLongPoolAsync() @@ -457,25 +457,25 @@ internal class VkLongPoolClient { var j = JsonConvert.DeserializeObject(jsonData) as JObject; var data = Json.ParseJson(jsonData); - if (data.Properties.ContainsKey("failed")) + if (data?.AsObject().ContainsKey("failed") == true) { Init(); } - LastTs = Convert.ToInt32(data.Properties["ts"].StringValue); - var updates = data.Properties["updates"].DataArray; + LastTs = Convert.ToInt32(data!["ts"].GetStringValue()); + var updates = data["updates"]!.AsArray(); List> messages = new List>(); foreach (var str in updates) { - if (str.Properties["type"].StringValue != "message_new") continue; + if (str!["type"]!.GetStringValue() != "message_new") continue; - var msgData = str.Properties["object"].Properties; + var msgData = str["object"]!.AsObject(); - var id = msgData["from_id"].StringValue; - var userId = msgData["from_id"].StringValue; - var peer_id = msgData["peer_id"].StringValue; + var id = msgData["from_id"]!.GetStringValue(); + var userId = msgData["from_id"]!.GetStringValue(); + var peer_id = msgData["peer_id"]!.GetStringValue(); string event_id = ""; - var msgText = msgData["text"].StringValue; - var conversation_message_id = msgData["conversation_message_id"].StringValue; + var msgText = msgData["text"]!.GetStringValue(); + var conversation_message_id = msgData["conversation_message_id"]!.GetStringValue(); messages.Add(new Tuple(userId, peer_id, msgText, conversation_message_id, id, event_id)); }