diff --git a/src/Client.cs b/src/Client.cs index a5fd6ba..4437242 100644 --- a/src/Client.cs +++ b/src/Client.cs @@ -43,6 +43,7 @@ namespace WTelegram private readonly Dictionary tcs)> _pendingRequests = new(); private SemaphoreSlim _sendSemaphore = new(0); private CancellationTokenSource _cts; + private int _reactorReconnects = 0; /// Welcome to WTelegramClient! 😀 /// Config callback, is queried for: api_id, api_hash, session_pathname @@ -164,7 +165,7 @@ namespace WTelegram var authorization = await this.Auth_ImportAuthorization(exported.id, exported.bytes); if (authorization is not Auth_Authorization { user: User user }) throw new ApplicationException("Failed to get Authorization: " + authorization.GetType().Name); - _session.User = user.Serialize(); + _session.User = user; } } @@ -180,7 +181,6 @@ namespace WTelegram private async Task Reactor(NetworkStream stream, CancellationTokenSource cts) { - int reconnects = 0; while (!cts.IsCancellationRequested) { ITLObject obj = null; @@ -203,8 +203,8 @@ namespace WTelegram _pendingRequests.Clear(); _bareRequest = 0; } - reconnects = (reconnects + 1) % MaxAutoReconnects; - if (reconnects != 0) + _reactorReconnects = (_reactorReconnects + 1) % MaxAutoReconnects; + if (_reactorReconnects != 0) { Reset(false); await ConnectAsync(); // start a new reactor @@ -696,7 +696,7 @@ namespace WTelegram { try { - var prevUser = Serialization.Deserialize(_session.User); + var prevUser = _session.User; if (prevUser?.id == int.Parse(botToken.Split(':')[0])) return prevUser; } @@ -709,7 +709,7 @@ namespace WTelegram var authorization = await this.Auth_ImportBotAuthorization(0, _apiId, _apiHash, botToken); if (authorization is not Auth_Authorization { user: User user }) throw new ApplicationException("Failed to get Authorization: " + authorization.GetType().Name); - _session.User = user.Serialize(); + _session.User = user; _session.Save(); return user; } @@ -728,7 +728,7 @@ namespace WTelegram { try { - var prevUser = Serialization.Deserialize(_session.User); + var prevUser = _session.User; var userId = _config("user_id"); // if config prefers to validate current user by its id, use it if (userId == null || !int.TryParse(userId, out int id) || id != -1 && prevUser.id != id) { @@ -780,7 +780,7 @@ namespace WTelegram if (authorization is not Auth_Authorization { user: User user }) throw new ApplicationException("Failed to get Authorization: " + authorization.GetType().Name); //TODO: find better serialization for User not subject to TL changes? - _session.User = user.Serialize(); + _session.User = user; _session.Save(); return user; } diff --git a/src/Helpers.cs b/src/Helpers.cs index 6e2e72f..0a39a5a 100644 --- a/src/Helpers.cs +++ b/src/Helpers.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.Numerics; +using System.Text.Json; +using System.Text.Json.Serialization; namespace WTelegram { @@ -9,7 +11,7 @@ namespace WTelegram // int argument is the LogLevel: https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.loglevel public static Action Log { get; set; } = DefaultLogger; - public static readonly System.Text.Json.JsonSerializerOptions JsonOptions = new(System.Text.Json.JsonSerializerDefaults.Web) { IncludeFields = true, WriteIndented = true }; + public static readonly JsonSerializerOptions JsonOptions = new() { IncludeFields = true, WriteIndented = true }; public static V GetOrCreate(this Dictionary dictionary, K key) where V : new() => dictionary.TryGetValue(key, out V value) ? value : dictionary[key] = new V(); @@ -23,6 +25,41 @@ namespace WTelegram Console.ResetColor(); } + internal class PolymorphicConverter : JsonConverter where T : class + { + public override bool HandleNull => true; + + public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) + { + if (value == null) + writer.WriteNullValue(); + else + { + writer.WriteStartObject(); + writer.WritePropertyName(value.GetType().FullName); + JsonSerializer.Serialize(writer, value, value.GetType(), JsonOptions); + writer.WriteEndObject(); + } + } + + public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.Null) + return null; + else if (reader.TokenType == JsonTokenType.StartObject) + { + if (!reader.Read() || reader.TokenType != JsonTokenType.PropertyName) throw new JsonException(); + var returnType = typeToConvert.Assembly.GetType(reader.GetString()); + if (!typeToConvert.IsAssignableFrom(returnType)) throw new JsonException(); + var result = (T)JsonSerializer.Deserialize(ref reader, returnType, JsonOptions); + if (!reader.Read() || reader.TokenType != JsonTokenType.EndObject) throw new JsonException(); + return result; + } + else + throw new JsonException(); + } + } + public static long RandomLong() { #if NETCOREAPP2_1_OR_GREATER diff --git a/src/Session.cs b/src/Session.cs index 811e2f8..d07fd09 100644 --- a/src/Session.cs +++ b/src/Session.cs @@ -16,13 +16,21 @@ namespace WTelegram public long ServerTicksOffset; public long LastSentMsgId; public TL.DcOption DataCenter; - public byte[] User; // serialization of TL.User + public TL.User User; public DateTime SessionStart => _sessionStart; private readonly DateTime _sessionStart = DateTime.UtcNow; private string _pathname; private byte[] _apiHash; // used as AES key for encryption of session file + private static readonly JsonSerializerOptions JsonOptions = new(Helpers.JsonOptions) + { + Converters = { + new Helpers.PolymorphicConverter(), + new Helpers.PolymorphicConverter() + } + }; + internal static Session LoadOrCreate(string pathname, byte[] apiHash) { if (File.Exists(pathname)) @@ -51,12 +59,12 @@ namespace WTelegram var utf8Json = decryptor.TransformFinalBlock(input, 16, input.Length - 16); if (!Encryption.Sha256.ComputeHash(utf8Json, 32, utf8Json.Length - 32).SequenceEqual(utf8Json[0..32])) throw new ApplicationException("Integrity check failed in session loading"); - return JsonSerializer.Deserialize(utf8Json.AsSpan(32), Helpers.JsonOptions); + return JsonSerializer.Deserialize(utf8Json.AsSpan(32), JsonOptions); } internal void Save() { - var utf8Json = JsonSerializer.SerializeToUtf8Bytes(this, Helpers.JsonOptions); + var utf8Json = JsonSerializer.SerializeToUtf8Bytes(this, JsonOptions); var finalBlock = new byte[16]; var output = new byte[(16 + 32 + utf8Json.Length + 16) & ~15]; Encryption.RNG.GetBytes(output, 0, 16); @@ -91,7 +99,8 @@ namespace WTelegram { DataCenter = newDC; AuthKeyID = Salt = Seqno = 0; - AuthKey = User = null; + AuthKey = null; + User = null; } } } \ No newline at end of file