From 38f088abab5224d126b5b721aa9be6dfa910305f Mon Sep 17 00:00:00 2001 From: Alexander Merkulov Date: Thu, 9 Jan 2020 10:44:08 +0300 Subject: [PATCH] Reapply Events PR #679 --- README.md | 251 ++++++++++++++++++++++++++ TLSharp.Core/Network/Exceptions.cs | 69 +++++++ TLSharp.Core/Network/MtProtoSender.cs | 182 ++++++++++--------- TLSharp.Core/Network/Sniffer.cs | 25 +++ TLSharp.Core/Network/TcpTransport.cs | 76 +++++++- TLSharp.Core/TLSharp.Core.csproj | 6 + TLSharp.Core/TelegramClient.cs | 65 ++++++- TLSharp.Core/packages.config | 1 + 8 files changed, 586 insertions(+), 89 deletions(-) create mode 100644 TLSharp.Core/Network/Exceptions.cs create mode 100644 TLSharp.Core/Network/Sniffer.cs diff --git a/README.md b/README.md index 9d89a90..d840e69 100644 --- a/README.md +++ b/README.md @@ -143,6 +143,257 @@ To download file you should call **GetFile** method Full code you can see at [DownloadFileFromContactTest](https://github.com/sochix/TLSharp/blob/master/TLSharp.Tests/TLSharpTests.cs#L167) +# Events Sample code +```csharp + using System; + using System.Threading.Tasks; + using TeleSharp.TL; + using TLSharp.Core; + using System.Linq; + using TeleSharp.TL.Messages; + using System.Collections.Generic; + + namespace TLSharpPOC + { + class MainClass + { + const int APIId = 0; + const string APIHash = "???"; + const string phone = "???"; + public static void Main(string[] args) + { + new MainClass().MainAsync(args).Wait(); + } + + private async Task MainAsync(string[] args) + { + TelegramClient client = null; + try + { + // -- if necessary, IP can be changed so the client can connect to the test network. + Session session = null; + // new Session(new FileSessionStore(), "session") + //{ + // ServerAddress = "149.154.175.10", + // Port = 443 + //}; + //Console.WriteLine($"{session.ServerAddress}:{session.Port} {phone}"); + client = new TelegramClient(APIId, APIHash, session); + // subscribe an event to receive live messages + client.Updates += Client_Updates; + await client.ConnectAsync(); + Console.WriteLine($"Authorised: {client.IsUserAuthorized()}"); + TLUser user = null; + // -- If the user has already authenticated, this step will prevent account from being blocked as it + // -- reuses the data from last authorisation. + if (client.IsUserAuthorized()) + user = client.Session.TLUser; + else + { + var registered = await client.IsPhoneRegisteredAsync(phone); + var hash = await client.SendCodeRequestAsync(phone); + Console.Write("Code: "); + var code = Console.ReadLine(); + if (!registered) + { + Console.WriteLine($"Sign up {phone}"); + user = await client.SignUpAsync(phone, hash, code, "First", "Last"); + } + Console.WriteLine($"Sign in {phone}"); + user = await client.MakeAuthAsync(phone, hash, code); + } + + var contacts = await client.GetContactsAsync(); + Console.WriteLine("Contacts:"); + foreach (var contact in contacts.Users.OfType()) + { + var contactUser = contact as TLUser; + Console.WriteLine($"\t{contact.Id} {contact.Phone} {contact.FirstName} {contact.LastName}"); + } + + + var dialogs = (TLDialogs) await client.GetUserDialogsAsync(); + Console.WriteLine("Channels: "); + foreach (var channelObj in dialogs.Chats.OfType()) + { + var channel = channelObj as TLChannel; + Console.WriteLine($"\tChat: {channel.Title}"); + } + + Console.WriteLine("Groups:"); + TLChat chat = null; + foreach (var chatObj in dialogs.Chats.OfType()) + { + chat = chatObj as TLChat; + Console.WriteLine($"Chat name: {chat.Title}"); + var request = new TLRequestGetFullChat() { ChatId = chat.Id }; + var fullChat = await client.SendRequestAsync(request); + + var participants = (fullChat.FullChat as TeleSharp.TL.TLChatFull).Participants as TLChatParticipants; + foreach (var p in participants.Participants) + { + if (p is TLChatParticipant) + { + var participant = p as TLChatParticipant; + Console.WriteLine($"\t{participant.UserId}"); + } + else if (p is TLChatParticipantAdmin) + { + var participant = p as TLChatParticipantAdmin; + Console.WriteLine($"\t{participant.UserId}**"); + } + else if (p is TLChatParticipantCreator) + { + var participant = p as TLChatParticipantCreator; + Console.WriteLine($"\t{participant.UserId}**"); + } + } + + var peer = new TLInputPeerChat() { ChatId = chat.Id }; + var m = await client.GetHistoryAsync(peer, 0, 0, 0); + Console.WriteLine(m); + if (m is TLMessages) + { + var messages = m as TLMessages; + + + foreach (var message in messages.Messages) + { + if (message is TLMessage) + { + var m1 = message as TLMessage; + Console.WriteLine($"\t\t{m1.Id} {m1.Message}"); + } + else if (message is TLMessageService) + { + var m1 = message as TLMessageService; + Console.WriteLine($"\t\t{m1.Id} {m1.Action}"); + } + } + } + else if (m is TLMessagesSlice) + { + bool done = false; + int total = 0; + while (!done) + { + var messages = m as TLMessagesSlice; + + foreach (var m1 in messages.Messages) + { + if (m1 is TLMessage) + { + var message = m1 as TLMessage; + Console.WriteLine($"\t\t{message.Id} {message.Message}"); + ++total; + } + else if (m1 is TLMessageService) + { + var message = m1 as TLMessageService; + Console.WriteLine($"\t\t{message.Id} {message.Action}"); + ++total; + done = message.Action is TLMessageActionChatCreate; + } + } + m = await client.GetHistoryAsync(peer, total, 0, 0); + } + } + } + + // -- Wait in a loop to handle incoming updates. No need to poll. + for (;;) + { + await client.WaitEventAsync(); + } + } + catch (Exception e) + { + Console.WriteLine(e); + } + } + + private void Client_Updates(TelegramClient client, TLAbsUpdates updates) + { + Console.WriteLine($"Got update: {updates}"); + if (updates is TLUpdateShort) + { + var updateShort = updates as TLUpdateShort; + Console.WriteLine($"Short: {updateShort.Update}"); + if (updateShort.Update is TLUpdateUserStatus) + { + var status = updateShort.Update as TLUpdateUserStatus; + Console.WriteLine($"User {status.UserId} is {status.Status}"); + if (status.Status is TLUserStatusOnline) + { + try + { + var peer = new TLInputPeerUser() { UserId = status.UserId }; + client.SendMessageAsync(peer, "Você está online.").Wait(); + } catch {} + } + } + } + else if (updates is TLUpdateShortMessage) + { + var message = updates as TLUpdateShortMessage; + Console.WriteLine($"Message: {message.Message}"); + MarkMessageRead(client, new TLInputPeerUser() { UserId = message.UserId }, message.Id); + } + else if (updates is TLUpdateShortChatMessage) + { + var message = updates as TLUpdateShortChatMessage; + Console.WriteLine($"Chat Message: {message.Message}"); + MarkMessageRead(client, new TLInputPeerChat() { ChatId = message.ChatId }, message.Id); + } + else if (updates is TLUpdates) + { + var allUpdates = updates as TLUpdates; + foreach (var update in allUpdates.Updates) + { + Console.WriteLine($"\t{update}"); + if (update is TLUpdateNewChannelMessage) + { + var metaMessage = update as TLUpdateNewChannelMessage; + var message = metaMessage.Message as TLMessage; + Console.WriteLine($"Channel message: {message.Message}"); + var channel = allUpdates.Chats[0] as TLChannel; + MarkMessageRead(client, + new TLInputPeerChannel() { ChannelId = channel.Id, AccessHash = channel.AccessHash.Value }, + message.Id ); + } + } + + foreach(var user in allUpdates.Users) + { + Console.WriteLine($"{user}"); + } + + foreach (var chat in allUpdates.Chats) + { + Console.WriteLine($"{chat}"); + } + } + } + + private void MarkMessageRead(TelegramClient client, TLAbsInputPeer peer, int id) + { + // An exception happens here but it's not fatal. + try + { + var request = new TLRequestReadHistory(); + request.MaxId = id; + request.Peer = peer; + client.SendRequestAsync(request).Wait(); + } + catch (InvalidOperationException e){ + System.Console.WriteLine(e.getMessage()) + } + + } + } + } +``` + # Available Methods For your convenience TLSharp have wrappers for several Telegram API methods. You could add your own, see details below. diff --git a/TLSharp.Core/Network/Exceptions.cs b/TLSharp.Core/Network/Exceptions.cs new file mode 100644 index 0000000..878e64e --- /dev/null +++ b/TLSharp.Core/Network/Exceptions.cs @@ -0,0 +1,69 @@ +using System; +namespace TLSharp.Core.Network +{ + public class FloodException : Exception + { + public TimeSpan TimeToWait { get; private set; } + + internal FloodException(TimeSpan timeToWait) + : base($"Flood prevention. Telegram now requires your program to do requests again only after {timeToWait.TotalSeconds} seconds have passed ({nameof(TimeToWait)} property)." + + " If you think the culprit of this problem may lie in TLSharp's implementation, open a Github issue please.") + { + TimeToWait = timeToWait; + } + } + + public class BadMessageException : Exception + { + internal BadMessageException(string description) : base(description) + { + } + } + + internal abstract class DataCenterMigrationException : Exception + { + internal int DC { get; private set; } + + private const string REPORT_MESSAGE = + " See: https://github.com/sochix/TLSharp#i-get-a-xxxmigrationexception-or-a-migrate_x-error"; + + protected DataCenterMigrationException(string msg, int dc) : base(msg + REPORT_MESSAGE) + { + DC = dc; + } + } + + internal class PhoneMigrationException : DataCenterMigrationException + { + internal PhoneMigrationException(int dc) + : base($"Phone number registered to a different DC: {dc}.", dc) + { + } + } + + internal class FileMigrationException : DataCenterMigrationException + { + internal FileMigrationException(int dc) + : base($"File located on a different DC: {dc}.", dc) + { + } + } + + internal class UserMigrationException : DataCenterMigrationException + { + internal UserMigrationException(int dc) + : base($"User located on a different DC: {dc}.", dc) + { + } + } + + internal class NetworkMigrationException : DataCenterMigrationException + { + internal NetworkMigrationException(int dc) + : base($"Network located on a different DC: {dc}.", dc) + { + } + } + + +} diff --git a/TLSharp.Core/Network/MtProtoSender.cs b/TLSharp.Core/Network/MtProtoSender.cs index a0361a2..0745e49 100644 --- a/TLSharp.Core/Network/MtProtoSender.cs +++ b/TLSharp.Core/Network/MtProtoSender.cs @@ -18,11 +18,19 @@ namespace TLSharp.Core.Network { //private ulong sessionId = GenerateRandomUlong(); - private readonly TcpTransport _transport; - private readonly Session _session; + private static NLog.Logger logger = NLog.LogManager.GetLogger("MTProto"); + + private readonly uint UpdatesTooLongID = (uint) new TeleSharp.TL.TLUpdatesTooLong ().Constructor; + + private TcpTransport _transport; + private Session _session; public readonly List needConfirmation = new List(); + public delegate void HandleUpdates (TeleSharp.TL.TLAbsUpdates updates); + + public event HandleUpdates UpdatesEvent; + public MtProtoSender(TcpTransport transport, Session session) { _transport = transport; @@ -77,20 +85,26 @@ namespace TLSharp.Core.Network plaintextWriter.Write(packet.Length); plaintextWriter.Write(packet); - msgKey = Helpers.CalcMsgKey(plaintextPacket.GetBuffer()); - ciphertext = AES.EncryptAES(Helpers.CalcKey(_session.AuthKey.Data, msgKey, true), plaintextPacket.GetBuffer()); + var buffer = plaintextPacket.GetBuffer(); + logger.Debug("Send {0} {1:x8} {2}", request, request.Constructor, Sniffer.MessageOut(buffer)); + msgKey = Helpers.CalcMsgKey(buffer); + ciphertext = AES.EncryptAES(Helpers.CalcKey(_session.AuthKey.Data, msgKey, true), + plaintextPacket.GetBuffer()); } } + } - using (MemoryStream ciphertextPacket = makeMemory(8 + 16 + ciphertext.Length)) + private async Task Ack() + { + if (needConfirmation.Any()) { - using (BinaryWriter writer = new BinaryWriter(ciphertextPacket)) + var ackRequest = new AckRequest(needConfirmation); + using (var memory = new MemoryStream()) + using (var writer = new BinaryWriter(memory)) { - writer.Write(_session.AuthKey.Id); - writer.Write(msgKey); - writer.Write(ciphertext); - - await _transport.Send(ciphertextPacket.GetBuffer()); + ackRequest.SerializeBody(writer); + await Send(memory.ToArray(), ackRequest); + needConfirmation.Clear(); } } } @@ -112,6 +126,7 @@ namespace TLSharp.Core.Network AESKeyData keyData = Helpers.CalcKey(_session.AuthKey.Data, msgKey, false); byte[] plaintext = AES.DecryptAES(keyData, inputReader.ReadBytes((int)(inputStream.Length - inputStream.Position))); + logger.Debug(Sniffer.MessageIn(plaintext)); using (MemoryStream plaintextStream = new MemoryStream(plaintext)) using (BinaryReader plaintextReader = new BinaryReader(plaintextStream)) @@ -127,22 +142,35 @@ namespace TLSharp.Core.Network return new Tuple(message, remoteMessageId, remoteSequence); } - public async Task Receive(TeleSharp.TL.TLMethod request) + public async Task Receive (TeleSharp.TL.TLMethod request) { - while (!request.ConfirmReceived) + while (!request.ConfirmReceived) { var result = DecodeMessage((await _transport.Receive()).Body); - using (var messageStream = new MemoryStream(result.Item1, false)) - using (var messageReader = new BinaryReader(messageStream)) + using (var messageStream = new MemoryStream (result.Item1, false)) + using (var messageReader = new BinaryReader (messageStream)) { - processMessage(result.Item2, result.Item3, messageReader, request); + processMessage (result.Item2, result.Item3, messageReader, request); } } return null; } + public async Task Receive(int timeoutms) + { + var result = DecodeMessage ((await _transport.Receieve (timeoutms)).Body); + + using (var messageStream = new MemoryStream (result.Item1, false)) + using (var messageReader = new BinaryReader (messageStream)) + { + processMessage (result.Item2, result.Item3, messageReader, null); + } + + return null; + } + public async Task SendPingAsync() { var pingRequest = new PingRequest(); @@ -162,11 +190,14 @@ namespace TLSharp.Core.Network // TODO: check sessionid // TODO: check seqno + //logger.debug("processMessage: msg_id {0}, sequence {1}, data {2}", BitConverter.ToString(((MemoryStream)messageReader.BaseStream).GetBuffer(), (int) messageReader.BaseStream.Position, (int) (messageReader.BaseStream.Length - messageReader.BaseStream.Position)).Replace("-","").ToLower()); needConfirmation.Add(messageId); + Ack().Wait(); uint code = messageReader.ReadUInt32(); messageReader.BaseStream.Position -= 4; + logger.Info("Processing message {0:x8}", code); switch (code) { case 0x73f1f8dc: // container @@ -208,29 +239,60 @@ namespace TLSharp.Core.Network case 0x78d4dec1: case 0x725b04c3: case 0x74ae4240: - return HandleUpdate(messageId, sequence, messageReader); + case 0x11f1331c: + return HandleUpdate(code, sequence, messageReader, request); default: - //logger.debug("unknown message: {0}", code); + logger.Info("unhandled message"); return false; } } - private bool HandleUpdate(ulong messageId, int sequence, BinaryReader messageReader) + private bool HandleUpdate(uint code, int sequence, BinaryReader messageReader, TeleSharp.TL.TLMethod request) { - return false; - - /* try { - UpdatesEvent(TL.Parse(messageReader)); - return true; + var update = ParseUpdate (code, messageReader); + if (update != null && UpdatesEvent != null) + { + UpdatesEvent (update); + } + return true; } catch (Exception e) { - logger.warning("update processing exception: {0}", e); - return false; - } - */ + logger.Debug($"HandleUpdate failed: {e}"); + return false; + } + } + + private TeleSharp.TL.TLAbsUpdates ParseUpdate(uint code, BinaryReader messageReader) + { + switch (code) + { + case 0xe317af7e: + return DecodeUpdate(messageReader); + case 0x914fbf11: + return DecodeUpdate (messageReader); + case 0x16812688: + return DecodeUpdate (messageReader); + case 0x78d4dec1: + return DecodeUpdate (messageReader); + case 0x725b04c3: + return DecodeUpdate (messageReader); + case 0x74ae4240: + return DecodeUpdate (messageReader); + case 0x11f1331c: + return DecodeUpdate (messageReader); + default: + return null; + } + } + + private TeleSharp.TL.TLAbsUpdates DecodeUpdate(BinaryReader messageReader) where T: TeleSharp.TL.TLAbsUpdates + { + var ms = messageReader.BaseStream as MemoryStream; + var update = (T) TeleSharp.TL.ObjectUtils.DeserializeObject (messageReader); + return update; } private bool HandleGzipPacked(ulong messageId, int sequence, BinaryReader messageReader, TeleSharp.TL.TLMethod request) @@ -275,6 +337,7 @@ namespace TLSharp.Core.Network { // rpc_error int errorCode = messageReader.ReadInt32(); string errorMessage = Serializers.String.read(messageReader); + Console.Error.WriteLine($"ERROR: {errorMessage} - {errorCode}"); if (errorMessage.StartsWith("FLOOD_WAIT_")) { @@ -392,7 +455,7 @@ namespace TLSharp.Core.Network throw new InvalidOperationException("invalid container"); } - throw new NotImplementedException("This should never happens"); + throw new NotImplementedException("This should never happen!"); /* logger.debug("bad_msg_notification: msgid {0}, seq {1}, errorcode {2}", requestId, requestSequence, errorCode); @@ -508,6 +571,10 @@ namespace TLSharp.Core.Network messageReader.BaseStream.Position = beginPosition + innerLength; } } + catch (InvalidOperationException e) + { + throw e; + } catch (Exception e) { // logger.error("failed to process message in container: {0}", e); @@ -523,61 +590,4 @@ namespace TLSharp.Core.Network return new MemoryStream(new byte[len], 0, len, true, true); } } - - public class FloodException : Exception - { - public TimeSpan TimeToWait { get; private set; } - - internal FloodException(TimeSpan timeToWait) - : base($"Flood prevention. Telegram now requires your program to do requests again only after {timeToWait.TotalSeconds} seconds have passed ({nameof(TimeToWait)} property)." + - " If you think the culprit of this problem may lie in TLSharp's implementation, open a Github issue please.") - { - TimeToWait = timeToWait; - } - } - - internal abstract class DataCenterMigrationException : Exception - { - internal int DC { get; private set; } - - private const string REPORT_MESSAGE = - " See: https://github.com/sochix/TLSharp#i-get-a-xxxmigrationexception-or-a-migrate_x-error"; - - protected DataCenterMigrationException(string msg, int dc) : base (msg + REPORT_MESSAGE) - { - DC = dc; - } - } - - internal class PhoneMigrationException : DataCenterMigrationException - { - internal PhoneMigrationException(int dc) - : base ($"Phone number registered to a different DC: {dc}.", dc) - { - } - } - - internal class FileMigrationException : DataCenterMigrationException - { - internal FileMigrationException(int dc) - : base ($"File located on a different DC: {dc}.", dc) - { - } - } - - internal class UserMigrationException : DataCenterMigrationException - { - internal UserMigrationException(int dc) - : base($"User located on a different DC: {dc}.", dc) - { - } - } - - internal class NetworkMigrationException : DataCenterMigrationException - { - internal NetworkMigrationException(int dc) - : base($"Network located on a different DC: {dc}.", dc) - { - } - } } diff --git a/TLSharp.Core/Network/Sniffer.cs b/TLSharp.Core/Network/Sniffer.cs new file mode 100644 index 0000000..a1033bc --- /dev/null +++ b/TLSharp.Core/Network/Sniffer.cs @@ -0,0 +1,25 @@ +using System; +using System.Text; + +namespace TLSharp.Core.Network +{ + public static class Sniffer + { + public static string MessageOut(byte[] data) + { + return WriteMessage(new StringBuilder("[OUT]:"), data); + } + + public static string MessageIn(byte[] data) + { + return WriteMessage(new StringBuilder("[IN]:"), data); + } + + private static string WriteMessage(StringBuilder log, byte[] data) + { + foreach (var b in data) + log.AppendFormat(" {0:x2}", b); + return log.ToString(); + } + } +} diff --git a/TLSharp.Core/Network/TcpTransport.cs b/TLSharp.Core/Network/TcpTransport.cs index d501751..8ff2df7 100644 --- a/TLSharp.Core/Network/TcpTransport.cs +++ b/TLSharp.Core/Network/TcpTransport.cs @@ -1,6 +1,7 @@ using System; using System.Net; using System.Net.Sockets; +using System.Threading; using System.Threading.Tasks; namespace TLSharp.Core.Network @@ -9,9 +10,11 @@ namespace TLSharp.Core.Network public class TcpTransport : IDisposable { - private readonly TcpClient _tcpClient; + private static NLog.Logger logger = TelegramClient.logger; + private readonly TcpClient _tcpClient; private readonly NetworkStream _stream; private int sendCounter = 0; + private CancellationTokenSource tokenSource = new CancellationTokenSource(); public TcpTransport(string address, int port, TcpClientConnectionHandler handler = null) { @@ -45,10 +48,14 @@ namespace TLSharp.Core.Network public async Task Receive() { + logger.Trace($"Wait for answer {_tcpClient.Available} ..."); + var stream = _tcpClient.GetStream(); + var packetLengthBytes = new byte[4]; if (await _stream.ReadAsync(packetLengthBytes, 0, 4) != 4) throw new InvalidOperationException("Couldn't read the packet length"); int packetLength = BitConverter.ToInt32(packetLengthBytes, 0); + logger.Debug("[IN] Packet length: {0}", packetLength); var seqBytes = new byte[4]; if (await _stream.ReadAsync(seqBytes, 0, 4) != 4) @@ -91,6 +98,72 @@ namespace TLSharp.Core.Network return new TcpMessage(seq, body); } + public async Task Receieve(int timeoutms) + { + logger.Trace($"Wait for event {_tcpClient.Available} ..."); + var stream = _tcpClient.GetStream(); + + var packetLengthBytes = new byte[4]; + var token = tokenSource.Token; + stream.ReadTimeout = timeoutms; + int bytes = 0; + try + { + bytes = stream.Read(packetLengthBytes, 0, 4); + } catch (System.IO.IOException io) + { + var socketError = io.InnerException as SocketException; + if (socketError != null && socketError.SocketErrorCode == SocketError.TimedOut) + throw new OperationCanceledException(); + throw io; + } + if (bytes != 4) + throw new InvalidOperationException("Couldn't read the packet length"); + int packetLength = BitConverter.ToInt32(packetLengthBytes, 0); + logger.Debug("[IN]* Packet length: {0}", packetLength); + + var seqBytes = new byte[4]; + if (await _stream.ReadAsync(seqBytes, 0, 4) != 4) + throw new InvalidOperationException("Couldn't read the sequence"); + int seq = BitConverter.ToInt32(seqBytes, 0); + logger.Debug("[IN]* sequence: {0}", seq); + + int readBytes = 0; + var body = new byte[packetLength - 12]; + int neededToRead = packetLength - 12; + + do + { + var bodyByte = new byte[packetLength - 12]; + var availableBytes = await _stream.ReadAsync(bodyByte, 0, neededToRead); + neededToRead -= availableBytes; + Buffer.BlockCopy(bodyByte, 0, body, readBytes, availableBytes); + readBytes += availableBytes; + } + while (readBytes != packetLength - 12); + + var crcBytes = new byte[4]; + if (await _stream.ReadAsync(crcBytes, 0, 4) != 4) + throw new InvalidOperationException("Couldn't read the crc"); + int checksum = BitConverter.ToInt32(crcBytes, 0); + + byte[] rv = new byte[packetLengthBytes.Length + seqBytes.Length + body.Length]; + + Buffer.BlockCopy(packetLengthBytes, 0, rv, 0, packetLengthBytes.Length); + Buffer.BlockCopy(seqBytes, 0, rv, packetLengthBytes.Length, seqBytes.Length); + Buffer.BlockCopy(body, 0, rv, packetLengthBytes.Length + seqBytes.Length, body.Length); + var crc32 = new Ionic.Crc.CRC32(); + crc32.SlurpBlock(rv, 0, rv.Length); + var validChecksum = crc32.Crc32Result; + + if (checksum != validChecksum) + { + throw new InvalidOperationException("invalid checksum! skip"); + } + + return new TcpMessage(seq, body); + } + public bool IsConnected { get @@ -99,7 +172,6 @@ namespace TLSharp.Core.Network } } - public void Dispose() { if (_tcpClient.Connected) diff --git a/TLSharp.Core/TLSharp.Core.csproj b/TLSharp.Core/TLSharp.Core.csproj index 74efe73..4237e50 100644 --- a/TLSharp.Core/TLSharp.Core.csproj +++ b/TLSharp.Core/TLSharp.Core.csproj @@ -42,6 +42,10 @@ + + ..\packages\NLog.4.4.12\lib\net45\NLog.dll + True + @@ -68,6 +72,8 @@ + + diff --git a/TLSharp.Core/TelegramClient.cs b/TLSharp.Core/TelegramClient.cs index f7e0258..d0fe23f 100644 --- a/TLSharp.Core/TelegramClient.cs +++ b/TLSharp.Core/TelegramClient.cs @@ -1,8 +1,9 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Security.Cryptography; using System.Text; +using System.Threading; using System.Threading.Tasks; using TeleSharp.TL; using TeleSharp.TL.Account; @@ -21,7 +22,9 @@ namespace TLSharp.Core { public class TelegramClient : IDisposable { + internal static NLog.Logger logger = NLog.LogManager.GetLogger("TelegramClient"); private MtProtoSender _sender; + private AuthKey _key; private TcpTransport _transport; private string _apiHash = ""; private int _apiId = 0; @@ -29,6 +32,16 @@ namespace TLSharp.Core private List dcOptions; private TcpClientConnectionHandler _handler; + private bool _looping = true; + public volatile bool AllowEvents = false; + + public delegate void UpdatesEvent (TelegramClient source, TLAbsUpdates updates); + public delegate void ClientEvent(TelegramClient source); + + public event UpdatesEvent Updates; + public event ClientEvent ScheduledTasks; + public event ClientEvent IdleTasks; + public Session Session { get { return _session; } @@ -62,6 +75,7 @@ namespace TLSharp.Core _session.TimeOffset = result.TimeOffset; } + _sender.UpdatesEvent += _sender_UpdatesEvent; _sender = new MtProtoSender(_transport, _session); //set-up layer @@ -110,6 +124,50 @@ namespace TLSharp.Core } } + public void Close() + { + _looping = false; + } + + public async Task MainLoopAsync(int timeslicems) + { + logger.Trace("Entered loop"); + var lastPing = DateTime.UtcNow; + await SendPingAsync(); + while (_looping) + { + try + { + await WaitEventAsync(timeslicems); + } catch (OperationCanceledException) + { + logger.Trace("Timeout"); + } + finally + { + var now = DateTime.UtcNow; + if ((now - lastPing).TotalSeconds >= 30) + { + await SendPingAsync(); + lastPing = now; + } + if (ScheduledTasks != null) + { + logger.Trace("Running idle tasks"); + ScheduledTasks.Invoke(this); + ScheduledTasks = null; + } + IdleTasks?.Invoke(this); + } + } + } + + private void _sender_UpdatesEvent (TLAbsUpdates updates) + { + if (AllowEvents && Updates != null) + Updates(this, updates); + } + private async Task RequestWithDcMigration(TLMethod request) { if (_sender == null) @@ -139,6 +197,11 @@ namespace TLSharp.Core } } + public async Task WaitEventAsync(int timeoutms) + { + await _sender.Receive (timeoutms); + } + public bool IsUserAuthorized() { return _session.TLUser != null; diff --git a/TLSharp.Core/packages.config b/TLSharp.Core/packages.config index 4966d6d..fd050b8 100644 --- a/TLSharp.Core/packages.config +++ b/TLSharp.Core/packages.config @@ -2,4 +2,5 @@ + \ No newline at end of file