diff --git a/README.md b/README.md
index 6605bbc6..45145c4f 100644
--- a/README.md
+++ b/README.md
@@ -1,261 +1,258 @@
TLSharp
-------------------------------
-
-[](https://gitter.im/TLSharp/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
-[](https://ci.appveyor.com/project/sochix/tlsharp)
-[](https://badge.fury.io/nu/TLSharp)
-
+_Unofficial_ Telegram (http://telegram.org) client library implemented in C#. Please refer to (https://github.com/sochix/TLSharp) for the original version and further documentation.
-_Unofficial_ Telegram (http://telegram.org) client library implemented in C#. Latest TL scheme supported, thanks to Afshin Arani
-
-It's a perfect fit for any developer who would like to send data directly to Telegram users or write own custom Telegram client.
-
-:star2: If you :heart: library, please star it! :star2:
-
-# News
-* **JavaScript Telegram Client**
-
- Hi everyone! I want to create JavaScript client for Telegram, it will have next features:
-
- * send & receieve messages from users/groups/channels
- * easy installation with npm or yarn
- * latest Telegram Schema
- * examples and documentation
-
- If you like this idea, please leave your email [here](http://eepurl.com/cBXX8n).
-
-* **TLSharp GUI**
-
- If you have difficulties with console or writing code, you can try [Telegram Tools](https://github.com/sochix/telegram-tools). It's a GUI for TLSharp.
-
-# Table of contents
-
-- [How do I add this to my project?](#how-do-i-add-this-to-my-project)
-- [Dependencies](#dependencies)
-- [Starter Guide](#starter-guide)
- - [Quick configuration](#quick-configuration)
- - [First requests](#first-requests)
- - [Working with files](#working-with-files)
-- [Available Methods](#available-methods)
-- [Contributing](#contributing)
-- [FAQ](#faq)
-- [Donations](#donations)
-- [License](#license)
-
-# How do I add this to my project?
-
-Install via NuGet
-
-```
- > Install-Package TLSharp
-```
-
-or build from source
-
-1. Clone TLSharp from GitHub
-1. Compile source with VS2015 or MonoDevelop
-1. Add reference to ```TLSharp.Core.dll``` to your awesome project.
-
-# Dependencies
-
-TLSharp has a few dependenices, most of functionality implemented from scratch.
-All dependencies listed in [package.conf file](https://github.com/sochix/TLSharp/blob/master/TLSharp.Core/packages.config).
-
-# Starter Guide
-
-## Quick Configuration
-Telegram API isn't that easy to start. You need to do some configuration first.
-
-1. Create a [developer account](https://my.telegram.org/) in Telegram.
-1. Goto [API development tools](https://my.telegram.org/apps) and copy **API_ID** and **API_HASH** from your account. You'll need it later.
-
-## First requests
-To start work, create an instance of TelegramClient and establish connection
-
-```csharp
- var client = new TelegramClient(apiId, apiHash);
- await client.ConnectAsync();
-```
-Now you can work with Telegram API, but ->
-> Only a small portion of the API methods are available to unauthorized users. ([full description](https://core.telegram.org/api/auth))
-
-For authentication you need to run following code
-```csharp
- var hash = await client.SendCodeRequestAsync("");
- var code = ""; // you can change code in debugger
-
- var user = await client.MakeAuthAsync("", hash, code);
-```
-
-Full code you can see at [AuthUser test](https://github.com/sochix/TLSharp/blob/master/TLSharp.Tests/TLSharpTests.cs#L70)
-
-When user is authenticated, TLSharp creates special file called _session.dat_. In this file TLSharp store all information needed for user session. So you need to authenticate user every time the _session.dat_ file is corrupted or removed.
-
-You can call any method on authenticated user. For example, let's send message to a friend by his phone number:
-
-```csharp
- //get available contacts
- var result = await client.GetContactsAsync();
-
- //find recipient in contacts
- var user = result.Users.lists
- .Where(x => x.GetType() == typeof (TLUser))
- .Cast()
- .FirstOrDefault(x => x.phone == "");
-
- //send message
- await client.SendMessageAsync(new TLInputPeerUser() {user_id = user.id}, "OUR_MESSAGE");
-```
-
-Full code you can see at [SendMessage test](https://github.com/sochix/TLSharp/blob/master/TLSharp.Tests/TLSharpTests.cs#L87)
-
-To send message to channel you could use the following code:
-```csharp
- //get user dialogs
- var dialogs = await client.GetUserDialogsAsync();
-
- //find channel by title
- var chat = dialogs.chats.lists
- .Where(c => c.GetType() == typeof(TLChannel))
- .Cast()
- .FirstOrDefault(c => c.title == "");
-
- //send message
- await client.SendMessageAsync(new TLInputPeerChannel() { channel_id = chat.id, access_hash = chat.access_hash.Value }, "OUR_MESSAGE");
-```
-Full code you can see at [SendMessageToChannel test](https://github.com/sochix/TLSharp/blob/master/TLSharp.Tests/TLSharpTests.cs#L107)
-## Working with files
-Telegram separate files to two categories -> big file and small file. File is Big if its size more than 10 Mb. TLSharp tries to hide this complexity from you, thats why we provide one method to upload files **UploadFile**.
-
-```csharp
- var fileResult = await client.UploadFile("cat.jpg", new StreamReader("data/cat.jpg"));
-```
-
-TLSharp provides two wrappers for sending photo and document
-
-```csharp
- await client.SendUploadedPhoto(new TLInputPeerUser() { user_id = user.id }, fileResult, "kitty");
- await client.SendUploadedDocument(
- new TLInputPeerUser() { user_id = user.id },
- fileResult,
- "some zips", //caption
- "application/zip", //mime-type
- new TLVector()); //document attributes, such as file name
-```
-Full code you can see at [SendPhotoToContactTest](https://github.com/sochix/TLSharp/blob/master/TLSharp.Tests/TLSharpTests.cs#L125) and [SendBigFileToContactTest](https://github.com/sochix/TLSharp/blob/master/TLSharp.Tests/TLSharpTests.cs#L143)
-
-To download file you should call **GetFile** method
+# Sample code
```csharp
- await client.GetFile(
- new TLInputDocumentFileLocation()
+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
{
- access_hash = document.access_hash,
- id = document.id,
- version = document.version
- },
- document.size); //size of fileChunk you want to retrieve
-```
-
-Full code you can see at [DownloadFileFromContactTest](https://github.com/sochix/TLSharp/blob/master/TLSharp.Tests/TLSharpTests.cs#L167)
-
-# Available Methods
-
-For your convenience TLSharp have wrappers for several Telegram API methods. You could add your own, see details below.
-
-1. IsPhoneRegisteredAsync
-1. SendCodeRequestAsync
-1. MakeAuthAsync
-1. SignUpAsync
-1. GetContactsAsync
-1. SendMessageAsync
-1. SendTypingAsync
-1. GetUserDialogsAsync
-1. SendUploadedPhoto
-1. SendUploadedDocument
-1. GetFile
-1. UploadFile
-1. SendPingAsync
-1. GetHistoryAsync
-
-**What if you can't find needed method at the list?**
-
-Don't panic. You can call any method with help of `SendRequestAsync` function. For example, send user typing method:
-
-```csharp
-
- //Create request
- var req = new TLRequestSetTyping()
- {
- action = new TLSendMessageTypingAction(),
- peer = peer
- };
-
- //run request, and deserialize response to Boolean
- return await SendRequestAsync(req);
-```
-
-**Where you can find a list of requests and its params?**
-
-The only way is [Telegram API docs](https://core.telegram.org/methods). Yes, it's outdated. But there is no other source.
-Latest scheme in JSON format you can find [here](https://gist.github.com/aarani/b22b7cda024973dff68e1672794b0298)
-
-# Contributing
-
-Contributing is highly appreciated! Donations required
-
-## What things can I Implement (Project Roadmap)?
-
-### Release 1.0.0
-
-* [DONE] Add PHONE_MIGRATE handling
-* [DONE] Add FILE_MIGRATE handling
-* Add Updates handling
-* [DONE] Add NuGet package
-* [DONE] Add wrappers for media uploading and downloading
-* Store user session as JSON
-
-# FAQ
-
-#### What API layer is supported?
-The latest one - 66. Thanks to Afshin Arani for his TLGenerator
-
-#### I get a xxxMigrationException or a MIGRATE_X error!
-
-TLSharp library should automatically handle these errors. If you see such errors, please open a new Github issue with the details (include a stacktrace, etc.).
-
-#### I get an exception: System.IO.EndOfStreamException: Unable to read beyond the end of the stream. All test methos except that AuthenticationWorks and TestConnection return same error. I did every thing including setting api id and hash, and setting server address.-
-
-You should create a Telegram session. See [configuration guide](#sending-messages-set-up)
-
-#### Why do I get a FloodException/FLOOD_WAIT error?
-It's likely [Telegram restrictions](https://core.telegram.org/api/errors#420-flood), or a bug in TLSharp (if you feel it's the latter, please open a Github issue). You can know the time to wait by accessing the FloodException::TimeToWait property.
-
-#### Why does TLSharp lacks feature XXXX?
-
-Now TLSharp is basic realization of Telegram protocol, you can be a contributor or a sponsor to speed-up developemnt of any feature.
-
-#### Nothing helps
-Ask your question at gitter or create an issue in project bug tracker.
+ 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}");
+ }
-**Attach following information**:
-* Full problem description and exception message
-* Stack-trace
-* Your code that runs in to this exception
+ 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}");
+ }
-Without information listen above your issue will be closed.
+ 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}");
+ }
-# Donations
-Thanks for donations! It's highly appreciated.
-
+ 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 {}
+
+ }
+ }
+}
-List of donators:
-* [mtbitcoin](https://github.com/mtbitcoin)
+```
-# Contributors
-* [Afshin Arani](http://aarani.ir) - TLGenerator, and a lot of other usefull things
-* [Knocte](https://github.com/knocte)
# License
diff --git a/TLSharp.Core/Network/Exceptions.cs b/TLSharp.Core/Network/Exceptions.cs
new file mode 100644
index 00000000..878e64e4
--- /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 795e8788..2c96dcb9 100644
--- a/TLSharp.Core/Network/MtProtoSender.cs
+++ b/TLSharp.Core/Network/MtProtoSender.cs
@@ -1,588 +1,595 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.IO;
-using System.Linq;
-using System.Text.RegularExpressions;
-using System.Threading;
-using System.Threading.Tasks;
-using Ionic.Zlib;
-using TLSharp.Core.MTProto;
-using TLSharp.Core.MTProto.Crypto;
-using TLSharp.Core.Requests;
-using TLSharp.Core.Utils;
-
-namespace TLSharp.Core.Network
-{
- public class MtProtoSender
- {
- //private ulong sessionId = GenerateRandomUlong();
-
- private TcpTransport _transport;
- private Session _session;
-
- public List needConfirmation = new List();
-
- public MtProtoSender(TcpTransport transport, Session session)
- {
- _transport = transport;
- _session = session;
- }
-
- public void ChangeTransport(TcpTransport transport)
- {
- _transport = transport;
- }
-
- private int GenerateSequence(bool confirmed)
- {
- return confirmed ? _session.Sequence++ * 2 + 1 : _session.Sequence * 2;
- }
-
- public async Task Send(TeleSharp.TL.TLMethod request)
- {
- // TODO: refactor
- 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();
- }
- }
-
-
- using (var memory = new MemoryStream())
- using (var writer = new BinaryWriter(memory))
- {
- request.SerializeBody(writer);
- await Send(memory.ToArray(), request);
- }
-
- _session.Save();
- }
-
- public async Task Send(byte[] packet, TeleSharp.TL.TLMethod request)
- {
- request.MessageId = _session.GetNewMessageId();
-
- byte[] msgKey;
- byte[] ciphertext;
- using (MemoryStream plaintextPacket = makeMemory(8 + 8 + 8 + 4 + 4 + packet.Length))
- {
- using (BinaryWriter plaintextWriter = new BinaryWriter(plaintextPacket))
- {
- plaintextWriter.Write(_session.Salt);
- plaintextWriter.Write(_session.Id);
- plaintextWriter.Write(request.MessageId);
- plaintextWriter.Write(GenerateSequence(request.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());
- }
- }
-
- using (MemoryStream ciphertextPacket = makeMemory(8 + 16 + ciphertext.Length))
- {
- using (BinaryWriter writer = new BinaryWriter(ciphertextPacket))
- {
- writer.Write(_session.AuthKey.Id);
- writer.Write(msgKey);
- writer.Write(ciphertext);
-
- await _transport.Send(ciphertextPacket.GetBuffer());
- }
- }
- }
-
- private Tuple DecodeMessage(byte[] body)
- {
- byte[] message;
- ulong remoteMessageId;
- int remoteSequence;
-
- using (var inputStream = new MemoryStream(body))
- using (var inputReader = new BinaryReader(inputStream))
- {
- if (inputReader.BaseStream.Length < 8)
- throw new InvalidOperationException($"Can't decode packet");
-
- ulong remoteAuthKeyId = inputReader.ReadUInt64(); // TODO: check auth key id
- byte[] msgKey = inputReader.ReadBytes(16); // TODO: check msg_key correctness
- AESKeyData keyData = Helpers.CalcKey(_session.AuthKey.Data, msgKey, false);
-
- byte[] plaintext = AES.DecryptAES(keyData, inputReader.ReadBytes((int)(inputStream.Length - inputStream.Position)));
-
- using (MemoryStream plaintextStream = new MemoryStream(plaintext))
- using (BinaryReader plaintextReader = new BinaryReader(plaintextStream))
- {
- var remoteSalt = plaintextReader.ReadUInt64();
- var remoteSessionId = plaintextReader.ReadUInt64();
- remoteMessageId = plaintextReader.ReadUInt64();
- remoteSequence = plaintextReader.ReadInt32();
- int msgLen = plaintextReader.ReadInt32();
- message = plaintextReader.ReadBytes(msgLen);
- }
- }
- return new Tuple(message, remoteMessageId, remoteSequence);
- }
-
- public async Task Receive(TeleSharp.TL.TLMethod request)
- {
- while (!request.ConfirmReceived)
- {
- var result = DecodeMessage((await _transport.Receieve()).Body);
-
- using (var messageStream = new MemoryStream(result.Item1, false))
- using (var messageReader = new BinaryReader(messageStream))
- {
- processMessage(result.Item2, result.Item3, messageReader, request);
- }
- }
-
- return null;
- }
-
- public async Task SendPingAsync()
- {
- var pingRequest = new PingRequest();
- using (var memory = new MemoryStream())
- using (var writer = new BinaryWriter(memory))
- {
- pingRequest.SerializeBody(writer);
- await Send(memory.ToArray(), pingRequest);
- }
-
- await Receive(pingRequest);
- }
-
- private bool processMessage(ulong messageId, int sequence, BinaryReader messageReader, TeleSharp.TL.TLMethod request)
- {
- // TODO: check salt
- // 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);
-
- uint code = messageReader.ReadUInt32();
- messageReader.BaseStream.Position -= 4;
- switch (code)
- {
- case 0x73f1f8dc: // container
- //logger.debug("MSG container");
- return HandleContainer(messageId, sequence, messageReader, request);
- case 0x7abe77ec: // ping
- //logger.debug("MSG ping");
- return HandlePing(messageId, sequence, messageReader);
- case 0x347773c5: // pong
- //logger.debug("MSG pong");
- return HandlePong(messageId, sequence, messageReader, request);
- case 0xae500895: // future_salts
- //logger.debug("MSG future_salts");
- return HandleFutureSalts(messageId, sequence, messageReader);
- case 0x9ec20908: // new_session_created
- //logger.debug("MSG new_session_created");
- return HandleNewSessionCreated(messageId, sequence, messageReader);
- case 0x62d6b459: // msgs_ack
- //logger.debug("MSG msds_ack");
- return HandleMsgsAck(messageId, sequence, messageReader);
- case 0xedab447b: // bad_server_salt
- //logger.debug("MSG bad_server_salt");
- return HandleBadServerSalt(messageId, sequence, messageReader, request);
- case 0xa7eff811: // bad_msg_notification
- //logger.debug("MSG bad_msg_notification");
- return HandleBadMsgNotification(messageId, sequence, messageReader);
- case 0x276d3ec6: // msg_detailed_info
- //logger.debug("MSG msg_detailed_info");
- return HandleMsgDetailedInfo(messageId, sequence, messageReader);
- case 0xf35c6d01: // rpc_result
- //logger.debug("MSG rpc_result");
- return HandleRpcResult(messageId, sequence, messageReader, request);
- case 0x3072cfa1: // gzip_packed
- //logger.debug("MSG gzip_packed");
- return HandleGzipPacked(messageId, sequence, messageReader, request);
- case 0xe317af7e:
- case 0xd3f45784:
- case 0x2b2fbd4e:
- case 0x78d4dec1:
- case 0x725b04c3:
- case 0x74ae4240:
- return HandleUpdate(messageId, sequence, messageReader);
- default:
- //logger.debug("unknown message: {0}", code);
- return false;
- }
- }
-
- private bool HandleUpdate(ulong messageId, int sequence, BinaryReader messageReader)
- {
- return false;
-
- /*
- try
- {
- UpdatesEvent(TL.Parse(messageReader));
- return true;
- }
- catch (Exception e)
- {
- logger.warning("update processing exception: {0}", e);
- return false;
- }
- */
- }
-
- private bool HandleGzipPacked(ulong messageId, int sequence, BinaryReader messageReader, TeleSharp.TL.TLMethod request)
- {
- uint code = messageReader.ReadUInt32();
- byte[] packedData = GZipStream.UncompressBuffer(Serializers.Bytes.read(messageReader));
- using (MemoryStream packedStream = new MemoryStream(packedData, false))
- using (BinaryReader compressedReader = new BinaryReader(packedStream))
- {
- processMessage(messageId, sequence, compressedReader, request);
- }
-
- return true;
- }
-
- private bool HandleRpcResult(ulong messageId, int sequence, BinaryReader messageReader, TeleSharp.TL.TLMethod request)
- {
- uint code = messageReader.ReadUInt32();
- ulong requestId = messageReader.ReadUInt64();
-
- if (requestId == (ulong)request.MessageId)
- request.ConfirmReceived = true;
-
- //throw new NotImplementedException();
- /*
- lock (runningRequests)
- {
- if (!runningRequests.ContainsKey(requestId))
- {
- logger.warning("rpc response on unknown request: {0}", requestId);
- messageReader.BaseStream.Position -= 12;
- return false;
- }
-
- request = runningRequests[requestId];
- runningRequests.Remove(requestId);
- }
- */
-
- uint innerCode = messageReader.ReadUInt32();
- if (innerCode == 0x2144ca19)
- { // rpc_error
- int errorCode = messageReader.ReadInt32();
- string errorMessage = Serializers.String.read(messageReader);
-
- if (errorMessage.StartsWith("FLOOD_WAIT_"))
- {
- var resultString = Regex.Match(errorMessage, @"\d+").Value;
- var seconds = int.Parse(resultString);
- throw new FloodException(TimeSpan.FromSeconds(seconds));
- }
- else if (errorMessage.StartsWith("PHONE_MIGRATE_"))
- {
- var resultString = Regex.Match(errorMessage, @"\d+").Value;
- var dcIdx = int.Parse(resultString);
- throw new PhoneMigrationException(dcIdx);
- }
- else if (errorMessage.StartsWith("FILE_MIGRATE_"))
- {
- var resultString = Regex.Match(errorMessage, @"\d+").Value;
- var dcIdx = int.Parse(resultString);
- throw new FileMigrationException(dcIdx);
- }
- else if (errorMessage.StartsWith("USER_MIGRATE_"))
- {
- var resultString = Regex.Match(errorMessage, @"\d+").Value;
- var dcIdx = int.Parse(resultString);
- throw new UserMigrationException(dcIdx);
- }
- else if (errorMessage.StartsWith("NETWORK_MIGRATE_"))
- {
- var resultString = Regex.Match(errorMessage, @"\d+").Value;
- var dcIdx = int.Parse(resultString);
- throw new NetworkMigrationException(dcIdx);
- }
- else if (errorMessage == "PHONE_CODE_INVALID")
- {
- throw new InvalidPhoneCodeException("The numeric code used to authenticate does not match the numeric code sent by SMS/Telegram");
- }
- else if (errorMessage == "SESSION_PASSWORD_NEEDED")
- {
- throw new CloudPasswordNeededException("This Account has Cloud Password !");
- }
- else
- {
- throw new InvalidOperationException(errorMessage);
- }
-
- }
- else if (innerCode == 0x3072cfa1)
- {
- try
- {
- // gzip_packed
- byte[] packedData = Serializers.Bytes.read(messageReader);
- using (var ms = new MemoryStream())
- {
- using (var packedStream = new MemoryStream(packedData, false))
- using (var zipStream = new GZipStream(packedStream, CompressionMode.Decompress))
- {
- zipStream.CopyTo(ms);
- ms.Position = 0;
- }
- using (var compressedReader = new BinaryReader(ms))
- {
- request.DeserializeResponse(compressedReader);
- }
- }
- }
- catch (ZlibException ex)
- {
-
- }
- }
- else
- {
- messageReader.BaseStream.Position -= 4;
- request.DeserializeResponse(messageReader);
- }
-
- return false;
- }
-
- private bool HandleMsgDetailedInfo(ulong messageId, int sequence, BinaryReader messageReader)
- {
- return false;
- }
-
- private bool HandleBadMsgNotification(ulong messageId, int sequence, BinaryReader messageReader)
- {
- uint code = messageReader.ReadUInt32();
- ulong requestId = messageReader.ReadUInt64();
- int requestSequence = messageReader.ReadInt32();
- int errorCode = messageReader.ReadInt32();
-
- switch (errorCode)
- {
- case 16:
- throw new InvalidOperationException("msg_id too low (most likely, client time is wrong; it would be worthwhile to synchronize it using msg_id notifications and re-send the original message with the “correct” msg_id or wrap it in a container with a new msg_id if the original message had waited too long on the client to be transmitted)");
- case 17:
- throw new InvalidOperationException("msg_id too high (similar to the previous case, the client time has to be synchronized, and the message re-sent with the correct msg_id)");
- case 18:
- throw new InvalidOperationException("incorrect two lower order msg_id bits (the server expects client message msg_id to be divisible by 4)");
- case 19:
- throw new InvalidOperationException("container msg_id is the same as msg_id of a previously received message (this must never happen)");
- case 20:
- throw new InvalidOperationException("message too old, and it cannot be verified whether the server has received a message with this msg_id or not");
- case 32:
- throw new InvalidOperationException("msg_seqno too low (the server has already received a message with a lower msg_id but with either a higher or an equal and odd seqno)");
- case 33:
- throw new InvalidOperationException(" msg_seqno too high (similarly, there is a message with a higher msg_id but with either a lower or an equal and odd seqno)");
- case 34:
- throw new InvalidOperationException("an even msg_seqno expected (irrelevant message), but odd received");
- case 35:
- throw new InvalidOperationException("odd msg_seqno expected (relevant message), but even received");
- case 48:
- throw new InvalidOperationException("incorrect server salt (in this case, the bad_server_salt response is received with the correct salt, and the message is to be re-sent with it)");
- case 64:
- throw new InvalidOperationException("invalid container");
-
- }
- throw new NotImplementedException("This should never happens");
- /*
- logger.debug("bad_msg_notification: msgid {0}, seq {1}, errorcode {2}", requestId, requestSequence,
- errorCode);
- */
- /*
- if (!runningRequests.ContainsKey(requestId))
- {
- logger.debug("bad msg notification on unknown request");
- return true;
- }
- */
-
- //OnBrokenSessionEvent();
- //MTProtoRequest request = runningRequests[requestId];
- //request.OnException(new MTProtoBadMessageException(errorCode));
-
- return true;
- }
-
- private bool HandleBadServerSalt(ulong messageId, int sequence, BinaryReader messageReader, TeleSharp.TL.TLMethod request)
- {
- uint code = messageReader.ReadUInt32();
- ulong badMsgId = messageReader.ReadUInt64();
- int badMsgSeqNo = messageReader.ReadInt32();
- int errorCode = messageReader.ReadInt32();
- ulong newSalt = messageReader.ReadUInt64();
-
- //logger.debug("bad_server_salt: msgid {0}, seq {1}, errorcode {2}, newsalt {3}", badMsgId, badMsgSeqNo, errorCode, newSalt);
-
- _session.Salt = newSalt;
-
- //resend
- Send(request);
- /*
- if(!runningRequests.ContainsKey(badMsgId)) {
- logger.debug("bad server salt on unknown message");
- return true;
- }
- */
-
-
- //MTProtoRequest request = runningRequests[badMsgId];
- //request.OnException(new MTProtoBadServerSaltException(salt));
-
- return true;
- }
-
- private bool HandleMsgsAck(ulong messageId, int sequence, BinaryReader messageReader)
- {
- return false;
- }
-
- private bool HandleNewSessionCreated(ulong messageId, int sequence, BinaryReader messageReader)
- {
- return false;
- }
-
- private bool HandleFutureSalts(ulong messageId, int sequence, BinaryReader messageReader)
- {
- uint code = messageReader.ReadUInt32();
- ulong requestId = messageReader.ReadUInt64();
-
- messageReader.BaseStream.Position -= 12;
-
- throw new NotImplementedException("Handle future server salts function isn't implemented.");
- /*
- if (!runningRequests.ContainsKey(requestId))
- {
- logger.info("future salts on unknown request");
- return false;
- }
- */
-
- // MTProtoRequest request = runningRequests[requestId];
- // runningRequests.Remove(requestId);
- // request.OnResponse(messageReader);
-
- return true;
- }
-
- private bool HandlePong(ulong messageId, int sequence, BinaryReader messageReader, TeleSharp.TL.TLMethod request)
- {
- uint code = messageReader.ReadUInt32();
- ulong msgId = messageReader.ReadUInt64();
-
- if (msgId == (ulong)request.MessageId)
- {
- request.ConfirmReceived = true;
- }
-
- return false;
- }
-
- private bool HandlePing(ulong messageId, int sequence, BinaryReader messageReader)
- {
- return false;
- }
-
- private bool HandleContainer(ulong messageId, int sequence, BinaryReader messageReader, TeleSharp.TL.TLMethod request)
- {
- uint code = messageReader.ReadUInt32();
- int size = messageReader.ReadInt32();
- for (int i = 0; i < size; i++)
- {
- ulong innerMessageId = messageReader.ReadUInt64();
- int innerSequence = messageReader.ReadInt32();
- int innerLength = messageReader.ReadInt32();
- long beginPosition = messageReader.BaseStream.Position;
- try
- {
- if (!processMessage(innerMessageId, sequence, messageReader, request))
- {
- messageReader.BaseStream.Position = beginPosition + innerLength;
- }
- }
- catch (Exception e)
- {
- // logger.error("failed to process message in contailer: {0}", e);
- messageReader.BaseStream.Position = beginPosition + innerLength;
- }
- }
-
- return false;
- }
-
- private MemoryStream makeMemory(int len)
- {
- 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)
- {
- }
- }
-}
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Text.RegularExpressions;
+using System.Threading;
+using System.Threading.Tasks;
+using Ionic.Zlib;
+using TLSharp.Core.MTProto;
+using TLSharp.Core.MTProto.Crypto;
+using TLSharp.Core.Requests;
+using TLSharp.Core.Utils;
+
+namespace TLSharp.Core.Network
+{
+ public class MtProtoSender
+ {
+ //private ulong sessionId = GenerateRandomUlong();
+
+ 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 delegate void HandleUpdates (TeleSharp.TL.TLAbsUpdates updates);
+
+ public event HandleUpdates UpdatesEvent;
+
+ public List needConfirmation = new List();
+
+ public MtProtoSender(TcpTransport transport, Session session)
+ {
+ _transport = transport;
+ _session = session;
+ }
+
+ public void ChangeTransport(TcpTransport transport)
+ {
+ _transport = transport;
+ }
+
+ private int GenerateSequence(bool confirmed)
+ {
+ return confirmed ? _session.Sequence++ * 2 + 1 : _session.Sequence * 2;
+ }
+
+ private async Task Ack()
+ {
+ 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();
+ }
+ }
+ }
+
+ public async Task Send(TeleSharp.TL.TLMethod request)
+ {
+ using (var memory = new MemoryStream())
+ using (var writer = new BinaryWriter(memory))
+ {
+ request.SerializeBody(writer);
+ await Send(memory.ToArray(), request);
+ }
+
+ _session.Save();
+ }
+
+ public async Task Send(byte[] packet, TeleSharp.TL.TLMethod request)
+ {
+ request.MessageId = _session.GetNewMessageId();
+
+ byte[] msgKey;
+ byte[] ciphertext;
+ using (MemoryStream plaintextPacket = makeMemory(8 + 8 + 8 + 4 + 4 + packet.Length))
+ {
+ using (BinaryWriter plaintextWriter = new BinaryWriter(plaintextPacket))
+ {
+ plaintextWriter.Write(_session.Salt);
+ plaintextWriter.Write(_session.Id);
+ plaintextWriter.Write(request.MessageId);
+ plaintextWriter.Write(GenerateSequence(request.Confirmed));
+ plaintextWriter.Write(packet.Length);
+ plaintextWriter.Write(packet);
+
+ 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))
+ {
+ using (BinaryWriter writer = new BinaryWriter(ciphertextPacket))
+ {
+ writer.Write(_session.AuthKey.Id);
+ writer.Write(msgKey);
+ writer.Write(ciphertext);
+
+ await _transport.Send(ciphertextPacket.GetBuffer());
+ }
+ }
+ }
+
+ private Tuple DecodeMessage(byte[] body)
+ {
+ byte[] message;
+ ulong remoteMessageId;
+ int remoteSequence;
+
+ using (var inputStream = new MemoryStream(body))
+ using (var inputReader = new BinaryReader(inputStream))
+ {
+ if (inputReader.BaseStream.Length < 8)
+ throw new InvalidOperationException($"Can't decode packet");
+
+ ulong remoteAuthKeyId = inputReader.ReadUInt64(); // TODO: check auth key id
+ byte[] msgKey = inputReader.ReadBytes(16); // TODO: check msg_key correctness
+ 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))
+ {
+ var remoteSalt = plaintextReader.ReadUInt64();
+ var remoteSessionId = plaintextReader.ReadUInt64();
+ remoteMessageId = plaintextReader.ReadUInt64();
+ remoteSequence = plaintextReader.ReadInt32();
+ int msgLen = plaintextReader.ReadInt32();
+ message = plaintextReader.ReadBytes(msgLen);
+ }
+ }
+ return new Tuple(message, remoteMessageId, remoteSequence);
+ }
+
+ public async Task Receive (TeleSharp.TL.TLMethod request)
+ {
+ while (!request.ConfirmReceived)
+ {
+ var result = DecodeMessage ((await _transport.Receieve ()).Body);
+
+ using (var messageStream = new MemoryStream (result.Item1, false))
+ using (var messageReader = new BinaryReader (messageStream))
+ {
+ 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();
+ using (var memory = new MemoryStream())
+ using (var writer = new BinaryWriter(memory))
+ {
+ pingRequest.SerializeBody(writer);
+ await Send(memory.ToArray(), pingRequest);
+ }
+
+ await Receive(pingRequest);
+ }
+
+ private bool processMessage(ulong messageId, int sequence, BinaryReader messageReader, TeleSharp.TL.TLMethod request)
+ {
+ // TODO: check salt
+ // 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
+ //logger.debug("MSG container");
+ return HandleContainer(messageId, sequence, messageReader, request);
+ case 0x7abe77ec: // ping
+ //logger.debug("MSG ping");
+ return HandlePing(messageId, sequence, messageReader);
+ case 0x347773c5: // pong
+ //logger.debug("MSG pong");
+ return HandlePong(messageId, sequence, messageReader, request);
+ case 0xae500895: // future_salts
+ //logger.debug("MSG future_salts");
+ return HandleFutureSalts(messageId, sequence, messageReader);
+ case 0x9ec20908: // new_session_created
+ //logger.debug("MSG new_session_created");
+ return HandleNewSessionCreated(messageId, sequence, messageReader);
+ case 0x62d6b459: // msgs_ack
+ //logger.debug("MSG msds_ack");
+ return HandleMsgsAck(messageId, sequence, messageReader);
+ case 0xedab447b: // bad_server_salt
+ //logger.debug("MSG bad_server_salt");
+ return HandleBadServerSalt(messageId, sequence, messageReader, request);
+ case 0xa7eff811: // bad_msg_notification
+ //logger.debug("MSG bad_msg_notification");
+ return HandleBadMsgNotification(messageId, sequence, messageReader);
+ case 0x276d3ec6: // msg_detailed_info
+ //logger.debug("MSG msg_detailed_info");
+ return HandleMsgDetailedInfo(messageId, sequence, messageReader);
+ case 0xf35c6d01: // rpc_result
+ //logger.debug("MSG rpc_result");
+ return HandleRpcResult(messageId, sequence, messageReader, request);
+ case 0x3072cfa1: // gzip_packed
+ //logger.debug("MSG gzip_packed");
+ return HandleGzipPacked(messageId, sequence, messageReader, request);
+ case 0xe317af7e:
+ case 0x914fbf11:
+ case 0x16812688:
+ case 0x78d4dec1:
+ case 0x725b04c3:
+ case 0x74ae4240:
+ case 0x11f1331c:
+ return HandleUpdate(code, sequence, messageReader, request);
+ default:
+ logger.Info("unhandled message");
+ return false;
+ }
+ }
+
+ private bool HandleUpdate(uint code, int sequence, BinaryReader messageReader, TeleSharp.TL.TLMethod request)
+ {
+ try
+ {
+ var update = ParseUpdate (code, messageReader);
+ if (update != null && UpdatesEvent != null)
+ {
+ UpdatesEvent (update);
+ }
+ return true;
+ }
+ catch (Exception ex)
+ {
+ logger.Debug($"HandleUpdate failed: {ex}");
+ }
+ 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)
+ {
+ uint code = messageReader.ReadUInt32();
+ byte[] packedData = GZipStream.UncompressBuffer(Serializers.Bytes.read(messageReader));
+ using (MemoryStream packedStream = new MemoryStream(packedData, false))
+ using (BinaryReader compressedReader = new BinaryReader(packedStream))
+ {
+ processMessage(messageId, sequence, compressedReader, request);
+ }
+
+ return true;
+ }
+
+ private bool HandleRpcResult(ulong messageId, int sequence, BinaryReader messageReader, TeleSharp.TL.TLMethod request)
+ {
+ uint code = messageReader.ReadUInt32();
+ ulong requestId = messageReader.ReadUInt64();
+
+ if (requestId == (ulong)request.MessageId)
+ request.ConfirmReceived = true;
+
+ //throw new NotImplementedException();
+ /*
+ lock (runningRequests)
+ {
+ if (!runningRequests.ContainsKey(requestId))
+ {
+ logger.warning("rpc response on unknown request: {0}", requestId);
+ messageReader.BaseStream.Position -= 12;
+ return false;
+ }
+
+ request = runningRequests[requestId];
+ runningRequests.Remove(requestId);
+ }
+ */
+
+ uint innerCode = messageReader.ReadUInt32();
+ if (innerCode == 0x2144ca19)
+ { // rpc_error
+ int errorCode = messageReader.ReadInt32();
+ string errorMessage = Serializers.String.read(messageReader);
+ Console.Error.WriteLine($"ERROR: {errorMessage} - {errorCode}");
+
+ if (errorMessage.StartsWith("FLOOD_WAIT_"))
+ {
+ var resultString = Regex.Match(errorMessage, @"\d+").Value;
+ var seconds = int.Parse(resultString);
+ throw new FloodException(TimeSpan.FromSeconds(seconds));
+ }
+ else if (errorMessage.StartsWith("PHONE_MIGRATE_"))
+ {
+ var resultString = Regex.Match(errorMessage, @"\d+").Value;
+ var dcIdx = int.Parse(resultString);
+ throw new PhoneMigrationException(dcIdx);
+ }
+ else if (errorMessage.StartsWith("FILE_MIGRATE_"))
+ {
+ var resultString = Regex.Match(errorMessage, @"\d+").Value;
+ var dcIdx = int.Parse(resultString);
+ throw new FileMigrationException(dcIdx);
+ }
+ else if (errorMessage.StartsWith("USER_MIGRATE_"))
+ {
+ var resultString = Regex.Match(errorMessage, @"\d+").Value;
+ var dcIdx = int.Parse(resultString);
+ throw new UserMigrationException(dcIdx);
+ }
+ else if (errorMessage.StartsWith("NETWORK_MIGRATE_"))
+ {
+ var resultString = Regex.Match(errorMessage, @"\d+").Value;
+ var dcIdx = int.Parse(resultString);
+ throw new NetworkMigrationException(dcIdx);
+ }
+ else if (errorMessage == "PHONE_CODE_INVALID")
+ {
+ throw new InvalidPhoneCodeException("The numeric code used to authenticate does not match the numeric code sent by SMS/Telegram");
+ }
+ else if (errorMessage == "SESSION_PASSWORD_NEEDED")
+ {
+ throw new CloudPasswordNeededException("This Account has Cloud Password !");
+ }
+ else
+ {
+ throw new InvalidOperationException(errorMessage);
+ }
+
+ }
+ else if (innerCode == 0x3072cfa1)
+ {
+ try
+ {
+ // gzip_packed
+ byte[] packedData = Serializers.Bytes.read(messageReader);
+ using (var ms = new MemoryStream())
+ {
+ using (var packedStream = new MemoryStream(packedData, false))
+ using (var zipStream = new GZipStream(packedStream, CompressionMode.Decompress))
+ {
+ zipStream.CopyTo(ms);
+ ms.Position = 0;
+ }
+ using (var compressedReader = new BinaryReader(ms))
+ {
+ request.DeserializeResponse(compressedReader);
+ }
+ }
+ }
+ catch (ZlibException ex)
+ {
+
+ }
+ }
+ else
+ {
+ messageReader.BaseStream.Position -= 4;
+ request.DeserializeResponse(messageReader);
+ }
+
+ return false;
+ }
+
+ private bool HandleMsgDetailedInfo(ulong messageId, int sequence, BinaryReader messageReader)
+ {
+ return false;
+ }
+
+ private bool HandleBadMsgNotification(ulong messageId, int sequence, BinaryReader messageReader)
+ {
+ uint code = messageReader.ReadUInt32();
+ ulong requestId = messageReader.ReadUInt64();
+ int requestSequence = messageReader.ReadInt32();
+ int errorCode = messageReader.ReadInt32();
+
+ switch (errorCode)
+ {
+ case 16:
+ throw new BadMessageException("msg_id too low (most likely, client time is wrong; it would be worthwhile to synchronize it using msg_id notifications and re-send the original message with the “correct” msg_id or wrap it in a container with a new msg_id if the original message had waited too long on the client to be transmitted)");
+ case 17:
+ throw new BadMessageException("msg_id too high (similar to the previous case, the client time has to be synchronized, and the message re-sent with the correct msg_id)");
+ case 18:
+ throw new BadMessageException("incorrect two lower order msg_id bits (the server expects client message msg_id to be divisible by 4)");
+ case 19:
+ throw new BadMessageException("container msg_id is the same as msg_id of a previously received message (this must never happen)");
+ case 20:
+ throw new BadMessageException("message too old, and it cannot be verified whether the server has received a message with this msg_id or not");
+ case 32:
+ throw new BadMessageException("msg_seqno too low (the server has already received a message with a lower msg_id but with either a higher or an equal and odd seqno)");
+ case 33:
+ throw new BadMessageException(" msg_seqno too high (similarly, there is a message with a higher msg_id but with either a lower or an equal and odd seqno)");
+ case 34:
+ throw new BadMessageException("an even msg_seqno expected (irrelevant message), but odd received");
+ case 35:
+ throw new BadMessageException("odd msg_seqno expected (relevant message), but even received");
+ case 48:
+ throw new BadMessageException("incorrect server salt (in this case, the bad_server_salt response is received with the correct salt, and the message is to be re-sent with it)");
+ case 64:
+ throw new BadMessageException("invalid container");
+
+ }
+ throw new NotImplementedException("This should never happen!");
+ /*
+ logger.debug("bad_msg_notification: msgid {0}, seq {1}, errorcode {2}", requestId, requestSequence,
+ errorCode);
+ */
+ /*
+ if (!runningRequests.ContainsKey(requestId))
+ {
+ logger.debug("bad msg notification on unknown request");
+ return true;
+ }
+ */
+
+ //OnBrokenSessionEvent();
+ //MTProtoRequest request = runningRequests[requestId];
+ //request.OnException(new MTProtoBadMessageException(errorCode));
+
+ return true;
+ }
+
+ private bool HandleBadServerSalt(ulong messageId, int sequence, BinaryReader messageReader, TeleSharp.TL.TLMethod request)
+ {
+ uint code = messageReader.ReadUInt32();
+ ulong badMsgId = messageReader.ReadUInt64();
+ int badMsgSeqNo = messageReader.ReadInt32();
+ int errorCode = messageReader.ReadInt32();
+ ulong newSalt = messageReader.ReadUInt64();
+
+ //logger.debug("bad_server_salt: msgid {0}, seq {1}, errorcode {2}, newsalt {3}", badMsgId, badMsgSeqNo, errorCode, newSalt);
+
+ _session.Salt = newSalt;
+
+ //resend
+ Send(request);
+ /*
+ if(!runningRequests.ContainsKey(badMsgId)) {
+ logger.debug("bad server salt on unknown message");
+ return true;
+ }
+ */
+
+
+ //MTProtoRequest request = runningRequests[badMsgId];
+ //request.OnException(new MTProtoBadServerSaltException(salt));
+
+ return true;
+ }
+
+ private bool HandleMsgsAck(ulong messageId, int sequence, BinaryReader messageReader)
+ {
+ return false;
+ }
+
+ private bool HandleNewSessionCreated(ulong messageId, int sequence, BinaryReader messageReader)
+ {
+ return false;
+ }
+
+ private bool HandleFutureSalts(ulong messageId, int sequence, BinaryReader messageReader)
+ {
+ uint code = messageReader.ReadUInt32();
+ ulong requestId = messageReader.ReadUInt64();
+
+ messageReader.BaseStream.Position -= 12;
+
+ throw new NotImplementedException("Handle future server salts function isn't implemented.");
+ /*
+ if (!runningRequests.ContainsKey(requestId))
+ {
+ logger.info("future salts on unknown request");
+ return false;
+ }
+ */
+
+ // MTProtoRequest request = runningRequests[requestId];
+ // runningRequests.Remove(requestId);
+ // request.OnResponse(messageReader);
+
+ return true;
+ }
+
+ private bool HandlePong(ulong messageId, int sequence, BinaryReader messageReader, TeleSharp.TL.TLMethod request)
+ {
+ uint code = messageReader.ReadUInt32();
+ ulong msgId = messageReader.ReadUInt64();
+
+ if (msgId == (ulong)request.MessageId)
+ {
+ request.ConfirmReceived = true;
+ }
+
+ return false;
+ }
+
+ private bool HandlePing(ulong messageId, int sequence, BinaryReader messageReader)
+ {
+ return false;
+ }
+
+ private bool HandleContainer(ulong messageId, int sequence, BinaryReader messageReader, TeleSharp.TL.TLMethod request)
+ {
+ uint code = messageReader.ReadUInt32();
+ int size = messageReader.ReadInt32();
+ for (int i = 0; i < size; i++)
+ {
+ ulong innerMessageId = messageReader.ReadUInt64();
+ int innerSequence = messageReader.ReadInt32();
+ int innerLength = messageReader.ReadInt32();
+ long beginPosition = messageReader.BaseStream.Position;
+ try
+ {
+ if (!processMessage(innerMessageId, sequence, messageReader, request))
+ {
+ messageReader.BaseStream.Position = beginPosition + innerLength;
+ }
+ }
+ catch (BadMessageException e)
+ {
+ throw e;
+ }
+ catch (Exception e)
+ {
+ logger.Debug($"failed to process message in contailer: {e}");
+ messageReader.BaseStream.Position = beginPosition + innerLength;
+ }
+ }
+
+ return false;
+ }
+
+ private MemoryStream makeMemory(int len)
+ {
+ return new MemoryStream(new byte[len], 0, len, true, true);
+ }
+ }
+}
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 31bd6b4c..d160cd3f 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,8 +10,10 @@ namespace TLSharp.Core.Network
public class TcpTransport : IDisposable
{
+ private static NLog.Logger logger = TelegramClient.logger;
private readonly TcpClient _tcpClient;
private int sendCounter = 0;
+ private CancellationTokenSource tokenSource = new CancellationTokenSource();
public TcpTransport(string address, int port, TcpClientConnectionHandler handler = null)
{
@@ -38,17 +41,20 @@ public async Task Send(byte[] packet)
public async Task Receieve()
{
+ 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)
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];
@@ -86,6 +92,72 @@ public async Task Receieve()
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
diff --git a/TLSharp.Core/Session.cs b/TLSharp.Core/Session.cs
index 987c5d22..f4bca817 100644
--- a/TLSharp.Core/Session.cs
+++ b/TLSharp.Core/Session.cs
@@ -69,13 +69,18 @@ public class Session
public long LastMessageId { get; set; }
public int SessionExpires { get; set; }
public TLUser TLUser { get; set; }
+ public ISessionStore Store { get { return _store; }}
private Random random;
private ISessionStore _store;
- public Session(ISessionStore store)
+ public Session(ISessionStore store, string sessionUserId)
{
random = new Random();
+ Id = GenerateRandomUlong ();
+ SessionUserId = sessionUserId;
+ ServerAddress = defaultConnectionAddress;
+ Port = defaultConnectionPort;
_store = store;
}
@@ -133,7 +138,7 @@ public static Session FromBytes(byte[] buffer, ISessionStore store, string sessi
var authData = Serializers.Bytes.read(reader);
- return new Session(store)
+ return new Session(store, sessionUserId)
{
AuthKey = new AuthKey(authData),
Id = id,
@@ -155,15 +160,9 @@ public void Save()
_store.Save(this);
}
- public static Session TryLoadOrCreateNew(ISessionStore store, string sessionUserId)
+ public static Session GetSession(ISessionStore store, string sessionUserId, Session provided)
{
- return store.Load(sessionUserId) ?? new Session(store)
- {
- Id = GenerateRandomUlong(),
- SessionUserId = sessionUserId,
- ServerAddress = defaultConnectionAddress,
- Port = defaultConnectionPort
- };
+ return store.Load (sessionUserId) ?? provided ?? new Session (store, sessionUserId);
}
private static ulong GenerateRandomUlong()
diff --git a/TLSharp.Core/TLSharp.Core.csproj b/TLSharp.Core/TLSharp.Core.csproj
index fbef9425..144f695c 100644
--- a/TLSharp.Core/TLSharp.Core.csproj
+++ b/TLSharp.Core/TLSharp.Core.csproj
@@ -42,6 +42,9 @@
+
+ ..\..\packages\NLog.4.4.12\lib\net45\NLog.dll
+
@@ -68,6 +71,8 @@
+
+
diff --git a/TLSharp.Core/TelegramClient.cs b/TLSharp.Core/TelegramClient.cs
index 06924869..c2ee5c80 100644
--- a/TLSharp.Core/TelegramClient.cs
+++ b/TLSharp.Core/TelegramClient.cs
@@ -1,90 +1,102 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Security.Cryptography;
-using System.Text;
-using System.Threading.Tasks;
-using TeleSharp.TL;
-using TeleSharp.TL.Account;
-using TeleSharp.TL.Auth;
-using TeleSharp.TL.Contacts;
-using TeleSharp.TL.Help;
-using TeleSharp.TL.Messages;
-using TeleSharp.TL.Upload;
-using TLSharp.Core.Auth;
-using TLSharp.Core.MTProto.Crypto;
-using TLSharp.Core.Network;
-using TLSharp.Core.Utils;
-using TLAuthorization = TeleSharp.TL.Auth.TLAuthorization;
-
-namespace TLSharp.Core
-{
- public class TelegramClient : IDisposable
- {
- private MtProtoSender _sender;
- private AuthKey _key;
- private TcpTransport _transport;
- private string _apiHash = "";
- private int _apiId = 0;
- private Session _session;
- private List dcOptions;
- private TcpClientConnectionHandler _handler;
-
- public TelegramClient(int apiId, string apiHash,
- ISessionStore store = null, string sessionUserId = "session", TcpClientConnectionHandler handler = null)
- {
- if (apiId == default(int))
- throw new MissingApiConfigurationException("API_ID");
- if (string.IsNullOrEmpty(apiHash))
- throw new MissingApiConfigurationException("API_HASH");
-
- if (store == null)
- store = new FileSessionStore();
-
- TLContext.Init();
- _apiHash = apiHash;
- _apiId = apiId;
- _handler = handler;
-
- _session = Session.TryLoadOrCreateNew(store, sessionUserId);
- _transport = new TcpTransport(_session.ServerAddress, _session.Port, _handler);
- }
-
- public async Task ConnectAsync(bool reconnect = false)
- {
- if (_session.AuthKey == null || reconnect)
- {
- var result = await Authenticator.DoAuthentication(_transport);
- _session.AuthKey = result.AuthKey;
- _session.TimeOffset = result.TimeOffset;
- }
-
- _sender = new MtProtoSender(_transport, _session);
-
- //set-up layer
- var config = new TLRequestGetConfig();
- var request = new TLRequestInitConnection()
- {
- ApiId = _apiId,
- AppVersion = "1.0.0",
- DeviceModel = "PC",
- LangCode = "en",
- Query = config,
- SystemVersion = "Win 10.0"
- };
- var invokewithLayer = new TLRequestInvokeWithLayer() { Layer = 66, Query = request };
- await _sender.Send(invokewithLayer);
- await _sender.Receive(invokewithLayer);
-
- dcOptions = ((TLConfig)invokewithLayer.Response).DcOptions.ToList();
-
- return true;
- }
-
- private async Task ReconnectToDcAsync(int dcId)
- {
- if (dcOptions == null || !dcOptions.Any())
- throw new InvalidOperationException($"Can't reconnect. Establish initial connection first.");
+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;
+using TeleSharp.TL.Auth;
+using TeleSharp.TL.Contacts;
+using TeleSharp.TL.Help;
+using TeleSharp.TL.Messages;
+using TeleSharp.TL.Upload;
+using TLSharp.Core.Auth;
+using TLSharp.Core.MTProto.Crypto;
+using TLSharp.Core.Network;
+using TLSharp.Core.Utils;
+using TLAuthorization = TeleSharp.TL.Auth.TLAuthorization;
+
+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;
+ private Session _session;
+ private List dcOptions;
+ private TcpClientConnectionHandler _handler;
+ 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; } }
+
+ public volatile bool AllowEvents = false;
+
+ public TelegramClient(int apiId, string apiHash,
+ Session session = null, string sessionUserId = "session", TcpClientConnectionHandler handler = null)
+ {
+ if (apiId == default(int))
+ throw new MissingApiConfigurationException("API_ID");
+ if (string.IsNullOrEmpty(apiHash))
+ throw new MissingApiConfigurationException("API_HASH");
+
+ TLContext.Init();
+ _apiHash = apiHash;
+ _apiId = apiId;
+ _handler = handler;
+
+ _session = Session.GetSession(session?.Store ?? new FileSessionStore(), session?.SessionUserId ?? sessionUserId, session);
+ _transport = new TcpTransport(_session.ServerAddress, _session.Port, _handler);
+ }
+
+ public async Task ConnectAsync(bool reconnect = false)
+ {
+ if (_session.AuthKey == null || reconnect)
+ {
+ var result = await Authenticator.DoAuthentication(_transport);
+ _session.AuthKey = result.AuthKey;
+ _session.TimeOffset = result.TimeOffset;
+ }
+
+ _sender = new MtProtoSender(_transport, _session);
+ _sender.UpdatesEvent += _sender_UpdatesEvent;
+
+ //set-up layer
+ var config = new TLRequestGetConfig();
+ var request = new TLRequestInitConnection()
+ {
+ ApiId = _apiId,
+ AppVersion = "1.0.0",
+ DeviceModel = "PC",
+ LangCode = "en",
+ Query = config,
+ SystemVersion = "Win 10.0"
+ };
+ var invokewithLayer = new TLRequestInvokeWithLayer() { Layer = 66, Query = request };
+ await _sender.Send(invokewithLayer);
+ await _sender.Receive(invokewithLayer);
+
+ dcOptions = ((TLConfig)invokewithLayer.Response).DcOptions.ToList();
+
+ return true;
+ }
+
+ private async Task ReconnectToDcAsync(int dcId)
+ {
+ if (dcOptions == null || !dcOptions.Any())
+ throw new InvalidOperationException($"Can't reconnect. Establish initial connection first.");
TLExportedAuthorization exported = null;
if (_session.TLUser != null)
@@ -92,13 +104,13 @@ private async Task ReconnectToDcAsync(int dcId)
TLRequestExportAuthorization exportAuthorization = new TLRequestExportAuthorization() { DcId = dcId };
exported = await SendRequestAsync(exportAuthorization);
}
-
- var dc = dcOptions.First(d => d.Id == dcId);
-
- _transport = new TcpTransport(dc.IpAddress, dc.Port, _handler);
- _session.ServerAddress = dc.IpAddress;
- _session.Port = dc.Port;
-
+
+ var dc = dcOptions.First(d => d.Id == dcId);
+
+ _transport = new TcpTransport(dc.IpAddress, dc.Port, _handler);
+ _session.ServerAddress = dc.IpAddress;
+ _session.Port = dc.Port;
+
await ConnectAsync(true);
if (_session.TLUser != null)
@@ -106,214 +118,264 @@ private async Task ReconnectToDcAsync(int dcId)
TLRequestImportAuthorization importAuthorization = new TLRequestImportAuthorization() { Id = exported.Id, Bytes = exported.Bytes };
var imported = await SendRequestAsync(importAuthorization);
OnUserAuthenticated(((TLUser)imported.User));
- }
- }
-
+ }
+ }
+
+ 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)
{
- var completed = false;
- while(!completed)
- {
- try
- {
- await _sender.Send(request);
- await _sender.Receive(request);
- completed = true;
- }
- catch(DataCenterMigrationException e)
- {
- await ReconnectToDcAsync(e.DC);
- // prepare the request for another try
- request.ConfirmReceived = false;
- }
+ var completed = false;
+ while(!completed)
+ {
+ try
+ {
+ await _sender.Send(request);
+ await _sender.Receive(request);
+ completed = true;
+ }
+ catch(DataCenterMigrationException e)
+ {
+ await ReconnectToDcAsync(e.DC);
+ // prepare the request for another try
+ request.ConfirmReceived = false;
+ }
}
- }
-
- public bool IsUserAuthorized()
- {
- return _session.TLUser != null;
- }
-
- public async Task IsPhoneRegisteredAsync(string phoneNumber)
- {
- if (String.IsNullOrWhiteSpace(phoneNumber))
- throw new ArgumentNullException(nameof(phoneNumber));
-
- if (_sender == null)
- throw new InvalidOperationException("Not connected!");
-
- var authCheckPhoneRequest = new TLRequestCheckPhone() { PhoneNumber = phoneNumber };
-
- await RequestWithDcMigration(authCheckPhoneRequest);
-
- return authCheckPhoneRequest.Response.PhoneRegistered;
- }
-
- public async Task SendCodeRequestAsync(string phoneNumber)
- {
- if (String.IsNullOrWhiteSpace(phoneNumber))
- throw new ArgumentNullException(nameof(phoneNumber));
-
- var request = new TLRequestSendCode() { PhoneNumber = phoneNumber, ApiId = _apiId, ApiHash = _apiHash };
-
- await RequestWithDcMigration(request);
-
- return request.Response.PhoneCodeHash;
- }
-
- public async Task MakeAuthAsync(string phoneNumber, string phoneCodeHash, string code)
- {
- if (String.IsNullOrWhiteSpace(phoneNumber))
- throw new ArgumentNullException(nameof(phoneNumber));
-
- if (String.IsNullOrWhiteSpace(phoneCodeHash))
- throw new ArgumentNullException(nameof(phoneCodeHash));
-
- if (String.IsNullOrWhiteSpace(code))
- throw new ArgumentNullException(nameof(code));
-
+ }
+
+ public async Task WaitEventAsync(int timeoutms)
+ {
+ await _sender.Receive (timeoutms);
+ }
+
+ public bool IsUserAuthorized()
+ {
+ return _session.TLUser != null;
+ }
+
+ public async Task IsPhoneRegisteredAsync(string phoneNumber)
+ {
+ if (String.IsNullOrWhiteSpace(phoneNumber))
+ throw new ArgumentNullException(nameof(phoneNumber));
+
+ if (_sender == null)
+ throw new InvalidOperationException("Not connected!");
+
+ var authCheckPhoneRequest = new TLRequestCheckPhone() { PhoneNumber = phoneNumber };
+
+ await RequestWithDcMigration(authCheckPhoneRequest);
+
+ return authCheckPhoneRequest.Response.PhoneRegistered;
+ }
+
+ public async Task SendCodeRequestAsync(string phoneNumber)
+ {
+ if (String.IsNullOrWhiteSpace(phoneNumber))
+ throw new ArgumentNullException(nameof(phoneNumber));
+
+ var request = new TLRequestSendCode() { PhoneNumber = phoneNumber, ApiId = _apiId, ApiHash = _apiHash };
+
+ await RequestWithDcMigration(request);
+
+ return request.Response.PhoneCodeHash;
+ }
+
+ public async Task MakeAuthAsync(string phoneNumber, string phoneCodeHash, string code)
+ {
+ if (String.IsNullOrWhiteSpace(phoneNumber))
+ throw new ArgumentNullException(nameof(phoneNumber));
+
+ if (String.IsNullOrWhiteSpace(phoneCodeHash))
+ throw new ArgumentNullException(nameof(phoneCodeHash));
+
+ if (String.IsNullOrWhiteSpace(code))
+ throw new ArgumentNullException(nameof(code));
+
var request = new TLRequestSignIn() { PhoneNumber = phoneNumber, PhoneCodeHash = phoneCodeHash, PhoneCode = code };
- await RequestWithDcMigration(request);
-
- OnUserAuthenticated(((TLUser)request.Response.User));
-
- return ((TLUser)request.Response.User);
- }
-
- public async Task GetPasswordSetting()
- {
- var request = new TLRequestGetPassword();
-
- await RequestWithDcMigration(request);
-
- return ((TLPassword)request.Response);
- }
-
- public async Task MakeAuthWithPasswordAsync(TLPassword password, string password_str)
- {
-
- byte[] password_Bytes = Encoding.UTF8.GetBytes(password_str);
- IEnumerable rv = password.CurrentSalt.Concat(password_Bytes).Concat(password.CurrentSalt);
-
- SHA256Managed hashstring = new SHA256Managed();
- var password_hash = hashstring.ComputeHash(rv.ToArray());
-
- var request = new TLRequestCheckPassword() { PasswordHash = password_hash };
-
- await RequestWithDcMigration(request);
-
- OnUserAuthenticated(((TLUser)request.Response.User));
-
- return ((TLUser)request.Response.User);
- }
-
- public async Task SignUpAsync(string phoneNumber, string phoneCodeHash, string code, string firstName, string lastName)
- {
- var request = new TLRequestSignUp() { PhoneNumber = phoneNumber, PhoneCode = code, PhoneCodeHash = phoneCodeHash, FirstName = firstName, LastName = lastName };
-
- await RequestWithDcMigration(request);
-
- OnUserAuthenticated(((TLUser)request.Response.User));
-
- return ((TLUser)request.Response.User);
- }
- public async Task SendRequestAsync(TLMethod methodToExecute)
- {
- await RequestWithDcMigration(methodToExecute);
-
- var result = methodToExecute.GetType().GetProperty("Response").GetValue(methodToExecute);
-
- return (T)result;
- }
-
- public async Task GetContactsAsync()
- {
- if (!IsUserAuthorized())
- throw new InvalidOperationException("Authorize user first!");
-
- var req = new TLRequestGetContacts() { Hash = "" };
-
- return await SendRequestAsync(req);
- }
-
- public async Task SendMessageAsync(TLAbsInputPeer peer, string message)
- {
- if (!IsUserAuthorized())
- throw new InvalidOperationException("Authorize user first!");
-
- return await SendRequestAsync(
- new TLRequestSendMessage()
- {
- Peer = peer,
- Message = message,
- RandomId = Helpers.GenerateRandomLong()
- });
- }
-
- public async Task SendTypingAsync(TLAbsInputPeer peer)
- {
- var req = new TLRequestSetTyping()
- {
- Action = new TLSendMessageTypingAction(),
- Peer = peer
- };
- return await SendRequestAsync(req);
- }
-
- public async Task GetUserDialogsAsync()
- {
- var peer = new TLInputPeerSelf();
- return await SendRequestAsync(
- new TLRequestGetDialogs() { OffsetDate = 0, OffsetPeer = peer, Limit = 100 });
- }
-
- public async Task SendUploadedPhoto(TLAbsInputPeer peer, TLAbsInputFile file, string caption)
- {
- return await SendRequestAsync(new TLRequestSendMedia()
- {
- RandomId = Helpers.GenerateRandomLong(),
- Background = false,
- ClearDraft = false,
- Media = new TLInputMediaUploadedPhoto() { File = file, Caption = caption },
- Peer = peer
- });
- }
-
- public async Task SendUploadedDocument(
- TLAbsInputPeer peer, TLAbsInputFile file, string caption, string mimeType, TLVector attributes)
- {
- return await SendRequestAsync(new TLRequestSendMedia()
- {
- RandomId = Helpers.GenerateRandomLong(),
- Background = false,
- ClearDraft = false,
- Media = new TLInputMediaUploadedDocument()
- {
- File = file,
- Caption = caption,
- MimeType = mimeType,
- Attributes = attributes
- },
- Peer = peer
- });
- }
-
- public async Task GetFile(TLAbsInputFileLocation location, int filePartSize, int offset = 0)
- {
- TLFile result = null;
- result = await SendRequestAsync(new TLRequestGetFile()
- {
- Location = location,
- Limit = filePartSize,
- Offset = offset
- });
- return result;
- }
-
- public async Task SendPingAsync()
- {
- await _sender.SendPingAsync();
+ await RequestWithDcMigration(request);
+
+ OnUserAuthenticated(((TLUser)request.Response.User));
+
+ return ((TLUser)request.Response.User);
+ }
+
+ public async Task GetPasswordSetting()
+ {
+ var request = new TLRequestGetPassword();
+
+ await RequestWithDcMigration(request);
+
+ return ((TLPassword)request.Response);
+ }
+
+ public async Task MakeAuthWithPasswordAsync(TLPassword password, string password_str)
+ {
+
+ byte[] password_Bytes = Encoding.UTF8.GetBytes(password_str);
+ IEnumerable rv = password.CurrentSalt.Concat(password_Bytes).Concat(password.CurrentSalt);
+
+ SHA256Managed hashstring = new SHA256Managed();
+ var password_hash = hashstring.ComputeHash(rv.ToArray());
+
+ var request = new TLRequestCheckPassword() { PasswordHash = password_hash };
+
+ await RequestWithDcMigration(request);
+
+ OnUserAuthenticated(((TLUser)request.Response.User));
+
+ return ((TLUser)request.Response.User);
+ }
+
+ public async Task SignUpAsync(string phoneNumber, string phoneCodeHash, string code, string firstName, string lastName)
+ {
+ var request = new TLRequestSignUp() { PhoneNumber = phoneNumber, PhoneCode = code, PhoneCodeHash = phoneCodeHash, FirstName = firstName, LastName = lastName };
+
+ await RequestWithDcMigration(request);
+
+ OnUserAuthenticated(((TLUser)request.Response.User));
+
+ return ((TLUser)request.Response.User);
+ }
+ public async Task SendRequestAsync(TLMethod methodToExecute)
+ {
+ logger.Info("Sending Request: {0} {1:x8}", methodToExecute, methodToExecute.Constructor);
+ await RequestWithDcMigration(methodToExecute);
+
+ var result = methodToExecute.GetType().GetProperty("Response").GetValue(methodToExecute);
+
+ return (T)result;
+ }
+
+ public async Task GetContactsAsync()
+ {
+ if (!IsUserAuthorized())
+ throw new InvalidOperationException("Authorize user first!");
+
+ var req = new TLRequestGetContacts() { Hash = "" };
+
+ return await SendRequestAsync(req);
+ }
+
+ public async Task SendMessageAsync(TLAbsInputPeer peer, string message)
+ {
+ if (!IsUserAuthorized())
+ throw new InvalidOperationException("Authorize user first!");
+
+ return await SendRequestAsync(
+ new TLRequestSendMessage()
+ {
+ Peer = peer,
+ Message = message,
+ RandomId = Helpers.GenerateRandomLong()
+ });
+ }
+
+ public async Task SendTypingAsync(TLAbsInputPeer peer)
+ {
+ var req = new TLRequestSetTyping()
+ {
+ Action = new TLSendMessageTypingAction(),
+ Peer = peer
+ };
+ return await SendRequestAsync(req);
+ }
+
+ public async Task GetUserDialogsAsync(int offset = 0)
+ {
+ var peer = new TLInputPeerSelf();
+ return await SendRequestAsync(
+ new TLRequestGetDialogs() { OffsetDate = offset, OffsetPeer = peer, Limit = 100 });
+ }
+
+ public async Task SendUploadedPhoto(TLAbsInputPeer peer, TLAbsInputFile file, string caption)
+ {
+ return await SendRequestAsync(new TLRequestSendMedia()
+ {
+ RandomId = Helpers.GenerateRandomLong(),
+ Background = false,
+ ClearDraft = false,
+ Media = new TLInputMediaUploadedPhoto() { File = file, Caption = caption },
+ Peer = peer
+ });
+ }
+
+ public async Task SendUploadedDocument(
+ TLAbsInputPeer peer, TLAbsInputFile file, string caption, string mimeType, TLVector attributes)
+ {
+ return await SendRequestAsync(new TLRequestSendMedia()
+ {
+ RandomId = Helpers.GenerateRandomLong(),
+ Background = false,
+ ClearDraft = false,
+ Media = new TLInputMediaUploadedDocument()
+ {
+ File = file,
+ Caption = caption,
+ MimeType = mimeType,
+ Attributes = attributes
+ },
+ Peer = peer
+ });
+ }
+
+ public async Task GetFile(TLAbsInputFileLocation location, int filePartSize, int offset = 0)
+ {
+ TLFile result = null;
+ result = await SendRequestAsync(new TLRequestGetFile()
+ {
+ Location = location,
+ Limit = filePartSize,
+ Offset = offset
+ });
+ return result;
+ }
+
+ public async Task SendPingAsync()
+ {
+ await _sender.SendPingAsync();
}
public async Task GetHistoryAsync(TLAbsInputPeer peer, int offset, int max_id, int limit)
@@ -329,31 +391,31 @@ public async Task GetHistoryAsync(TLAbsInputPeer peer, int offset
Limit = limit
};
return await SendRequestAsync(req);
- }
-
- ///
- /// Serch user or chat. API: contacts.search#11f812d8 q:string limit:int = contacts.Found;
- ///
- /// User or chat name
- /// Max result count
- ///
- public async Task SearchUserAsync(string q, int limit = 10)
- {
- var r = new TeleSharp.TL.Contacts.TLRequestSearch
- {
- Q = q,
- Limit = limit
- };
-
- return await SendRequestAsync(r);
- }
-
- private void OnUserAuthenticated(TLUser TLUser)
- {
- _session.TLUser = TLUser;
- _session.SessionExpires = int.MaxValue;
-
- _session.Save();
+ }
+
+ ///
+ /// Serch user or chat. API: contacts.search#11f812d8 q:string limit:int = contacts.Found;
+ ///
+ /// User or chat name
+ /// Max result count
+ ///
+ public async Task SearchUserAsync(string q, int limit = 10)
+ {
+ var r = new TeleSharp.TL.Contacts.TLRequestSearch
+ {
+ Q = q,
+ Limit = limit
+ };
+
+ return await SendRequestAsync(r);
+ }
+
+ private void OnUserAuthenticated(TLUser TLUser)
+ {
+ _session.TLUser = TLUser;
+ _session.SessionExpires = int.MaxValue;
+
+ _session.Save();
}
public bool IsConnected
@@ -366,32 +428,32 @@ public bool IsConnected
}
}
- public void Dispose()
- {
- if (_transport != null)
- {
- _transport.Dispose();
- _transport = null;
- }
- }
- }
-
- public class MissingApiConfigurationException : Exception
- {
- public const string InfoUrl = "https://github.com/sochix/TLSharp#quick-configuration";
-
- internal MissingApiConfigurationException(string invalidParamName) :
- base($"Your {invalidParamName} setting is missing. Adjust the configuration first, see {InfoUrl}")
- {
- }
- }
-
- public class InvalidPhoneCodeException : Exception
- {
- internal InvalidPhoneCodeException(string msg) : base(msg) { }
- }
- public class CloudPasswordNeededException : Exception
- {
- internal CloudPasswordNeededException(string msg) : base(msg) { }
- }
-}
+ public void Dispose()
+ {
+ if (_transport != null)
+ {
+ _transport.Dispose();
+ _transport = null;
+ }
+ }
+ }
+
+ public class MissingApiConfigurationException : Exception
+ {
+ public const string InfoUrl = "https://github.com/sochix/TLSharp#quick-configuration";
+
+ internal MissingApiConfigurationException(string invalidParamName) :
+ base($"Your {invalidParamName} setting is missing. Adjust the configuration first, see {InfoUrl}")
+ {
+ }
+ }
+
+ public class InvalidPhoneCodeException : Exception
+ {
+ internal InvalidPhoneCodeException(string msg) : base(msg) { }
+ }
+ public class CloudPasswordNeededException : Exception
+ {
+ internal CloudPasswordNeededException(string msg) : base(msg) { }
+ }
+}
diff --git a/TLSharp.Core/packages.config b/TLSharp.Core/packages.config
index 00518560..518c7dc2 100644
--- a/TLSharp.Core/packages.config
+++ b/TLSharp.Core/packages.config
@@ -1,5 +1,5 @@
-
+
\ No newline at end of file
diff --git a/TLSharp.Tests.NUnit/TLSharp.Tests.NUnit.csproj b/TLSharp.Tests.NUnit/TLSharp.Tests.NUnit.csproj
index e947f4e9..761b473b 100644
--- a/TLSharp.Tests.NUnit/TLSharp.Tests.NUnit.csproj
+++ b/TLSharp.Tests.NUnit/TLSharp.Tests.NUnit.csproj
@@ -30,7 +30,7 @@
- ..\packages\NUnit.2.6.4\lib\nunit.framework.dll
+ ..\packages\NUnit.3.9.0\lib\net45\nunit.framework.dll
@@ -40,11 +40,9 @@
app.config
-
-
-
+
{DE5C0467-EE99-4734-95F2-EFF7A0B99924}
diff --git a/TLSharp.Tests.NUnit/packages.config b/TLSharp.Tests.NUnit/packages.config
index c714ef3a..967e817f 100644
--- a/TLSharp.Tests.NUnit/packages.config
+++ b/TLSharp.Tests.NUnit/packages.config
@@ -1,4 +1,4 @@
-
+
\ No newline at end of file
diff --git a/TeleSharp.Generator/Result.cs b/TeleSharp.Generator/Result.cs
new file mode 100644
index 00000000..e69de29b
diff --git a/TeleSharp.Generator/TeleSharp.Generator.csproj b/TeleSharp.Generator/TeleSharp.Generator.csproj
index 94f3e9b3..b90bc141 100644
--- a/TeleSharp.Generator/TeleSharp.Generator.csproj
+++ b/TeleSharp.Generator/TeleSharp.Generator.csproj
@@ -33,10 +33,6 @@
4
-
- ..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll
- True
-
@@ -45,6 +41,9 @@
+
+ ..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll
+
diff --git a/TeleSharp.Generator/packages.config b/TeleSharp.Generator/packages.config
index 9d64bf36..ee51c237 100644
--- a/TeleSharp.Generator/packages.config
+++ b/TeleSharp.Generator/packages.config
@@ -1,4 +1,4 @@
-
+
\ No newline at end of file
diff --git a/TeleSharp.Generator/schema.json b/TeleSharp.Generator/schema.json
new file mode 100644
index 00000000..9ed6a48b
--- /dev/null
+++ b/TeleSharp.Generator/schema.json
@@ -0,0 +1,14451 @@
+{
+ "constructors": [
+ {
+ "id": "-1132882121",
+ "predicate": "boolFalse",
+ "params": [],
+ "type": "Bool"
+ },
+ {
+ "id": "-1720552011",
+ "predicate": "boolTrue",
+ "params": [],
+ "type": "Bool"
+ },
+ {
+ "id": "-994444869",
+ "predicate": "error",
+ "params": [
+ {
+ "name": "code",
+ "type": "int"
+ },
+ {
+ "name": "text",
+ "type": "string"
+ }
+ ],
+ "type": "Error"
+ },
+ {
+ "id": "1450380236",
+ "predicate": "null",
+ "params": [],
+ "type": "Null"
+ },
+ {
+ "id": "2134579434",
+ "predicate": "inputPeerEmpty",
+ "params": [],
+ "type": "InputPeer"
+ },
+ {
+ "id": "2107670217",
+ "predicate": "inputPeerSelf",
+ "params": [],
+ "type": "InputPeer"
+ },
+ {
+ "id": "396093539",
+ "predicate": "inputPeerChat",
+ "params": [
+ {
+ "name": "chat_id",
+ "type": "int"
+ }
+ ],
+ "type": "InputPeer"
+ },
+ {
+ "id": "-1182234929",
+ "predicate": "inputUserEmpty",
+ "params": [],
+ "type": "InputUser"
+ },
+ {
+ "id": "-138301121",
+ "predicate": "inputUserSelf",
+ "params": [],
+ "type": "InputUser"
+ },
+ {
+ "id": "-208488460",
+ "predicate": "inputPhoneContact",
+ "params": [
+ {
+ "name": "client_id",
+ "type": "long"
+ },
+ {
+ "name": "phone",
+ "type": "string"
+ },
+ {
+ "name": "first_name",
+ "type": "string"
+ },
+ {
+ "name": "last_name",
+ "type": "string"
+ }
+ ],
+ "type": "InputContact"
+ },
+ {
+ "id": "-181407105",
+ "predicate": "inputFile",
+ "params": [
+ {
+ "name": "id",
+ "type": "long"
+ },
+ {
+ "name": "parts",
+ "type": "int"
+ },
+ {
+ "name": "name",
+ "type": "string"
+ },
+ {
+ "name": "md5_checksum",
+ "type": "string"
+ }
+ ],
+ "type": "InputFile"
+ },
+ {
+ "id": "-1771768449",
+ "predicate": "inputMediaEmpty",
+ "params": [],
+ "type": "InputMedia"
+ },
+ {
+ "id": "792191537",
+ "predicate": "inputMediaUploadedPhoto",
+ "params": [
+ {
+ "name": "flags",
+ "type": "#"
+ },
+ {
+ "name": "file",
+ "type": "InputFile"
+ },
+ {
+ "name": "caption",
+ "type": "string"
+ },
+ {
+ "name": "stickers",
+ "type": "flags.0?Vector"
+ },
+ {
+ "name": "ttl_seconds",
+ "type": "flags.1?int"
+ }
+ ],
+ "type": "InputMedia"
+ },
+ {
+ "id": "-2114308294",
+ "predicate": "inputMediaPhoto",
+ "params": [
+ {
+ "name": "flags",
+ "type": "#"
+ },
+ {
+ "name": "id",
+ "type": "InputPhoto"
+ },
+ {
+ "name": "caption",
+ "type": "string"
+ },
+ {
+ "name": "ttl_seconds",
+ "type": "flags.0?int"
+ }
+ ],
+ "type": "InputMedia"
+ },
+ {
+ "id": "-104578748",
+ "predicate": "inputMediaGeoPoint",
+ "params": [
+ {
+ "name": "geo_point",
+ "type": "InputGeoPoint"
+ }
+ ],
+ "type": "InputMedia"
+ },
+ {
+ "id": "-1494984313",
+ "predicate": "inputMediaContact",
+ "params": [
+ {
+ "name": "phone_number",
+ "type": "string"
+ },
+ {
+ "name": "first_name",
+ "type": "string"
+ },
+ {
+ "name": "last_name",
+ "type": "string"
+ }
+ ],
+ "type": "InputMedia"
+ },
+ {
+ "id": "480546647",
+ "predicate": "inputChatPhotoEmpty",
+ "params": [],
+ "type": "InputChatPhoto"
+ },
+ {
+ "id": "-1837345356",
+ "predicate": "inputChatUploadedPhoto",
+ "params": [
+ {
+ "name": "file",
+ "type": "InputFile"
+ }
+ ],
+ "type": "InputChatPhoto"
+ },
+ {
+ "id": "-1991004873",
+ "predicate": "inputChatPhoto",
+ "params": [
+ {
+ "name": "id",
+ "type": "InputPhoto"
+ }
+ ],
+ "type": "InputChatPhoto"
+ },
+ {
+ "id": "-457104426",
+ "predicate": "inputGeoPointEmpty",
+ "params": [],
+ "type": "InputGeoPoint"
+ },
+ {
+ "id": "-206066487",
+ "predicate": "inputGeoPoint",
+ "params": [
+ {
+ "name": "lat",
+ "type": "double"
+ },
+ {
+ "name": "long",
+ "type": "double"
+ }
+ ],
+ "type": "InputGeoPoint"
+ },
+ {
+ "id": "483901197",
+ "predicate": "inputPhotoEmpty",
+ "params": [],
+ "type": "InputPhoto"
+ },
+ {
+ "id": "-74070332",
+ "predicate": "inputPhoto",
+ "params": [
+ {
+ "name": "id",
+ "type": "long"
+ },
+ {
+ "name": "access_hash",
+ "type": "long"
+ }
+ ],
+ "type": "InputPhoto"
+ },
+ {
+ "id": "342061462",
+ "predicate": "inputFileLocation",
+ "params": [
+ {
+ "name": "volume_id",
+ "type": "long"
+ },
+ {
+ "name": "local_id",
+ "type": "int"
+ },
+ {
+ "name": "secret",
+ "type": "long"
+ }
+ ],
+ "type": "InputFileLocation"
+ },
+ {
+ "id": "1996904104",
+ "predicate": "inputAppEvent",
+ "params": [
+ {
+ "name": "time",
+ "type": "double"
+ },
+ {
+ "name": "type",
+ "type": "string"
+ },
+ {
+ "name": "peer",
+ "type": "long"
+ },
+ {
+ "name": "data",
+ "type": "string"
+ }
+ ],
+ "type": "InputAppEvent"
+ },
+ {
+ "id": "-1649296275",
+ "predicate": "peerUser",
+ "params": [
+ {
+ "name": "user_id",
+ "type": "int"
+ }
+ ],
+ "type": "Peer"
+ },
+ {
+ "id": "-1160714821",
+ "predicate": "peerChat",
+ "params": [
+ {
+ "name": "chat_id",
+ "type": "int"
+ }
+ ],
+ "type": "Peer"
+ },
+ {
+ "id": "-1432995067",
+ "predicate": "storage.fileUnknown",
+ "params": [],
+ "type": "storage.FileType"
+ },
+ {
+ "id": "8322574",
+ "predicate": "storage.fileJpeg",
+ "params": [],
+ "type": "storage.FileType"
+ },
+ {
+ "id": "-891180321",
+ "predicate": "storage.fileGif",
+ "params": [],
+ "type": "storage.FileType"
+ },
+ {
+ "id": "172975040",
+ "predicate": "storage.filePng",
+ "params": [],
+ "type": "storage.FileType"
+ },
+ {
+ "id": "1384777335",
+ "predicate": "storage.fileMp3",
+ "params": [],
+ "type": "storage.FileType"
+ },
+ {
+ "id": "1258941372",
+ "predicate": "storage.fileMov",
+ "params": [],
+ "type": "storage.FileType"
+ },
+ {
+ "id": "1086091090",
+ "predicate": "storage.filePartial",
+ "params": [],
+ "type": "storage.FileType"
+ },
+ {
+ "id": "-1278304028",
+ "predicate": "storage.fileMp4",
+ "params": [],
+ "type": "storage.FileType"
+ },
+ {
+ "id": "276907596",
+ "predicate": "storage.fileWebp",
+ "params": [],
+ "type": "storage.FileType"
+ },
+ {
+ "id": "2086234950",
+ "predicate": "fileLocationUnavailable",
+ "params": [
+ {
+ "name": "volume_id",
+ "type": "long"
+ },
+ {
+ "name": "local_id",
+ "type": "int"
+ },
+ {
+ "name": "secret",
+ "type": "long"
+ }
+ ],
+ "type": "FileLocation"
+ },
+ {
+ "id": "1406570614",
+ "predicate": "fileLocation",
+ "params": [
+ {
+ "name": "dc_id",
+ "type": "int"
+ },
+ {
+ "name": "volume_id",
+ "type": "long"
+ },
+ {
+ "name": "local_id",
+ "type": "int"
+ },
+ {
+ "name": "secret",
+ "type": "long"
+ }
+ ],
+ "type": "FileLocation"
+ },
+ {
+ "id": "537022650",
+ "predicate": "userEmpty",
+ "params": [
+ {
+ "name": "id",
+ "type": "int"
+ }
+ ],
+ "type": "User"
+ },
+ {
+ "id": "1326562017",
+ "predicate": "userProfilePhotoEmpty",
+ "params": [],
+ "type": "UserProfilePhoto"
+ },
+ {
+ "id": "-715532088",
+ "predicate": "userProfilePhoto",
+ "params": [
+ {
+ "name": "photo_id",
+ "type": "long"
+ },
+ {
+ "name": "photo_small",
+ "type": "FileLocation"
+ },
+ {
+ "name": "photo_big",
+ "type": "FileLocation"
+ }
+ ],
+ "type": "UserProfilePhoto"
+ },
+ {
+ "id": "164646985",
+ "predicate": "userStatusEmpty",
+ "params": [],
+ "type": "UserStatus"
+ },
+ {
+ "id": "-306628279",
+ "predicate": "userStatusOnline",
+ "params": [
+ {
+ "name": "expires",
+ "type": "int"
+ }
+ ],
+ "type": "UserStatus"
+ },
+ {
+ "id": "9203775",
+ "predicate": "userStatusOffline",
+ "params": [
+ {
+ "name": "was_online",
+ "type": "int"
+ }
+ ],
+ "type": "UserStatus"
+ },
+ {
+ "id": "-1683826688",
+ "predicate": "chatEmpty",
+ "params": [
+ {
+ "name": "id",
+ "type": "int"
+ }
+ ],
+ "type": "Chat"
+ },
+ {
+ "id": "-652419756",
+ "predicate": "chat",
+ "params": [
+ {
+ "name": "flags",
+ "type": "#"
+ },
+ {
+ "name": "creator",
+ "type": "flags.0?true"
+ },
+ {
+ "name": "kicked",
+ "type": "flags.1?true"
+ },
+ {
+ "name": "left",
+ "type": "flags.2?true"
+ },
+ {
+ "name": "admins_enabled",
+ "type": "flags.3?true"
+ },
+ {
+ "name": "admin",
+ "type": "flags.4?true"
+ },
+ {
+ "name": "deactivated",
+ "type": "flags.5?true"
+ },
+ {
+ "name": "id",
+ "type": "int"
+ },
+ {
+ "name": "title",
+ "type": "string"
+ },
+ {
+ "name": "photo",
+ "type": "ChatPhoto"
+ },
+ {
+ "name": "participants_count",
+ "type": "int"
+ },
+ {
+ "name": "date",
+ "type": "int"
+ },
+ {
+ "name": "version",
+ "type": "int"
+ },
+ {
+ "name": "migrated_to",
+ "type": "flags.6?InputChannel"
+ }
+ ],
+ "type": "Chat"
+ },
+ {
+ "id": "120753115",
+ "predicate": "chatForbidden",
+ "params": [
+ {
+ "name": "id",
+ "type": "int"
+ },
+ {
+ "name": "title",
+ "type": "string"
+ }
+ ],
+ "type": "Chat"
+ },
+ {
+ "id": "771925524",
+ "predicate": "chatFull",
+ "params": [
+ {
+ "name": "id",
+ "type": "int"
+ },
+ {
+ "name": "participants",
+ "type": "ChatParticipants"
+ },
+ {
+ "name": "chat_photo",
+ "type": "Photo"
+ },
+ {
+ "name": "notify_settings",
+ "type": "PeerNotifySettings"
+ },
+ {
+ "name": "exported_invite",
+ "type": "ExportedChatInvite"
+ },
+ {
+ "name": "bot_info",
+ "type": "Vector"
+ }
+ ],
+ "type": "ChatFull"
+ },
+ {
+ "id": "-925415106",
+ "predicate": "chatParticipant",
+ "params": [
+ {
+ "name": "user_id",
+ "type": "int"
+ },
+ {
+ "name": "inviter_id",
+ "type": "int"
+ },
+ {
+ "name": "date",
+ "type": "int"
+ }
+ ],
+ "type": "ChatParticipant"
+ },
+ {
+ "id": "-57668565",
+ "predicate": "chatParticipantsForbidden",
+ "params": [
+ {
+ "name": "flags",
+ "type": "#"
+ },
+ {
+ "name": "chat_id",
+ "type": "int"
+ },
+ {
+ "name": "self_participant",
+ "type": "flags.0?ChatParticipant"
+ }
+ ],
+ "type": "ChatParticipants"
+ },
+ {
+ "id": "1061556205",
+ "predicate": "chatParticipants",
+ "params": [
+ {
+ "name": "chat_id",
+ "type": "int"
+ },
+ {
+ "name": "participants",
+ "type": "Vector"
+ },
+ {
+ "name": "version",
+ "type": "int"
+ }
+ ],
+ "type": "ChatParticipants"
+ },
+ {
+ "id": "935395612",
+ "predicate": "chatPhotoEmpty",
+ "params": [],
+ "type": "ChatPhoto"
+ },
+ {
+ "id": "1632839530",
+ "predicate": "chatPhoto",
+ "params": [
+ {
+ "name": "photo_small",
+ "type": "FileLocation"
+ },
+ {
+ "name": "photo_big",
+ "type": "FileLocation"
+ }
+ ],
+ "type": "ChatPhoto"
+ },
+ {
+ "id": "-2082087340",
+ "predicate": "messageEmpty",
+ "params": [
+ {
+ "name": "id",
+ "type": "int"
+ }
+ ],
+ "type": "Message"
+ },
+ {
+ "id": "1157215293",
+ "predicate": "message",
+ "params": [
+ {
+ "name": "flags",
+ "type": "#"
+ },
+ {
+ "name": "out",
+ "type": "flags.1?true"
+ },
+ {
+ "name": "mentioned",
+ "type": "flags.4?true"
+ },
+ {
+ "name": "media_unread",
+ "type": "flags.5?true"
+ },
+ {
+ "name": "silent",
+ "type": "flags.13?true"
+ },
+ {
+ "name": "post",
+ "type": "flags.14?true"
+ },
+ {
+ "name": "id",
+ "type": "int"
+ },
+ {
+ "name": "from_id",
+ "type": "flags.8?int"
+ },
+ {
+ "name": "to_id",
+ "type": "Peer"
+ },
+ {
+ "name": "fwd_from",
+ "type": "flags.2?MessageFwdHeader"
+ },
+ {
+ "name": "via_bot_id",
+ "type": "flags.11?int"
+ },
+ {
+ "name": "reply_to_msg_id",
+ "type": "flags.3?int"
+ },
+ {
+ "name": "date",
+ "type": "int"
+ },
+ {
+ "name": "message",
+ "type": "string"
+ },
+ {
+ "name": "media",
+ "type": "flags.9?MessageMedia"
+ },
+ {
+ "name": "reply_markup",
+ "type": "flags.6?ReplyMarkup"
+ },
+ {
+ "name": "entities",
+ "type": "flags.7?Vector"
+ },
+ {
+ "name": "views",
+ "type": "flags.10?int"
+ },
+ {
+ "name": "edit_date",
+ "type": "flags.15?int"
+ },
+ {
+ "name": "post_author",
+ "type": "flags.16?string"
+ },
+ {
+ "name": "grouped_id",
+ "type": "flags.17?long"
+ }
+ ],
+ "type": "Message"
+ },
+ {
+ "id": "-1642487306",
+ "predicate": "messageService",
+ "params": [
+ {
+ "name": "flags",
+ "type": "#"
+ },
+ {
+ "name": "out",
+ "type": "flags.1?true"
+ },
+ {
+ "name": "mentioned",
+ "type": "flags.4?true"
+ },
+ {
+ "name": "media_unread",
+ "type": "flags.5?true"
+ },
+ {
+ "name": "silent",
+ "type": "flags.13?true"
+ },
+ {
+ "name": "post",
+ "type": "flags.14?true"
+ },
+ {
+ "name": "id",
+ "type": "int"
+ },
+ {
+ "name": "from_id",
+ "type": "flags.8?int"
+ },
+ {
+ "name": "to_id",
+ "type": "Peer"
+ },
+ {
+ "name": "reply_to_msg_id",
+ "type": "flags.3?int"
+ },
+ {
+ "name": "date",
+ "type": "int"
+ },
+ {
+ "name": "action",
+ "type": "MessageAction"
+ }
+ ],
+ "type": "Message"
+ },
+ {
+ "id": "1038967584",
+ "predicate": "messageMediaEmpty",
+ "params": [],
+ "type": "MessageMedia"
+ },
+ {
+ "id": "-1256047857",
+ "predicate": "messageMediaPhoto",
+ "params": [
+ {
+ "name": "flags",
+ "type": "#"
+ },
+ {
+ "name": "photo",
+ "type": "flags.0?Photo"
+ },
+ {
+ "name": "caption",
+ "type": "flags.1?string"
+ },
+ {
+ "name": "ttl_seconds",
+ "type": "flags.2?int"
+ }
+ ],
+ "type": "MessageMedia"
+ },
+ {
+ "id": "1457575028",
+ "predicate": "messageMediaGeo",
+ "params": [
+ {
+ "name": "geo",
+ "type": "GeoPoint"
+ }
+ ],
+ "type": "MessageMedia"
+ },
+ {
+ "id": "1585262393",
+ "predicate": "messageMediaContact",
+ "params": [
+ {
+ "name": "phone_number",
+ "type": "string"
+ },
+ {
+ "name": "first_name",
+ "type": "string"
+ },
+ {
+ "name": "last_name",
+ "type": "string"
+ },
+ {
+ "name": "user_id",
+ "type": "int"
+ }
+ ],
+ "type": "MessageMedia"
+ },
+ {
+ "id": "-1618676578",
+ "predicate": "messageMediaUnsupported",
+ "params": [],
+ "type": "MessageMedia"
+ },
+ {
+ "id": "-1230047312",
+ "predicate": "messageActionEmpty",
+ "params": [],
+ "type": "MessageAction"
+ },
+ {
+ "id": "-1503425638",
+ "predicate": "messageActionChatCreate",
+ "params": [
+ {
+ "name": "title",
+ "type": "string"
+ },
+ {
+ "name": "users",
+ "type": "Vector"
+ }
+ ],
+ "type": "MessageAction"
+ },
+ {
+ "id": "-1247687078",
+ "predicate": "messageActionChatEditTitle",
+ "params": [
+ {
+ "name": "title",
+ "type": "string"
+ }
+ ],
+ "type": "MessageAction"
+ },
+ {
+ "id": "2144015272",
+ "predicate": "messageActionChatEditPhoto",
+ "params": [
+ {
+ "name": "photo",
+ "type": "Photo"
+ }
+ ],
+ "type": "MessageAction"
+ },
+ {
+ "id": "-1780220945",
+ "predicate": "messageActionChatDeletePhoto",
+ "params": [],
+ "type": "MessageAction"
+ },
+ {
+ "id": "1217033015",
+ "predicate": "messageActionChatAddUser",
+ "params": [
+ {
+ "name": "users",
+ "type": "Vector"
+ }
+ ],
+ "type": "MessageAction"
+ },
+ {
+ "id": "-1297179892",
+ "predicate": "messageActionChatDeleteUser",
+ "params": [
+ {
+ "name": "user_id",
+ "type": "int"
+ }
+ ],
+ "type": "MessageAction"
+ },
+ {
+ "id": "-455150117",
+ "predicate": "dialog",
+ "params": [
+ {
+ "name": "flags",
+ "type": "#"
+ },
+ {
+ "name": "pinned",
+ "type": "flags.2?true"
+ },
+ {
+ "name": "peer",
+ "type": "Peer"
+ },
+ {
+ "name": "top_message",
+ "type": "int"
+ },
+ {
+ "name": "read_inbox_max_id",
+ "type": "int"
+ },
+ {
+ "name": "read_outbox_max_id",
+ "type": "int"
+ },
+ {
+ "name": "unread_count",
+ "type": "int"
+ },
+ {
+ "name": "unread_mentions_count",
+ "type": "int"
+ },
+ {
+ "name": "notify_settings",
+ "type": "PeerNotifySettings"
+ },
+ {
+ "name": "pts",
+ "type": "flags.0?int"
+ },
+ {
+ "name": "draft",
+ "type": "flags.1?DraftMessage"
+ }
+ ],
+ "type": "Dialog"
+ },
+ {
+ "id": "590459437",
+ "predicate": "photoEmpty",
+ "params": [
+ {
+ "name": "id",
+ "type": "long"
+ }
+ ],
+ "type": "Photo"
+ },
+ {
+ "id": "-1836524247",
+ "predicate": "photo",
+ "params": [
+ {
+ "name": "flags",
+ "type": "#"
+ },
+ {
+ "name": "has_stickers",
+ "type": "flags.0?true"
+ },
+ {
+ "name": "id",
+ "type": "long"
+ },
+ {
+ "name": "access_hash",
+ "type": "long"
+ },
+ {
+ "name": "date",
+ "type": "int"
+ },
+ {
+ "name": "sizes",
+ "type": "Vector"
+ }
+ ],
+ "type": "Photo"
+ },
+ {
+ "id": "236446268",
+ "predicate": "photoSizeEmpty",
+ "params": [
+ {
+ "name": "type",
+ "type": "string"
+ }
+ ],
+ "type": "PhotoSize"
+ },
+ {
+ "id": "2009052699",
+ "predicate": "photoSize",
+ "params": [
+ {
+ "name": "type",
+ "type": "string"
+ },
+ {
+ "name": "location",
+ "type": "FileLocation"
+ },
+ {
+ "name": "w",
+ "type": "int"
+ },
+ {
+ "name": "h",
+ "type": "int"
+ },
+ {
+ "name": "size",
+ "type": "int"
+ }
+ ],
+ "type": "PhotoSize"
+ },
+ {
+ "id": "-374917894",
+ "predicate": "photoCachedSize",
+ "params": [
+ {
+ "name": "type",
+ "type": "string"
+ },
+ {
+ "name": "location",
+ "type": "FileLocation"
+ },
+ {
+ "name": "w",
+ "type": "int"
+ },
+ {
+ "name": "h",
+ "type": "int"
+ },
+ {
+ "name": "bytes",
+ "type": "bytes"
+ }
+ ],
+ "type": "PhotoSize"
+ },
+ {
+ "id": "286776671",
+ "predicate": "geoPointEmpty",
+ "params": [],
+ "type": "GeoPoint"
+ },
+ {
+ "id": "541710092",
+ "predicate": "geoPoint",
+ "params": [
+ {
+ "name": "long",
+ "type": "double"
+ },
+ {
+ "name": "lat",
+ "type": "double"
+ }
+ ],
+ "type": "GeoPoint"
+ },
+ {
+ "id": "-2128698738",
+ "predicate": "auth.checkedPhone",
+ "params": [
+ {
+ "name": "phone_registered",
+ "type": "Bool"
+ }
+ ],
+ "type": "auth.CheckedPhone"
+ },
+ {
+ "id": "1577067778",
+ "predicate": "auth.sentCode",
+ "params": [
+ {
+ "name": "flags",
+ "type": "#"
+ },
+ {
+ "name": "phone_registered",
+ "type": "flags.0?true"
+ },
+ {
+ "name": "type",
+ "type": "auth.SentCodeType"
+ },
+ {
+ "name": "phone_code_hash",
+ "type": "string"
+ },
+ {
+ "name": "next_type",
+ "type": "flags.1?auth.CodeType"
+ },
+ {
+ "name": "timeout",
+ "type": "flags.2?int"
+ }
+ ],
+ "type": "auth.SentCode"
+ },
+ {
+ "id": "-855308010",
+ "predicate": "auth.authorization",
+ "params": [
+ {
+ "name": "flags",
+ "type": "#"
+ },
+ {
+ "name": "tmp_sessions",
+ "type": "flags.0?int"
+ },
+ {
+ "name": "user",
+ "type": "User"
+ }
+ ],
+ "type": "auth.Authorization"
+ },
+ {
+ "id": "-543777747",
+ "predicate": "auth.exportedAuthorization",
+ "params": [
+ {
+ "name": "id",
+ "type": "int"
+ },
+ {
+ "name": "bytes",
+ "type": "bytes"
+ }
+ ],
+ "type": "auth.ExportedAuthorization"
+ },
+ {
+ "id": "-1195615476",
+ "predicate": "inputNotifyPeer",
+ "params": [
+ {
+ "name": "peer",
+ "type": "InputPeer"
+ }
+ ],
+ "type": "InputNotifyPeer"
+ },
+ {
+ "id": "423314455",
+ "predicate": "inputNotifyUsers",
+ "params": [],
+ "type": "InputNotifyPeer"
+ },
+ {
+ "id": "1251338318",
+ "predicate": "inputNotifyChats",
+ "params": [],
+ "type": "InputNotifyPeer"
+ },
+ {
+ "id": "-1540769658",
+ "predicate": "inputNotifyAll",
+ "params": [],
+ "type": "InputNotifyPeer"
+ },
+ {
+ "id": "949182130",
+ "predicate": "inputPeerNotifySettings",
+ "params": [
+ {
+ "name": "flags",
+ "type": "#"
+ },
+ {
+ "name": "show_previews",
+ "type": "flags.0?true"
+ },
+ {
+ "name": "silent",
+ "type": "flags.1?true"
+ },
+ {
+ "name": "mute_until",
+ "type": "int"
+ },
+ {
+ "name": "sound",
+ "type": "string"
+ }
+ ],
+ "type": "InputPeerNotifySettings"
+ },
+ {
+ "id": "-1378534221",
+ "predicate": "peerNotifyEventsEmpty",
+ "params": [],
+ "type": "PeerNotifyEvents"
+ },
+ {
+ "id": "1830677896",
+ "predicate": "peerNotifyEventsAll",
+ "params": [],
+ "type": "PeerNotifyEvents"
+ },
+ {
+ "id": "1889961234",
+ "predicate": "peerNotifySettingsEmpty",
+ "params": [],
+ "type": "PeerNotifySettings"
+ },
+ {
+ "id": "-1697798976",
+ "predicate": "peerNotifySettings",
+ "params": [
+ {
+ "name": "flags",
+ "type": "#"
+ },
+ {
+ "name": "show_previews",
+ "type": "flags.0?true"
+ },
+ {
+ "name": "silent",
+ "type": "flags.1?true"
+ },
+ {
+ "name": "mute_until",
+ "type": "int"
+ },
+ {
+ "name": "sound",
+ "type": "string"
+ }
+ ],
+ "type": "PeerNotifySettings"
+ },
+ {
+ "id": "-860866985",
+ "predicate": "wallPaper",
+ "params": [
+ {
+ "name": "id",
+ "type": "int"
+ },
+ {
+ "name": "title",
+ "type": "string"
+ },
+ {
+ "name": "sizes",
+ "type": "Vector"
+ },
+ {
+ "name": "color",
+ "type": "int"
+ }
+ ],
+ "type": "WallPaper"
+ },
+ {
+ "id": "253890367",
+ "predicate": "userFull",
+ "params": [
+ {
+ "name": "flags",
+ "type": "#"
+ },
+ {
+ "name": "blocked",
+ "type": "flags.0?true"
+ },
+ {
+ "name": "phone_calls_available",
+ "type": "flags.4?true"
+ },
+ {
+ "name": "phone_calls_private",
+ "type": "flags.5?true"
+ },
+ {
+ "name": "user",
+ "type": "User"
+ },
+ {
+ "name": "about",
+ "type": "flags.1?string"
+ },
+ {
+ "name": "link",
+ "type": "contacts.Link"
+ },
+ {
+ "name": "profile_photo",
+ "type": "flags.2?Photo"
+ },
+ {
+ "name": "notify_settings",
+ "type": "PeerNotifySettings"
+ },
+ {
+ "name": "bot_info",
+ "type": "flags.3?BotInfo"
+ },
+ {
+ "name": "common_chats_count",
+ "type": "int"
+ }
+ ],
+ "type": "UserFull"
+ },
+ {
+ "id": "-116274796",
+ "predicate": "contact",
+ "params": [
+ {
+ "name": "user_id",
+ "type": "int"
+ },
+ {
+ "name": "mutual",
+ "type": "Bool"
+ }
+ ],
+ "type": "Contact"
+ },
+ {
+ "id": "-805141448",
+ "predicate": "importedContact",
+ "params": [
+ {
+ "name": "user_id",
+ "type": "int"
+ },
+ {
+ "name": "client_id",
+ "type": "long"
+ }
+ ],
+ "type": "ImportedContact"
+ },
+ {
+ "id": "1444661369",
+ "predicate": "contactBlocked",
+ "params": [
+ {
+ "name": "user_id",
+ "type": "int"
+ },
+ {
+ "name": "date",
+ "type": "int"
+ }
+ ],
+ "type": "ContactBlocked"
+ },
+ {
+ "id": "-748155807",
+ "predicate": "contactStatus",
+ "params": [
+ {
+ "name": "user_id",
+ "type": "int"
+ },
+ {
+ "name": "status",
+ "type": "UserStatus"
+ }
+ ],
+ "type": "ContactStatus"
+ },
+ {
+ "id": "986597452",
+ "predicate": "contacts.link",
+ "params": [
+ {
+ "name": "my_link",
+ "type": "ContactLink"
+ },
+ {
+ "name": "foreign_link",
+ "type": "ContactLink"
+ },
+ {
+ "name": "user",
+ "type": "User"
+ }
+ ],
+ "type": "contacts.Link"
+ },
+ {
+ "id": "-353862078",
+ "predicate": "contacts.contacts",
+ "params": [
+ {
+ "name": "contacts",
+ "type": "Vector"
+ },
+ {
+ "name": "saved_count",
+ "type": "int"
+ },
+ {
+ "name": "users",
+ "type": "Vector"
+ }
+ ],
+ "type": "contacts.Contacts"
+ },
+ {
+ "id": "-1219778094",
+ "predicate": "contacts.contactsNotModified",
+ "params": [],
+ "type": "contacts.Contacts"
+ },
+ {
+ "id": "2010127419",
+ "predicate": "contacts.importedContacts",
+ "params": [
+ {
+ "name": "imported",
+ "type": "Vector"
+ },
+ {
+ "name": "popular_invites",
+ "type": "Vector"
+ },
+ {
+ "name": "retry_contacts",
+ "type": "Vector"
+ },
+ {
+ "name": "users",
+ "type": "Vector"
+ }
+ ],
+ "type": "contacts.ImportedContacts"
+ },
+ {
+ "id": "471043349",
+ "predicate": "contacts.blocked",
+ "params": [
+ {
+ "name": "blocked",
+ "type": "Vector"
+ },
+ {
+ "name": "users",
+ "type": "Vector"
+ }
+ ],
+ "type": "contacts.Blocked"
+ },
+ {
+ "id": "-1878523231",
+ "predicate": "contacts.blockedSlice",
+ "params": [
+ {
+ "name": "count",
+ "type": "int"
+ },
+ {
+ "name": "blocked",
+ "type": "Vector"
+ },
+ {
+ "name": "users",
+ "type": "Vector"
+ }
+ ],
+ "type": "contacts.Blocked"
+ },
+ {
+ "id": "-1290580579",
+ "predicate": "contacts.found",
+ "params": [
+ {
+ "name": "my_results",
+ "type": "Vector"
+ },
+ {
+ "name": "results",
+ "type": "Vector"
+ },
+ {
+ "name": "chats",
+ "type": "Vector"
+ },
+ {
+ "name": "users",
+ "type": "Vector"
+ }
+ ],
+ "type": "contacts.Found"
+ },
+ {
+ "id": "364538944",
+ "predicate": "messages.dialogs",
+ "params": [
+ {
+ "name": "dialogs",
+ "type": "Vector