mirror of
https://github.com/MCCTeam/Minecraft-Console-Client
synced 2026-08-15 13:04:36 +00:00
Merge pull request #11 from milutinke/copilot/update-json-http-client
This commit is contained in:
commit
5e99e3a42d
16 changed files with 393 additions and 999 deletions
|
|
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// JSON utilities backed by System.Text.Json.
|
||||
/// </summary>
|
||||
public static class Json
|
||||
{
|
||||
private static readonly JsonSerializerOptions s_escapeOptions = new()
|
||||
{
|
||||
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Parse a JSON string into a mutable <see cref="JsonNode"/> DOM.
|
||||
/// Returns null for null, empty, or whitespace-only input.
|
||||
/// Returns a <see cref="JsonValue"/> wrapping the raw string when the input
|
||||
/// is not valid JSON (e.g. a plain-text Minecraft MOTD or chat message).
|
||||
/// </summary>
|
||||
public static JsonNode? ParseJson(string? json)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(json)) return null;
|
||||
try { return JsonNode.Parse(json); }
|
||||
catch (JsonException) { return JsonValue.Create(json); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Escape a string for embedding inside a JSON string literal.
|
||||
/// Uses System.Text.Json serialization and strips the surrounding quotes.
|
||||
/// </summary>
|
||||
public static string EscapeString(string src) =>
|
||||
JsonSerializer.Serialize(src, s_escapeOptions)[1..^1];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extension helpers for <see cref="JsonNode"/> that replicate the access patterns
|
||||
/// of the former <c>JSONData.StringValue</c> property.
|
||||
/// </summary>
|
||||
public static class JsonNodeExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public static class Json
|
||||
public static string GetStringValue(this JsonNode? node) => node switch
|
||||
{
|
||||
/// <summary>
|
||||
/// Parse some JSON and return the corresponding JSON object
|
||||
/// </summary>
|
||||
public static JSONData ParseJson(string json)
|
||||
{
|
||||
int cursorpos = 0;
|
||||
return String2Data(json, ref cursorpos);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The class storing unserialized JSON data
|
||||
/// The data can be an object, an array or a string
|
||||
/// </summary>
|
||||
public class JSONData
|
||||
{
|
||||
public enum DataType
|
||||
{
|
||||
Object,
|
||||
Array,
|
||||
String
|
||||
};
|
||||
|
||||
private readonly DataType type;
|
||||
|
||||
public DataType Type
|
||||
{
|
||||
get { return type; }
|
||||
}
|
||||
|
||||
public Dictionary<string, JSONData> Properties;
|
||||
public List<JSONData> DataArray;
|
||||
public string StringValue;
|
||||
|
||||
public JSONData(DataType datatype)
|
||||
{
|
||||
type = datatype;
|
||||
Properties = new Dictionary<string, JSONData>();
|
||||
DataArray = new List<JSONData>();
|
||||
StringValue = String.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a JSON string to build a JSON object
|
||||
/// </summary>
|
||||
/// <param name="toparse">String to parse</param>
|
||||
/// <param name="cursorpos">Cursor start (set to 0 for function init)</param>
|
||||
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<end of string>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if a char is an hexadecimal char (0-9 A-F a-f)
|
||||
/// </summary>
|
||||
/// <param name="c">Char to test</param>
|
||||
/// <returns>True if hexadecimal</returns>
|
||||
private static bool IsHex(char c)
|
||||
{
|
||||
return ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Advance the cursor to skip white spaces and line breaks
|
||||
/// </summary>
|
||||
/// <param name="toparse">String to parse</param>
|
||||
/// <param name="cursorpos">Cursor position to update</param>
|
||||
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 "</" for <script> 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<string>(out var s) => s,
|
||||
_ => node.ToJsonString()
|
||||
};
|
||||
}
|
||||
|
|
@ -51,8 +51,8 @@ namespace MinecraftClient.Mapping.BlockPalettes
|
|||
HashSet<int> knownStates = new();
|
||||
Dictionary<string, HashSet<int>> blocks = new();
|
||||
|
||||
Json.JSONData palette = Json.ParseJson(File.ReadAllText(blocksJsonFile, Encoding.UTF8));
|
||||
foreach (KeyValuePair<string, Json.JSONData> 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<int>();
|
||||
|
||||
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 + "!?");
|
||||
|
|
|
|||
|
|
@ -21,13 +21,13 @@ namespace MinecraftClient.Protocol
|
|||
/// <returns></returns>
|
||||
private static Dictionary<int, string> 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<int, string> registry = new();
|
||||
|
||||
foreach (KeyValuePair<string, Json.JSONData> 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(
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ namespace MinecraftClient.Protocol.Handlers.Forge
|
|||
/// </summary>
|
||||
/// <param name="data">The modinfo JSON tag.</param>
|
||||
/// <param name="fmlVersion">Forge protocol version</param>
|
||||
internal ForgeInfo(Json.JSONData data, FMLVersion fmlVersion)
|
||||
internal ForgeInfo(System.Text.Json.Nodes.JsonObject data, FMLVersion fmlVersion)
|
||||
{
|
||||
Mods = new List<ForgeMod>();
|
||||
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<byte> dataPackage = decodeOptimized(encodedData);
|
||||
DataTypes dataTypes = new DataTypes(Protocol18Handler.MC_1_18_1_Version);
|
||||
|
||||
|
|
|
|||
|
|
@ -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 : "")));
|
||||
|
|
|
|||
|
|
@ -484,7 +484,7 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
/// <param name="jsonData">JSON data returned by the server</param>
|
||||
/// <param name="forgeInfo">ForgeInfo to populate</param>
|
||||
/// <returns>True if the server is running Forge</returns>
|
||||
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
|
|||
/// <param name="forgeInfo">ForgeInfo to populate</param>
|
||||
/// <param name="fmlVersion">Forge protocol version</param>
|
||||
/// <returns>True if the server is running Forge</returns>
|
||||
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())
|
||||
|
|
|
|||
|
|
@ -436,74 +436,70 @@ namespace MinecraftClient.Protocol.Message
|
|||
/// <param name="colorcode">Allow parent color code to affect child elements (set to "" for function init)</param>
|
||||
/// <param name="links">Container for links from JSON serialized text</param>
|
||||
/// <returns>returns the Minecraft-formatted string</returns>
|
||||
private static string JSONData2String(Json.JSONData data, string colorcode, List<string>? links)
|
||||
private static string JSONData2String(System.Text.Json.Nodes.JsonNode? data, string colorcode, List<string>? 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<string> 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<string, object> nbt)
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -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()
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -121,7 +121,7 @@ namespace MinecraftClient.Protocol
|
|||
{
|
||||
Task<string> 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<string> 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<string, DateTime> UuidToNameHistory(string uuid)
|
||||
{
|
||||
Dictionary<string, DateTime> tempDict = new();
|
||||
List<Json.JSONData> jsonDataList;
|
||||
System.Text.Json.Nodes.JsonArray jsonDataList;
|
||||
|
||||
// Perform web request
|
||||
try
|
||||
{
|
||||
Task<string> 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
|
|||
/// <returns>Dictionary of the Mojang services</returns>
|
||||
public static MojangServiceStatus GetMojangServiceStatus()
|
||||
{
|
||||
List<Json.JSONData> jsonDataList;
|
||||
System.Text.Json.Nodes.JsonArray jsonDataList;
|
||||
|
||||
// Perform web request
|
||||
try
|
||||
{
|
||||
Task<string> 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
|
|||
/// <returns>Dictionary with a link to the skin and cape of a player.</returns>
|
||||
public static SkinInfo GetSkinInfo(string uuid)
|
||||
{
|
||||
Dictionary<string, Json.JSONData> 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<string> 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");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<string> 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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Set to true to tell the http client proxy is enabled
|
||||
/// </summary>
|
||||
public bool IsProxy { get { return isProxied; } set { isProxied = value; } }
|
||||
public bool Debug { get { return Settings.Config.Logging.DebugMessages; } }
|
||||
|
||||
/// <summary>
|
||||
/// Create a new http request
|
||||
/// Create a new HTTP request
|
||||
/// </summary>
|
||||
/// <param name="url">Target URL</param>
|
||||
public ProxiedWebRequest(string url)
|
||||
{
|
||||
uri = new Uri(url);
|
||||
_uri = new Uri(url);
|
||||
SetupBasicHeaders();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new http request with cookies
|
||||
/// Create a new HTTP request with cookies
|
||||
/// </summary>
|
||||
/// <param name="url">Target URL</param>
|
||||
/// <param name="cookies">Cookies to use</param>
|
||||
/// <param name="cookies">Cookies to include in the request</param>
|
||||
public ProxiedWebRequest(string url, NameValueCollection cookies)
|
||||
{
|
||||
uri = new Uri(url);
|
||||
_uri = new Uri(url);
|
||||
Headers.Add("Cookie", GetCookieString(cookies));
|
||||
SetupBasicHeaders();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new http request with custom tcp client
|
||||
/// </summary>
|
||||
/// <param name="tcpFactory">Tcp factory to be used</param>
|
||||
/// <param name="url">Target URL</param>
|
||||
public ProxiedWebRequest(ITcpFactory tcpFactory, string url) : this(url)
|
||||
{
|
||||
this.tcpFactory = tcpFactory;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new http request with custom tcp client and cookies
|
||||
/// </summary>
|
||||
/// <param name="tcpFactory">Tcp factory to be used</param>
|
||||
/// <param name="url">Target URL</param>
|
||||
/// <param name="cookies">Cookies to use</param>
|
||||
public ProxiedWebRequest(ITcpFactory tcpFactory, string url, NameValueCollection cookies) : this(url, cookies)
|
||||
{
|
||||
this.tcpFactory = tcpFactory;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Setup some basic headers
|
||||
/// </summary>
|
||||
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");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Perform GET request and get the response. Proxy is handled automatically
|
||||
/// Perform GET request. Proxy is handled automatically.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public Response Get()
|
||||
{
|
||||
return Send("GET");
|
||||
}
|
||||
public Response Get() => Send(HttpMethod.Get);
|
||||
|
||||
/// <summary>
|
||||
/// Perform POST request and get the response. Proxy is handled automatically
|
||||
/// Perform POST request. Proxy is handled automatically.
|
||||
/// </summary>
|
||||
/// <param name="contentType">The content type of request body</param>
|
||||
/// <param name="body">Request body</param>
|
||||
/// <returns></returns>
|
||||
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);
|
||||
|
||||
/// <summary>
|
||||
/// Send a http request to the server. Proxy is handled automatically
|
||||
/// Send an HTTP request. Proxy is configured automatically from Settings.
|
||||
/// </summary>
|
||||
/// <param name="method">Method in string representation</param>
|
||||
/// <param name="body">Optional request body</param>
|
||||
/// <returns></returns>
|
||||
private Response Send(string method, string body = "")
|
||||
private Response Send(HttpMethod method, string? contentType = null, string? body = null)
|
||||
{
|
||||
List<string> 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(""); // <CR><LF>
|
||||
if (body != "")
|
||||
|
||||
if (body is not null)
|
||||
{
|
||||
requestMessage.Add(body);
|
||||
request.Content = new StringContent(body, Encoding.UTF8, contentType ?? "text/plain");
|
||||
}
|
||||
else requestMessage.Add(""); // <CR><LF>
|
||||
|
||||
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<string>());
|
||||
}
|
||||
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);
|
||||
|
||||
/// <summary>
|
||||
/// Read HTTP response line from a Stream
|
||||
/// </summary>
|
||||
/// <param name="s">Stream to read</param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="InvalidDataException">If server return unknown data</exception>
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read HTTP headers from a Stream
|
||||
/// </summary>
|
||||
/// <param name="s">Stream to read</param>
|
||||
/// <returns>Headers in lower-case</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read HTTP body from a Stream
|
||||
/// Create a SocketsHttpHandler with proxy support from ProxyHandler settings.
|
||||
/// </summary>
|
||||
/// <param name="s">Stream to read</param>
|
||||
/// <param name="length">Length of the body (the Content-Length header)</param>
|
||||
/// <returns>Body or null if length is zero</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read HTTP chunked body from a Stream
|
||||
/// Build a cookie header value from a NameValueCollection.
|
||||
/// </summary>
|
||||
/// <param name="s">Stream to read</param>
|
||||
/// <returns>Body or empty string if nothing is received</returns>
|
||||
private static string ReadBodyChunked(Stream s)
|
||||
{
|
||||
List<byte> 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());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse the Set-Cookie header value into NameValueCollection. Cookie options are ignored
|
||||
/// </summary>
|
||||
/// <param name="headerValue">Array of value strings</param>
|
||||
/// <returns>Parsed cookies</returns>
|
||||
private static NameValueCollection ParseSetCookie(IEnumerable<string> 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=<date> and Max-Age=<number>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a line from a Stream
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Line break by \r\n and they are not included in returned string
|
||||
/// </remarks>
|
||||
/// <param name="s">Stream to read</param>
|
||||
/// <returns>String</returns>
|
||||
private static string ReadLine(Stream s)
|
||||
{
|
||||
List<byte> 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());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the cookie string representation to use in header
|
||||
/// </summary>
|
||||
/// <param name="cookies"></param>
|
||||
/// <returns></returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Basic response object
|
||||
/// Basic HTTP response object.
|
||||
/// </summary>
|
||||
public class Response
|
||||
{
|
||||
|
|
@ -424,14 +217,8 @@ namespace MinecraftClient.Protocol
|
|||
Cookies = cookies;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get an empty response object
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
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();
|
||||
|
|
|
|||
|
|
@ -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<string, Json.JSONData> 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<string, Json.JSONData> 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)
|
||||
|
|
|
|||
|
|
@ -146,7 +146,7 @@ namespace MinecraftClient.Scripting
|
|||
/// Any text sent by the server will be sent here by MinecraftCom (extended variant)
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 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.
|
||||
/// </remarks>
|
||||
/// <param name="text">Text from the server</param>
|
||||
/// <param name="json">Raw JSON from the server. This parameter will be NULL on MC 1.5 or lower!</param>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Tuple<string, string, string, string, string, string>> messages = new List<Tuple<string, string, string, string, string, string>>();
|
||||
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<string, string, string, string, string, string>(userId, peer_id, msgText, conversation_message_id, id, event_id));
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue