diff --git a/Client.cs b/Client.cs index d854257..ff46f04 100644 --- a/Client.cs +++ b/Client.cs @@ -74,7 +74,7 @@ namespace WTelegram { var endpoint = _session.DataCenter == null ? IPEndPoint.Parse(_config("server_address")) : new IPEndPoint(IPAddress.Parse(_session.DataCenter.ip_address), _session.DataCenter.port); - Console.WriteLine($"Connecting to {endpoint}..."); + Helpers.Log(2, $"Connecting to {endpoint}..."); _tcpClient = new TcpClient(endpoint.AddressFamily); await _tcpClient.ConnectAsync(endpoint.Address, endpoint.Port); _networkStream = _tcpClient.GetStream(); @@ -102,7 +102,7 @@ namespace WTelegram private async Task MigrateDCAsync(int dcId) { - Console.WriteLine($"Migrate to DC {dcId}..."); + Helpers.Log(2, $"Migrate to DC {dcId}..."); //TODO: Export/Import client authorization? var prevFamily = _tcpClient.Client.RemoteEndPoint.AddressFamily; _tcpClient.Close(); @@ -137,7 +137,7 @@ namespace WTelegram if (_session.AuthKeyID == 0) // send unencrypted message { - Console.WriteLine($"Sending {msg.GetType().Name}..."); + Helpers.Log(1, $"Sending {msg.GetType().Name}..."); writer.Write(0L); // int64 auth_key_id = 0 (Unencrypted) writer.Write(msgId); // int64 message_id writer.Write(0); // int32 message_data_length (to be patched) @@ -146,7 +146,7 @@ namespace WTelegram } else { - Console.WriteLine($"Sending {msg.GetType().Name}... (seqno {seqno})"); + Helpers.Log(1, $"Sending {msg.GetType().Name}... (seqno {seqno})"); //TODO: Implement MTProto 2.0 using var clearStream = new MemoryStream(1024); //TODO: choose a useful capacity using var clearWriter = new BinaryWriter(clearStream, Encoding.UTF8); @@ -183,7 +183,7 @@ namespace WTelegram _lastSentMsg = msg; } - internal async Task RecvInternalAsync() + internal async Task RecvInternalAsync() { var data = await RecvFrameAsync(); if (data.Length == 4 && data[3] == 0xFF) @@ -204,7 +204,7 @@ namespace WTelegram var ctorNb = reader.ReadUInt32(); if (!Schema.Mappings.TryGetValue(ctorNb, out var realType)) throw new ApplicationException($"Cannot find type for ctor #{ctorNb:x}"); - Console.WriteLine($"Receiving {realType.Name,-50} timestamp={_session.MsgIdToStamp(msgId)} isResponse={(msgId & 2) != 0} unencrypted"); + Helpers.Log(1, $"Receiving {realType.Name,-50} timestamp={_session.MsgIdToStamp(msgId)} isResponse={(msgId & 2) != 0} unencrypted"); return Schema.DeserializeObject(reader, realType); } else if (authKeyId != _session.AuthKeyID) @@ -234,7 +234,7 @@ namespace WTelegram var ctorNb = reader.ReadUInt32(); if (!Schema.Mappings.TryGetValue(ctorNb, out var realType)) throw new ApplicationException($"Cannot find type for ctor #{ctorNb:x}"); - Console.WriteLine($"Receiving {realType.Name,-50} timestamp={_session.MsgIdToStamp(msgId)} isResponse={(msgId & 2) != 0} {(seqno == -1 ? "clearText" : "isContent")}={(seqno & 1) != 0}"); + Helpers.Log(1, $"Receiving {realType.Name,-50} timestamp={_session.MsgIdToStamp(msgId)} isResponse={(msgId & 2) != 0} {(seqno == -1 ? "clearText" : "isContent")}={(seqno & 1) != 0}"); if (realType == typeof(RpcResult)) return DeserializeRpcResult(reader); // hack necessary because some RPC return bare types like bool or int[] else @@ -282,14 +282,14 @@ namespace WTelegram } } - private object DeserializeRpcResult(BinaryReader reader) + private RpcResult DeserializeRpcResult(BinaryReader reader) { long reqMsgId = reader.ReadInt64(); var rpcResult = new RpcResult { req_msg_id = reqMsgId }; if (reqMsgId == _session.LastSentMsgId) rpcResult.result = Schema.DeserializeValue(reader, _lastRpcResultType); else - rpcResult.result = Schema.Deserialize(reader); + rpcResult.result = Schema.Deserialize(reader); return rpcResult; } @@ -343,15 +343,14 @@ namespace WTelegram await SendAsync(msgsAck, false); } - private async Task HandleMessageAsync(object obj) + private async Task HandleMessageAsync(ITLObject obj) { switch (obj) { case MsgContainer container: foreach (var msg in container.messages) { - Console.Write($" → {msg.body?.GetType().Name}"); - Console.WriteLine($"{new string(' ', Math.Max(0, 60 - Console.CursorLeft))} timestamp={_session.MsgIdToStamp(msg.msg_id)} isResponse={(msg.msg_id & 2) != 0} {(msg.seqno == -1 ? "clearText" : "isContent")}={(msg.seqno & 1) != 0}"); + Helpers.Log(1, $" → {msg.body?.GetType().Name,-48} timestamp={_session.MsgIdToStamp(msg.msg_id)} isResponse={(msg.msg_id & 2) != 0} {(msg.seqno == -1 ? "clearText" : "isContent")}={(msg.seqno & 1) != 0}"); if ((msg.seqno & 1) != 0) lock (_msgsToAck) _msgsToAck.Add(msg.msg_id); if (msg.body != null) await HandleMessageAsync(msg.body); } @@ -362,14 +361,14 @@ namespace WTelegram await SendAsync(_lastSentMsg); break; case BadMsgNotification badMsgNotification: - Console.WriteLine($"BadMsgNotification {badMsgNotification.error_code} for msg {badMsgNotification.bad_msg_seqno}"); + Helpers.Log(3, $"BadMsgNotification {badMsgNotification.error_code} for msg {badMsgNotification.bad_msg_seqno}"); break; case RpcResult rpcResult: if (_session.MsgIdToStamp(rpcResult.req_msg_id) >= _session.SessionStart) throw new ApplicationException($"Got RpcResult({rpcResult.result.GetType().Name}) for unknown msgId {rpcResult.req_msg_id}"); break; // silently ignore results for msg_id from previous sessions default: - //_updateHandler?.Invoke(obj); + _updateHandler?.Invoke(obj); break; } } @@ -386,7 +385,7 @@ namespace WTelegram api_hash = _apiHash, settings = settings ?? new() }); - Console.WriteLine($"A verification code has been sent via {sentCode.type.GetType().Name[17..]}"); + Helpers.Log(3, $"A verification code has been sent via {sentCode.type.GetType().Name[17..]}"); var verification_code = _config("verification_code"); var authorization = await CallAsync(new Auth_SignIn { diff --git a/Encryption.cs b/Encryption.cs index 98f31f6..d0fbc7b 100644 --- a/Encryption.cs +++ b/Encryption.cs @@ -28,7 +28,7 @@ namespace WTelegram if (resPQ.nonce != reqPQ.nonce) throw new ApplicationException("Nonce mismatch"); var fingerprint = resPQ.server_public_key_fingerprints.FirstOrDefault(PublicKeys.ContainsKey); if (fingerprint == 0) throw new ApplicationException("Couldn't match any server_public_key_fingerprints"); - Console.WriteLine($"Selected public key with fingerprint {fingerprint:X}"); + Helpers.Log(2, $"Selected public key with fingerprint {fingerprint:X}"); //3) long retry_id = 0; ulong pq = Helpers.FromBigEndian(resPQ.pq); @@ -69,7 +69,7 @@ namespace WTelegram var g_a = new BigInteger(serverDHinnerData.g_a, true, true); var dh_prime = new BigInteger(serverDHinnerData.dh_prime, true, true); ValidityChecks(dh_prime, serverDHinnerData.g); - Console.WriteLine($"Server time: {serverDHinnerData.server_time} UTC"); + Helpers.Log(1, $"Server time: {serverDHinnerData.server_time} UTC"); session.ServerTicksOffset = (serverDHinnerData.server_time - localTime).Ticks; //6) @@ -210,7 +210,7 @@ namespace WTelegram var bareData = Schema.Serialize(publicKey).AsSpan(4); // bare serialization var fingerprint = BitConverter.ToInt64(SHA1.HashData(bareData), 12); // 64 lower-order bits of SHA1 PublicKeys[fingerprint] = publicKey; - Console.WriteLine($"Loaded a public key with fingerprint {fingerprint:X}"); + Helpers.Log(1, $"Loaded a public key with fingerprint {fingerprint:X}"); } private static void LoadDefaultPublicKey() // fingerprint C3B42B026CE86B21 diff --git a/Helpers.cs b/Helpers.cs index ae18305..55dbfda 100644 --- a/Helpers.cs +++ b/Helpers.cs @@ -10,6 +10,17 @@ namespace WTelegram public static V GetOrCreate(this Dictionary dictionary, K key) where V : new() => dictionary.TryGetValue(key, out V value) ? value : dictionary[key] = new V(); + public static Action Log { get; set; } = DefaultLogger; + + private static readonly ConsoleColor[] LogLevelToColor = new[] { ConsoleColor.DarkGray, ConsoleColor.DarkCyan, ConsoleColor.Cyan, + ConsoleColor.Yellow, ConsoleColor.Red, ConsoleColor.Magenta, ConsoleColor.DarkBlue }; + private static void DefaultLogger(int level, string message) + { + Console.ForegroundColor = LogLevelToColor[level]; + Console.WriteLine(message); + Console.ResetColor(); + } + public static void LittleEndian(byte[] buffer, int offset, int value) { buffer[offset + 0] = (byte)value; diff --git a/Session.cs b/Session.cs index abc96c0..dbad8cb 100644 --- a/Session.cs +++ b/Session.cs @@ -31,12 +31,12 @@ namespace WTelegram var json = File.ReadAllText(pathname); var session = JsonSerializer.Deserialize(json, Helpers.JsonOptions); session._pathname = pathname; - Console.WriteLine("Loaded previous session"); + Helpers.Log(2, "Loaded previous session"); return session; } catch (Exception ex) { - Console.WriteLine($"Exception while reading session file: {ex.Message}"); + Helpers.Log(4, $"Exception while reading session file: {ex.Message}"); } } var sessionId = new byte[8]; diff --git a/TL.cs b/TL.cs index 8b7fac4..5546e7a 100644 --- a/TL.cs +++ b/TL.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Reflection; using System.Security.Cryptography; using System.Text; +using WTelegram; namespace TL { @@ -35,7 +36,7 @@ namespace TL return memStream.ToArray(); } - internal static T Deserialize(byte[] bytes) + internal static T Deserialize(byte[] bytes) where T : ITLObject { using var memStream = new MemoryStream(bytes); using var reader = new BinaryReader(memStream); @@ -50,7 +51,7 @@ namespace TL SerializeObject(writer, msg); } - internal static T Deserialize(BinaryReader reader) + internal static T Deserialize(BinaryReader reader) where T : ITLObject { var ctorNb = reader.ReadUInt32(); if (!Mappings.TryGetValue(ctorNb, out var realType)) @@ -72,7 +73,7 @@ namespace TL } } - internal static object DeserializeObject(BinaryReader reader, Type type) + internal static ITLObject DeserializeObject(BinaryReader reader, Type type) { var obj = Activator.CreateInstance(type); var fields = obj.GetType().GetFields().GroupBy(f => f.DeclaringType).Reverse().SelectMany(g => g); @@ -85,7 +86,7 @@ namespace TL field.SetValue(obj, value); if (field.Name.Equals("Flags", StringComparison.OrdinalIgnoreCase)) flags = (int)value; } - return type == typeof(GzipPacked) ? UnzipPacket((GzipPacked)obj) : obj; + return type == typeof(GzipPacked) ? UnzipPacket((GzipPacked)obj) : (ITLObject)obj; } internal static void SerializeValue(BinaryWriter writer, object value) @@ -161,7 +162,7 @@ namespace TL else if (type.IsValueType) return DeserializeObject(reader, type); else - return Deserialize(reader); + return Deserialize(reader); default: ShouldntBeHere(); return null; @@ -239,18 +240,18 @@ namespace TL } catch (Exception ex) { - Console.WriteLine(ex); + Helpers.Log(4, ex.ToString()); } reader.BaseStream.Position = pos + array[i].bytes; } return array; } - private static object UnzipPacket(GzipPacked obj) + private static ITLObject UnzipPacket(GzipPacked obj) { using var reader = new BinaryReader(new GZipStream(new MemoryStream(obj.packed_data), CompressionMode.Decompress)); - var result = DeserializeValue(reader, typeof(object)); - Console.WriteLine($" → {result.GetType().Name}"); + var result = Deserialize(reader); + Helpers.Log(1, $" → {result.GetType().Name}"); return result; } diff --git a/WTelegramClient.csproj b/WTelegramClient.csproj index a7a43bd..4e98d26 100644 --- a/WTelegramClient.csproj +++ b/WTelegramClient.csproj @@ -6,6 +6,11 @@ WTelegram + + + + +