diff --git a/README.md b/README.md index 16c95d69..6c55b392 100644 --- a/README.md +++ b/README.md @@ -137,6 +137,220 @@ 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; + +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; + + client = new TelegramClient(APIId, APIHash); + // subscribe an event to receive live messages + client.Updates += ClientUpdates; + 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 chatParticipant) + { + Console.WriteLine($"\t{chatParticipant.UserId}"); + } + else if (p is TLChatParticipantAdmin chatParticipantAdmin) + { + Console.WriteLine($"\t{chatParticipantAdmin.UserId}**"); + } + else if (p is TLChatParticipantCreator chatParticipantCreator) + { + Console.WriteLine($"\t{chatParticipantCreator.UserId}**"); + } + } + + var peer = new TLInputPeerChat() { ChatId = chat.Id }; + var msg = await client.GetHistoryAsync(peer, 0, 0, 0); + Console.WriteLine(msg); + if (msg is TLMessages messages) + { + foreach (var message in messages.Messages) + { + if (message is TLMessage m1) + { + Console.WriteLine($"\t\t{m1.Id} {m1.Message}"); + } + else if (message is TLMessageService msgService) + { + Console.WriteLine($"\t\t{msgService.Id} {msgService.Action}"); + } + } + } + else if (msg is TLMessagesSlice messagesSlice) + { + bool done = false; + int total = 0; + while (!done) + { + foreach (var m1 in messagesSlice.Messages) + { + if (m1 is TLMessage message) + { + Console.WriteLine($"\t\t{message.Id} {message.Message}"); + ++total; + } + else if (m1 is TLMessageService messageService) + { + Console.WriteLine($"\t\t{messageService.Id} {messageService.Action}"); + ++total; + done = messageService.Action is TLMessageActionChatCreate; + } + } + msg = await client.GetHistoryAsync(peer, total, 0, 0); + } + } + } + + // -- Wait in a loop to handle incoming updates. No need to poll. + while(true) + { + await client.WaitEventAsync(TimeSpan.FromSeconds(1)); + } + } + + private void ClientUpdates(TelegramClient client, TLAbsUpdates updates) + { + Console.WriteLine($"Got update: {updates}"); + if (updates is TLUpdateShort updateShort) + { + Console.WriteLine($"Short: {updateShort.Update}"); + if (updateShort.Update is TLUpdateUserStatus status) + { + Console.WriteLine($"User {status.UserId} is {status.Status}"); + if (status.Status is TLUserStatusOnline) + { + var peer = new TLInputPeerUser() { UserId = status.UserId }; + client.SendMessageAsync(peer, "Você está online.").Wait(); + } + } + } + else if (updates is TLUpdateShortMessage message) + { + Console.WriteLine($"Message: {message.Message}"); + MarkMessageRead(client, new TLInputPeerUser() { UserId = message.UserId }, message.Id); + } + else if (updates is TLUpdateShortChatMessage shortChatMessage) + { + Console.WriteLine($"Chat Message: {shortChatMessage.Message}"); + MarkMessageRead(client, new TLInputPeerChat() { ChatId = shortChatMessage.ChatId }, shortChatMessage.Id); + } + else if (updates is TLUpdates allUpdates) + { + foreach (var update in allUpdates.Updates) + { + Console.WriteLine($"\t{update}"); + if (update is TLUpdateNewChannelMessage metaMessage) + { + var channelMsg = metaMessage.Message as TLMessage; + Console.WriteLine($"Channel message: {channelMsg.Message}"); + var channel = allUpdates.Chats[0] as TLChannel; + MarkMessageRead(client, + new TLInputPeerChannel() { ChannelId = channel.Id, AccessHash = channel.AccessHash.Value }, + channelMsg.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) + { + try + { + var request = new TLRequestReadHistory(); + request.MaxId = id; + request.Peer = peer; + client.SendRequestAsync(request).Wait(); + } + catch (InvalidOperationException e) + { + Console.WriteLine($"MarkMessageRead Error: {e.Message}"); + } + } + } +} +``` + # 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/MtProtoSender.cs b/TLSharp.Core/Network/MtProtoSender.cs index 91e44574..37073ec3 100644 --- a/TLSharp.Core/Network/MtProtoSender.cs +++ b/TLSharp.Core/Network/MtProtoSender.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.IO.Compression; using System.Linq; @@ -24,8 +23,14 @@ public class MtProtoSender private readonly TcpTransport transport; private readonly Session session; + private readonly uint UpdatesTooLongID = (uint) new TLUpdatesTooLong().Constructor; + public readonly List needConfirmation = new List(); + public delegate void HandleUpdates (TLAbsUpdates updates); + + public event HandleUpdates UpdatesEvent; + public MtProtoSender(TcpTransport transport, Session session) { this.transport = transport; @@ -54,7 +59,6 @@ private int GenerateSequence(bool confirmed) } } - using (var memory = new MemoryStream()) using (var writer = new BinaryWriter(memory)) { @@ -84,11 +88,12 @@ private int GenerateSequence(bool confirmed) 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(); + msgKey = Helpers.CalcMsgKey(buffer); + ciphertext = AES.EncryptAES(Helpers.CalcKey(session.AuthKey.Data, msgKey, true), + plaintextPacket.GetBuffer()); } } - using (MemoryStream ciphertextPacket = makeMemory(8 + 16 + ciphertext.Length)) { using (BinaryWriter writer = new BinaryWriter(ciphertextPacket)) @@ -102,6 +107,23 @@ private int GenerateSequence(bool confirmed) } } + private async Task Ack(CancellationToken token = default(CancellationToken)) + { + token.ThrowIfCancellationRequested(); + + if (needConfirmation.Any()) + { + var ackRequest = new AckRequest(needConfirmation); + using (var memory = new MemoryStream()) + using (var writer = new BinaryWriter(memory)) + { + ackRequest.SerializeBody(writer); + await Send(memory.ToArray(), ackRequest); + needConfirmation.Clear(); + } + } + } + private Tuple DecodeMessage(byte[] body) { byte[] message; @@ -141,9 +163,9 @@ private Tuple DecodeMessage(byte[] body) var result = DecodeMessage((await transport.Receive(token).ConfigureAwait(false)).Body); using (var messageStream = new MemoryStream(result.Item1, false)) - using (var messageReader = new BinaryReader(messageStream)) + using (var messageReader = new BinaryReader(messageStream)) { - processMessage(result.Item2, result.Item3, messageReader, request, token); + await ProcessMessageAsync(result.Item2, result.Item3, messageReader, request, token); } token.ThrowIfCancellationRequested(); @@ -152,6 +174,21 @@ private Tuple DecodeMessage(byte[] body) return null; } + public async Task Receive(TimeSpan timeToWait, CancellationToken token = default(CancellationToken)) + { + var result = DecodeMessage((await transport.Receive(timeToWait)).Body); + + using (var messageStream = new MemoryStream(result.Item1, false)) + using (var messageReader = new BinaryReader(messageStream)) + { + await ProcessMessageAsync(result.Item2, result.Item3, messageReader, null); + } + + token.ThrowIfCancellationRequested(); + + return null; + } + public async Task SendPingAsync(CancellationToken token = default(CancellationToken)) { token.ThrowIfCancellationRequested(); @@ -167,7 +204,7 @@ private Tuple DecodeMessage(byte[] body) await Receive(pingRequest, token).ConfigureAwait(false); } - private bool processMessage(ulong messageId, int sequence, BinaryReader messageReader, TLMethod request, CancellationToken token = default(CancellationToken)) + private async Task ProcessMessageAsync(ulong messageId, int sequence, BinaryReader messageReader, TeleSharp.TL.TLMethod request, CancellationToken token = default(CancellationToken)) { token.ThrowIfCancellationRequested(); @@ -175,8 +212,10 @@ private Tuple DecodeMessage(byte[] body) // 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); + await Ack(token); uint code = messageReader.ReadUInt32(); messageReader.BaseStream.Position -= 4; @@ -184,7 +223,7 @@ private Tuple DecodeMessage(byte[] body) { case 0x73f1f8dc: // container //logger.debug("MSG container"); - return HandleContainer(messageId, sequence, messageReader, request, token); + return await HandleContainerAsync(messageId, sequence, messageReader, request, token); case 0x7abe77ec: // ping //logger.debug("MSG ping"); return HandlePing(messageId, sequence, messageReader); @@ -202,7 +241,7 @@ private Tuple DecodeMessage(byte[] body) return HandleMsgsAck(messageId, sequence, messageReader); case 0xedab447b: // bad_server_salt //logger.debug("MSG bad_server_salt"); - return HandleBadServerSalt(messageId, sequence, messageReader, request, token); + return await HandleBadServerSaltAsync(messageId, sequence, messageReader, request, token); case 0xa7eff811: // bad_msg_notification //logger.debug("MSG bad_msg_notification"); return HandleBadMsgNotification(messageId, sequence, messageReader); @@ -214,39 +253,61 @@ private Tuple DecodeMessage(byte[] body) return HandleRpcResult(messageId, sequence, messageReader, request); case 0x3072cfa1: // gzip_packed //logger.debug("MSG gzip_packed"); - return HandleGzipPacked(messageId, sequence, messageReader, request, token); + return await HandleGzipPackedAsync(messageId, sequence, messageReader, request, token); case 0xe317af7e: case 0xd3f45784: case 0x2b2fbd4e: 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); return false; } } - private bool HandleUpdate(ulong messageId, int sequence, BinaryReader messageReader) + private bool HandleUpdate(uint code, int sequence, BinaryReader messageReader, TLMethod request) { - return false; + var update = ParseUpdate(code, messageReader); + if (update != null && UpdatesEvent != null) + { + UpdatesEvent(update); + } + return true; + } - /* - try - { - UpdatesEvent(TL.Parse(messageReader)); - return true; - } - catch (Exception e) - { - logger.warning("update processing exception: {0}", e); - return false; - } - */ + private 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 TLAbsUpdates DecodeUpdate(BinaryReader messageReader) where T : TLAbsUpdates + { + var ms = messageReader.BaseStream as MemoryStream; + var update = (T)ObjectUtils.DeserializeObject(messageReader); + return update; } - private bool HandleGzipPacked(ulong messageId, int sequence, BinaryReader messageReader, TLMethod request, CancellationToken token = default(CancellationToken)) + private async Task HandleGzipPackedAsync(ulong messageId, int sequence, BinaryReader messageReader, TeleSharp.TL.TLMethod request, CancellationToken token = default(CancellationToken)) { token.ThrowIfCancellationRequested(); @@ -263,7 +324,7 @@ private bool HandleUpdate(ulong messageId, int sequence, BinaryReader messageRea } using (BinaryReader compressedReader = new BinaryReader(ms)) { - processMessage(messageId, sequence, compressedReader, request, token); + await ProcessMessageAsync(messageId, sequence, compressedReader, request, token); } } @@ -299,6 +360,7 @@ private bool HandleRpcResult(ulong messageId, int sequence, BinaryReader message { // rpc_error int errorCode = messageReader.ReadInt32(); string errorMessage = Serializers.String.Read(messageReader); + Console.Error.WriteLine($"ERROR: {errorMessage} - {errorCode}"); if (errorMessage.StartsWith("FLOOD_WAIT_")) { @@ -409,7 +471,7 @@ private bool HandleBadMsgNotification(ulong messageId, int sequence, BinaryReade 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); @@ -429,7 +491,7 @@ private bool HandleBadMsgNotification(ulong messageId, int sequence, BinaryReade return true; } - private bool HandleBadServerSalt(ulong messageId, int sequence, BinaryReader messageReader, TLMethod request, CancellationToken token = default(CancellationToken)) + private async Task HandleBadServerSaltAsync(ulong messageId, int sequence, BinaryReader messageReader, TeleSharp.TL.TLMethod request, CancellationToken token = default(CancellationToken)) { token.ThrowIfCancellationRequested(); @@ -444,7 +506,7 @@ private bool HandleBadMsgNotification(ulong messageId, int sequence, BinaryReade session.Salt = newSalt; //resend - Send(request, token); + await Send(request, token); /* if(!runningRequests.ContainsKey(badMsgId)) { logger.debug("bad server salt on unknown message"); @@ -510,7 +572,7 @@ private bool HandlePing(ulong messageId, int sequence, BinaryReader messageReade return false; } - private bool HandleContainer(ulong messageId, int sequence, BinaryReader messageReader, TLMethod request, CancellationToken token = default(CancellationToken)) + private async Task HandleContainerAsync(ulong messageId, int sequence, BinaryReader messageReader, TeleSharp.TL.TLMethod request, CancellationToken token = default(CancellationToken)) { token.ThrowIfCancellationRequested(); @@ -524,11 +586,16 @@ private bool HandlePing(ulong messageId, int sequence, BinaryReader messageReade long beginPosition = messageReader.BaseStream.Position; try { - if (!processMessage(innerMessageId, sequence, messageReader, request, token)) + var processedMessage = await ProcessMessageAsync(innerMessageId, sequence, messageReader, request, token); + if (!processedMessage) { messageReader.BaseStream.Position = beginPosition + innerLength; } } + catch (InvalidOperationException e) + { + throw; + } catch (Exception e) { // logger.error("failed to process message in container: {0}", e); diff --git a/TLSharp.Core/Network/Sniffer.cs b/TLSharp.Core/Network/Sniffer.cs new file mode 100644 index 00000000..a1033bc2 --- /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 cfc79561..0433df30 100644 --- a/TLSharp.Core/Network/TcpTransport.cs +++ b/TLSharp.Core/Network/TcpTransport.cs @@ -15,6 +15,7 @@ public class TcpTransport : IDisposable 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) { @@ -54,11 +55,76 @@ public TcpTransport(string address, int port, TcpClientConnectionHandler handler public async Task Receive(CancellationToken token = default(CancellationToken)) { + var stream = tcpClient.GetStream(); + var packetLengthBytes = new byte[4]; if (await stream.ReadAsync(packetLengthBytes, 0, 4, token).ConfigureAwait(false) != 4) throw new InvalidOperationException("Couldn't read the packet length"); int packetLength = BitConverter.ToInt32(packetLengthBytes, 0); + 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); + + 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 Crc32(); + var computedChecksum = crc32.ComputeHash(rv).Reverse(); + + if (!crcBytes.SequenceEqual(computedChecksum)) + { + throw new InvalidOperationException("invalid checksum! skip"); + } + + return new TcpMessage(seq, body); + } + + public async Task Receive(TimeSpan timeToWait) + { + var stream = tcpClient.GetStream(); + + var packetLengthBytes = new byte[4]; + var token = tokenSource.Token; + stream.ReadTimeout = (int)timeToWait.TotalMilliseconds; + 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; + } + if (bytes != 4) + throw new InvalidOperationException("Couldn't read the packet length"); + int packetLength = BitConverter.ToInt32(packetLengthBytes, 0); + var seqBytes = new byte[4]; if (await stream.ReadAsync(seqBytes, 0, 4, token).ConfigureAwait(false) != 4) throw new InvalidOperationException("Couldn't read the sequence"); @@ -106,7 +172,6 @@ public bool IsConnected } } - public void Dispose() { if (tcpClient.Connected) diff --git a/TLSharp.Core/TLSharp.Core.csproj b/TLSharp.Core/TLSharp.Core.csproj index 9924ab2e..62bdc6cc 100644 --- a/TLSharp.Core/TLSharp.Core.csproj +++ b/TLSharp.Core/TLSharp.Core.csproj @@ -75,6 +75,7 @@ + diff --git a/TLSharp.Core/TelegramClient.cs b/TLSharp.Core/TelegramClient.cs index e5b0819f..666f4304 100644 --- a/TLSharp.Core/TelegramClient.cs +++ b/TLSharp.Core/TelegramClient.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Security.Cryptography; @@ -25,6 +25,7 @@ namespace TLSharp.Core public class TelegramClient : IDisposable { private MtProtoSender sender; + private AuthKey key; private TcpTransport transport; private string apiHash = String.Empty; private int apiId = 0; @@ -33,6 +34,15 @@ public class TelegramClient : IDisposable private TcpClientConnectionHandler handler; private DataCenterIPVersion dcIpVersion; + private bool looping = true; + + 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; } @@ -80,6 +90,7 @@ public TelegramClient(int apiId, string apiHash, } sender = new MtProtoSender(transport, session); + sender.UpdatesEvent += SenderUpdatesEvent; //set-up layer var config = new TLRequestGetConfig(); @@ -149,6 +160,50 @@ public TelegramClient(int apiId, string apiHash, } } + public void Close() + { + looping = false; + } + + public async Task MainLoopAsync(TimeSpan timeToWait, CancellationToken token = default(CancellationToken)) + { + var lastPing = DateTime.UtcNow; + await SendPingAsync(); + while (looping) + { + try + { + await WaitEventAsync(timeToWait, token); + } + catch (OperationCanceledException) + { + // Handle timeout, no problem + } + finally + { + var now = DateTime.UtcNow; + if ((now - lastPing).TotalSeconds >= 30) + { + await SendPingAsync(); + lastPing = now; + } + if (ScheduledTasks != null) + { + ScheduledTasks.Invoke(this); + ScheduledTasks = null; + } + IdleTasks?.Invoke(this); + } + + token.ThrowIfCancellationRequested(); + } + } + + private void SenderUpdatesEvent (TLAbsUpdates updates) + { + Updates?.Invoke(this, updates); + } + private async Task RequestWithDcMigration(TLMethod request, CancellationToken token = default(CancellationToken)) { if (sender == null) @@ -178,6 +233,11 @@ public TelegramClient(int apiId, string apiHash, } } + public async Task WaitEventAsync(TimeSpan timeToWait, CancellationToken token = default(CancellationToken)) + { + await sender.Receive (timeToWait, token); + } + public bool IsUserAuthorized() { return session.TLUser != null; diff --git a/TLSharp.Tests.NUnit/Test.cs b/TLSharp.Tests.NUnit/Test.cs index 40e54ac6..3696b07e 100644 --- a/TLSharp.Tests.NUnit/Test.cs +++ b/TLSharp.Tests.NUnit/Test.cs @@ -81,5 +81,11 @@ public override async Task SendMessageByUserNameTest() { await base.SendMessageByUserNameTest(); } + + [Test] + public override async Task GetUpdatesForUser() + { + await base.GetUpdatesForUser(); + } } } diff --git a/TLSharp.Tests.VS/TLSharpTestsVs.cs b/TLSharp.Tests.VS/TLSharpTestsVs.cs index 8f6c58dc..ccb7ea7a 100644 --- a/TLSharp.Tests.VS/TLSharpTestsVs.cs +++ b/TLSharp.Tests.VS/TLSharpTestsVs.cs @@ -79,5 +79,12 @@ public override async Task SendMessageByUserNameTest() { await base.SendMessageByUserNameTest(); } + + [TestMethod] + public override async Task GetUpdatesForUser() + { + await base.GetUpdatesForUser(); + } + } } diff --git a/TLSharp.Tests/TLSharpTests.cs b/TLSharp.Tests/TLSharpTests.cs index 6acde729..8f2cfe50 100644 --- a/TLSharp.Tests/TLSharpTests.cs +++ b/TLSharp.Tests/TLSharpTests.cs @@ -1,5 +1,6 @@  using System; +using System.Collections.Generic; using System.Configuration; using System.Diagnostics; using System.IO; @@ -11,7 +12,6 @@ using TeleSharp.TL.Messages; using TLSharp.Core; using TLSharp.Core.Exceptions; -using TLSharp.Core.Network; using TLSharp.Core.Network.Exceptions; using TLSharp.Core.Utils; @@ -269,7 +269,7 @@ public virtual async Task DownloadFileFromContactTest() Version = document.Version }, document.Size); - + Assert.IsTrue(resFile.Bytes.Length > 0); } @@ -284,7 +284,7 @@ public virtual async Task DownloadFileFromWrongLocationTest() var user = result.Users .OfType() .FirstOrDefault(x => x.Id == 5880094); - + var photo = ((TLUserProfilePhoto)user.Photo); var photoLocation = (TLFileLocation) photo.PhotoBig; @@ -377,5 +377,72 @@ public virtual async Task SendMessageByUserNameTest() Thread.Sleep(3000); await client.SendMessageAsync(new TLInputPeerUser() { UserId = user.Id }, "TEST"); } + + public virtual async Task GetUpdatesForUser() + { + IList newMsgs = new List(); + TLUser user = null; + var updateMsg = "Send yourself an UPDATE_1 message to trigger update during loop"; + + var client = NewClient(); + await client.ConnectAsync(); + + if (client.IsUserAuthorized()) + user = client.Session.TLUser; + + else + { + var hash = await client.SendCodeRequestAsync(NumberToAuthenticate); + var code = CodeToAuthenticate; // you can change code in debugger too + if (string.IsNullOrWhiteSpace(code)) + { + throw new Exception("CodeToAuthenticate is empty in the app.config file, fill it with the code you just got now by SMS/Telegram"); + } + + try + { + user = await client.MakeAuthAsync(NumberToAuthenticate, hash, code); + } + catch (CloudPasswordNeededException) + { + var passwordSetting = await client.GetPasswordSetting(); + var password = PasswordToAuthenticate; + user = await client.MakeAuthWithPasswordAsync(passwordSetting, password); + } + catch (InvalidPhoneCodeException ex) + { + throw new Exception("CodeToAuthenticate is wrong in the app.config file, fill it with the code you just got now by SMS/Telegram", ex); + } + } + + // Things to note:- If the updates are not getting triggered, please re-authenticate the user + // Would trigger the updates on a seperate thread if possible + + client.Updates += (TelegramClient tclient, TLAbsUpdates updates) => + { + if (updates is TLUpdates) + { + var allUpdates = updates as TLUpdates; + + foreach (var update in allUpdates.Updates) + { + if (update is TLUpdateNewMessage) + { + var metaMsg = update as TLUpdateNewMessage; + var msg = metaMsg.Message as TLMessage; + newMsgs.Add(msg); + } + } + } + }; + + Console.WriteLine(updateMsg); + Debug.WriteLine(updateMsg); + + await client.MainLoopAsync(new TimeSpan(0, 0, 1)); + + Assert.IsTrue(newMsgs.Count == 1); + Assert.IsTrue(newMsgs.First().Message.Equals("UPDATE_1")); + } } -} +} \ No newline at end of file