Move code to src folder

This commit is contained in:
Wizou 2021-08-07 06:25:51 +02:00
parent 9c76112be1
commit 4dd03c66a6
12 changed files with 0 additions and 0 deletions

565
src/Client.cs Normal file
View file

@ -0,0 +1,565 @@
using System;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using TL;
using static WTelegram.Encryption;
namespace WTelegram
{
public sealed class Client : IDisposable
{
public Config TLConfig { get; private set; }
private readonly Func<string, string> _config;
private readonly Func<ITLObject, Task> _updateHandler;
private readonly int _apiId;
private readonly string _apiHash;
private readonly Session _session;
private TcpClient _tcpClient;
private NetworkStream _networkStream;
private int _frame_seqTx = 0, _frame_seqRx = 0;
private ITLObject _lastSentMsg;
private Type _lastRpcResultType = typeof(object);
private readonly List<long> _msgsToAck = new();
private int _unexpectedSaltChange;
public Client(Func<string,string> configProvider = null, Func<ITLObject, Task> updateHandler = null)
{
_config = configProvider ?? DefaultConfigOrAsk;
_updateHandler = updateHandler;
_apiId = int.Parse(Config("api_id"));
_apiHash = Config("api_hash");
_session = Session.LoadOrCreate(Config("session_pathname"), Convert.FromHexString(_apiHash));
}
public string Config(string config)
=> _config(config) ?? DefaultConfig(config) ?? throw new ApplicationException("You must provide a config value for " + config);
public static string DefaultConfig(string config) => config switch
{
"session_pathname" => Path.Combine(
Path.GetDirectoryName(Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory.TrimEnd(Path.DirectorySeparatorChar))),
"WTelegram.session"),
#if DEBUG
"server_address" => "149.154.167.40:443",
#else
"server_address" => "149.154.167.50:443",
#endif
"device_model" => Environment.Is64BitOperatingSystem ? "PC 64bit" : "PC 32bit",
"system_version" => System.Runtime.InteropServices.RuntimeInformation.OSDescription,
"app_version" => Assembly.GetEntryAssembly().GetName().Version.ToString(),
"system_lang_code" => CultureInfo.InstalledUICulture.TwoLetterISOLanguageName,
"lang_pack" => "",
"lang_code" => CultureInfo.CurrentUICulture.TwoLetterISOLanguageName,
_ => null // api_id api_hash phone_number verification_code... it's up to you to reply to these correctly
};
public static string DefaultConfigOrAsk(string config)
{
var value = DefaultConfig(config);
if (value != null) return value;
Console.Write($"Enter {config.Replace('_', ' ')}: ");
return Console.ReadLine();
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1822")]
public void LoadPublicKey(string pem) => Encryption.LoadPublicKey(pem);
public void Reset() // disconnect and reset session (forget server address, current user and authkey)
{
_tcpClient.Close();
_session.Reset();
}
public async Task ConnectAsync()
{
var endpoint = _session.DataCenter == null ? IPEndPoint.Parse(Config("server_address"))
: new IPEndPoint(IPAddress.Parse(_session.DataCenter.ip_address), _session.DataCenter.port);
Helpers.Log(2, $"Connecting to {endpoint}...");
_tcpClient = new TcpClient(endpoint.AddressFamily);
await _tcpClient.ConnectAsync(endpoint.Address, endpoint.Port);
_networkStream = _tcpClient.GetStream();
_frame_seqTx = _frame_seqRx = 0;
if (_session.AuthKey == null)
await CreateAuthorizationKey(this, _session);
TLConfig = await CallAsync(new Fn.InvokeWithLayer<Config>
{
layer = Schema.Layer,
query = new Fn.InitConnection<Config>
{
api_id = _apiId,
device_model = Config("device_model"),
system_version = Config("system_version"),
app_version = Config("app_version"),
system_lang_code = Config("system_lang_code"),
lang_pack = Config("lang_pack"),
lang_code = Config("lang_code"),
query = new Fn.Help_GetConfig()
}
});
}
private async Task MigrateDCAsync(int dcId)
{
Helpers.Log(2, $"Migrate to DC {dcId}...");
Auth_ExportedAuthorization exported = null;
if (_session.User != null)
exported = await CallAsync(new Fn.Auth_ExportAuthorization { dc_id = dcId });
var prevFamily = _tcpClient.Client.RemoteEndPoint.AddressFamily;
_tcpClient.Close();
var dcOptions = TLConfig.dc_options.Where(dc => dc.id == dcId && (dc.flags & (DcOption.Flags.media_only | DcOption.Flags.cdn)) == 0);
if (prevFamily == AddressFamily.InterNetworkV6) // try to stay in the same connectivity
dcOptions = dcOptions.OrderByDescending(dc => dc.flags & DcOption.Flags.ipv6); // list ipv6 first
else
dcOptions = dcOptions.OrderBy(dc => dc.flags & DcOption.Flags.ipv6); // list ipv4 first
var dcOption = dcOptions.FirstOrDefault();
_session.Reset(dcOption ?? throw new ApplicationException($"Could not find adequate dcOption for DC {dcId}"));
await ConnectAsync();
if (exported != null)
{
var authorization = await CallAsync(new Fn.Auth_ImportAuthorization { id = exported.id, bytes = exported.bytes });
if (authorization is not Auth_Authorization { user: User user })
throw new ApplicationException("Failed to get Authorization: " + authorization.GetType().Name);
_session.User = Schema.Serialize(user);
}
}
public void Dispose()
{
CheckMsgsToAck().Wait(1000);
_networkStream?.Dispose();
_tcpClient?.Dispose();
}
public async Task SendAsync(ITLObject msg, bool isContent = true)
{
if (_session.AuthKeyID != 0) await CheckMsgsToAck();
using var memStream = new MemoryStream(1024);
using var writer = new BinaryWriter(memStream, Encoding.UTF8);
writer.Write(0); // int32 frame_len (to be patched with full frame length)
writer.Write(_frame_seqTx++); // int32 frame_seq
(long msgId, int seqno) = _session.NewMsg(isContent);
if (_session.AuthKeyID == 0) // send unencrypted message
{
Helpers.Log(1, $"Sending {msg.GetType().Name}...");
writer.Write(0L); // int64 auth_key_id = 0 (Unencrypted)
writer.Write(msgId); // int64 message_id
writer.Write(0); // int32 message_data_length (to be patched)
Schema.Serialize(writer, msg); // bytes message_data
BinaryPrimitives.WriteInt32LittleEndian(memStream.GetBuffer().AsSpan(24), (int)memStream.Length - 28); // patch message_data_length
}
else
{
Helpers.Log(1, $"Sending {msg.GetType().Name,-50} #{(short)msgId.GetHashCode():X4}");
//TODO: implement MTProto 2.0
using var clearStream = new MemoryStream(1024);
using var clearWriter = new BinaryWriter(clearStream, Encoding.UTF8);
clearWriter.Write(_session.Salt); // int64 salt
clearWriter.Write(_session.Id); // int64 session_id
clearWriter.Write(msgId); // int64 message_id
clearWriter.Write(seqno); // int32 msg_seqno
clearWriter.Write(0); // int32 message_data_length (to be patched)
Schema.Serialize(clearWriter, msg); // bytes message_data
int clearLength = (int)clearStream.Length; // length before padding (= 32 + message_data_length)
int padding = (0x7FFFFFF0 - clearLength) % 16;
clearStream.SetLength(clearLength + padding);
byte[] clearBuffer = clearStream.GetBuffer();
BinaryPrimitives.WriteInt32LittleEndian(clearBuffer.AsSpan(28), clearLength - 32); // patch message_data_length
RNG.GetBytes(clearBuffer, clearLength, padding);
var clearSha1 = SHA1.HashData(clearBuffer.AsSpan(0, clearLength)); // padding excluded from computation!
byte[] encrypted_data = EncryptDecryptMessage(clearBuffer.AsSpan(0, clearLength + padding), true, _session.AuthKey, clearSha1);
writer.Write(_session.AuthKeyID); // int64 auth_key_id
writer.Write(clearSha1, 4, 16); // int128 msg_key = low 128-bits of SHA1(clear message body)
writer.Write(encrypted_data); // bytes encrypted_data
}
var buffer = memStream.GetBuffer();
int frameLength = (int)memStream.Length;
//TODO: support Quick Ack?
BinaryPrimitives.WriteInt32LittleEndian(buffer, frameLength + 4); // patch frame_len with correct value
uint crc = Force.Crc32.Crc32Algorithm.Compute(buffer, 0, frameLength);
writer.Write(crc); // int32 frame_crc
var frame = memStream.GetBuffer().AsMemory(0, frameLength + 4);
//TODO: support Transport obfuscation?
await _networkStream.WriteAsync(frame);
_lastSentMsg = msg;
}
internal async Task<ITLObject> RecvInternalAsync()
{
var data = await RecvFrameAsync();
if (data.Length == 4 && data[3] == 0xFF)
{
int error_code = -BinaryPrimitives.ReadInt32LittleEndian(data);
throw new RpcException(error_code, TransportError(error_code));
}
if (data.Length < 24) // authKeyId+msgId+length+ctorNb | authKeyId+msgKey
throw new ApplicationException($"Packet payload too small: {data.Length}");
//TODO: ignore msgId <= lastRecvMsgId, ignore MsgId >= 30 sec in the future or < 300 sec in the past
long authKeyId = BinaryPrimitives.ReadInt64LittleEndian(data);
if (authKeyId == 0) // Unencrypted message
{
using var reader = new BinaryReader(new MemoryStream(data, 8, data.Length - 8));
long msgId = reader.ReadInt64();
if ((msgId & 1) == 0) throw new ApplicationException($"Invalid server msgId {msgId}");
int length = reader.ReadInt32();
if (length != data.Length - 20) throw new ApplicationException($"Unexpected unencrypted length {length} != {data.Length - 20}");
var ctorNb = reader.ReadUInt32();
if (!Schema.Table.TryGetValue(ctorNb, out var realType))
throw new ApplicationException($"Cannot find type for ctor #{ctorNb:x}");
Helpers.Log(1, $"Receiving {realType.Name,-50} timestamp={_session.MsgIdToStamp(msgId)} isResponse={(msgId & 2) != 0} unencrypted");
return Schema.DeserializeObject(reader, realType);
}
else if (authKeyId != _session.AuthKeyID)
throw new ApplicationException($"Received a packet encrypted with unexpected key {authKeyId:X}");
else
{
byte[] decrypted_data = EncryptDecryptMessage(data.AsSpan(24), false, _session.AuthKey, data[4..24]);
if (decrypted_data.Length < 36) // header below+ctorNb
throw new ApplicationException($"Decrypted packet too small: {decrypted_data.Length}");
using var reader = new BinaryReader(new MemoryStream(decrypted_data));
var serverSalt = reader.ReadInt64(); // int64 salt
var sessionId = reader.ReadInt64(); // int64 session_id
var msgId = reader.ReadInt64(); // int64 message_id
var seqno = reader.ReadInt32(); // int32 msg_seqno
var length = reader.ReadInt32(); // int32 message_data_length
if (serverSalt != _session.Salt)
{
Helpers.Log(3, $"Server salt has changed: {_session.Salt:X8} -> {serverSalt:X8}");
_session.Salt = serverSalt;
if (++_unexpectedSaltChange >= 10)
throw new ApplicationException($"Server salt changed unexpectedly more than 10 times during this run");
}
if (sessionId != _session.Id) throw new ApplicationException($"Unexpected session ID {_session.Id} != {_session.Id}");
if ((msgId & 1) == 0) throw new ApplicationException($"Invalid server msgId {msgId}");
if ((seqno & 1) != 0) lock(_msgsToAck) _msgsToAck.Add(msgId);
if (decrypted_data.Length - 32 - length is < 0 or > 15) throw new ApplicationException($"Unexpected decrypted message_data_length {length} / {decrypted_data.Length - 32}");
if (!data.AsSpan(8, 16).SequenceEqual(SHA1.HashData(decrypted_data.AsSpan(0, 32 + length)).AsSpan(4)))
throw new ApplicationException($"Mismatch between MsgKey & decrypted SHA1");
var ctorNb = reader.ReadUInt32();
if (!Schema.Table.TryGetValue(ctorNb, out var realType))
throw new ApplicationException($"Cannot find type for ctor #{ctorNb:x}");
Helpers.Log(1, $"Receiving {realType.Name,-50} timestamp={_session.MsgIdToStamp(msgId)} isResponse={(msgId & 2) != 0} {(seqno == -1 ? "clearText" : "isContent")}={(seqno & 1) != 0}");
if (realType == typeof(RpcResult))
return DeserializeRpcResult(reader); // necessary hack because some RPC return bare types like bool or int[]
else
return Schema.DeserializeObject(reader, realType);
}
static string TransportError(int error_code) => error_code switch
{
404 => "Auth key not found",
429 => "Transport flood",
_ => ((HttpStatusCode)error_code).ToString(),
};
}
private async Task<byte[]> RecvFrameAsync()
{
byte[] frame = new byte[8];
if (await FullReadAsync(_networkStream, frame, 8) != 8)
throw new ApplicationException("Could not read frame prefix : Connection shut down");
int length = BinaryPrimitives.ReadInt32LittleEndian(frame) - 12;
if (length <= 0 || length >= 0x10000)
throw new ApplicationException("Invalid frame_len");
int seqno = BinaryPrimitives.ReadInt32LittleEndian(frame.AsSpan(4));
if (seqno != _frame_seqRx++)
{
Trace.TraceWarning($"Unexpected frame_seq received: {seqno} instead of {_frame_seqRx}");
_frame_seqRx = seqno + 1;
}
var payload = new byte[length];
if (await FullReadAsync(_networkStream, payload, length) != length)
throw new ApplicationException("Could not read frame data : Connection shut down");
uint crc32 = Force.Crc32.Crc32Algorithm.Compute(frame, 0, 8);
crc32 = Force.Crc32.Crc32Algorithm.Append(crc32, payload);
if (await FullReadAsync(_networkStream, frame, 4) != 4)
throw new ApplicationException("Could not read frame CRC : Connection shut down");
if (crc32 != BinaryPrimitives.ReadUInt32LittleEndian(frame))
throw new ApplicationException("Invalid envelope CRC32");
return payload;
}
private static async Task<int> FullReadAsync(Stream stream, byte[] buffer, int length)
{
for (int offset = 0; offset != length;)
{
var read = await stream.ReadAsync(buffer.AsMemory(offset, length - offset));
if (read == 0) return offset;
offset += read;
}
return length;
}
private RpcResult DeserializeRpcResult(BinaryReader reader)
{
long reqMsgId = reader.ReadInt64();
var rpcResult = new RpcResult { req_msg_id = reqMsgId };
if (reqMsgId == _session.LastSentMsgId)
rpcResult.result = Schema.DeserializeValue(reader, _lastRpcResultType);
else
rpcResult.result = Schema.Deserialize<ITLObject>(reader);
Helpers.Log(1, $" → {rpcResult.result.GetType().Name,-50} #{(short)reqMsgId.GetHashCode():X4}");
return rpcResult;
}
public class RpcException : Exception
{
public readonly int Code;
public RpcException(int code, string message) : base(message) => Code = code;
}
public async Task<X> CallAsync<X>(ITLFunction<X> request)
{
await SendAsync(request);
// TODO: create a background reactor system that handles incoming packets and wake up awaiting tasks when their result has arrived
// This would allow parallelization of Send task and avoid the risk of calling RecvInternal concurrently
_lastRpcResultType = typeof(X);
for (; ;)
{
var reply = await RecvInternalAsync();
if (reply is RpcResult rpcResult && rpcResult.req_msg_id == _session.LastSentMsgId)
{
if (rpcResult.result is RpcError rpcError)
{
int migrateDC;
if (rpcError.error_code == 303 && ((migrateDC = rpcError.error_message.IndexOf("_MIGRATE_")) > 0))
{
migrateDC = int.Parse(rpcError.error_message[(migrateDC + 9)..]);
await MigrateDCAsync(migrateDC);
await SendAsync(request);
}
else
throw new RpcException(rpcError.error_code, rpcError.error_message);
}
else if (rpcResult.result is X result)
return result;
else
throw new ApplicationException($"{request.GetType().Name} call got a result of type {rpcResult.result.GetType().Name} instead of {typeof(X).Name}");
}
else
await HandleMessageAsync(reply);
}
}
private async Task CheckMsgsToAck()
{
MsgsAck msgsAck = null;
lock (_msgsToAck)
if (_msgsToAck.Count != 0)
{
msgsAck = new MsgsAck { msg_ids = _msgsToAck.ToArray() };
_msgsToAck.Clear();
}
if (msgsAck != null)
await SendAsync(msgsAck, false);
}
private async Task HandleMessageAsync(ITLObject obj)
{
switch (obj)
{
case MsgContainer container:
foreach (var msg in container.messages)
{
Helpers.Log(1, $" → {msg.body?.GetType().Name,-48} timestamp={_session.MsgIdToStamp(msg.msg_id)} isResponse={(msg.msg_id & 2) != 0} {(msg.seqno == -1 ? "clearText" : "isContent")}={(msg.seqno & 1) != 0}");
if ((msg.seqno & 1) != 0) lock (_msgsToAck) _msgsToAck.Add(msg.msg_id);
if (msg.body != null) await HandleMessageAsync(msg.body);
}
break;
case BadServerSalt badServerSalt:
_session.Salt = badServerSalt.new_server_salt;
if (badServerSalt.bad_msg_id == _session.LastSentMsgId)
await SendAsync(_lastSentMsg);
break;
case BadMsgNotification badMsgNotification:
Helpers.Log(3, $"BadMsgNotification {badMsgNotification.error_code} for msg {badMsgNotification.bad_msg_seqno}");
break;
case RpcResult rpcResult:
if (_session.MsgIdToStamp(rpcResult.req_msg_id) >= _session.SessionStart)
throw new ApplicationException($"Got RpcResult({rpcResult.result.GetType().Name}) for unknown msgId {rpcResult.req_msg_id}");
break; // silently ignore results for msg_id from previous sessions
default:
if (_updateHandler != null) await _updateHandler?.Invoke(obj);
break;
}
}
public async Task<User> UserAuthIfNeeded(CodeSettings settings = null)
{
if (_session.User != null)
return Schema.Deserialize<User>(_session.User);
string phone_number = Config("phone_number");
var sentCode = await CallAsync(new Fn.Auth_SendCode
{
phone_number = phone_number,
api_id = _apiId,
api_hash = _apiHash,
settings = settings ?? new()
});
Helpers.Log(3, $"A verification code has been sent via {sentCode.type.GetType().Name[17..]}");
var verification_code = Config("verification_code");
Auth_AuthorizationBase authorization;
try
{
authorization = await CallAsync(new Fn.Auth_SignIn
{
phone_number = phone_number,
phone_code_hash = sentCode.phone_code_hash,
phone_code = verification_code
});
}
catch (RpcException e) when (e.Code == 400 && e.Message == "SESSION_PASSWORD_NEEDED")
{
throw new NotImplementedException("Library does not support 2FA yet"); //TODO: support 2FA
}
if (authorization is Auth_AuthorizationSignUpRequired signUpRequired)
{
var waitUntil = DateTime.UtcNow.AddSeconds(3);
if (signUpRequired.terms_of_service != null && _updateHandler != null)
await _updateHandler?.Invoke(signUpRequired.terms_of_service); // give caller the possibility to read and accept TOS
var signUp = new Fn.Auth_SignUp
{
phone_number = phone_number,
phone_code_hash = sentCode.phone_code_hash,
first_name = Config("first_name"),
last_name = Config("last_name"),
};
var wait = waitUntil - DateTime.UtcNow;
if (wait > TimeSpan.Zero) await Task.Delay(wait); // we get a FLOOD_WAIT_3 if we SignUp too fast
authorization = await CallAsync(signUp);
}
if (authorization is not Auth_Authorization { user: User user })
throw new ApplicationException("Failed to get Authorization: " + authorization.GetType().Name);
_session.User = Schema.Serialize(user);
_session.Save();
return user;
}
#region TL-Helpers
/// <summary>Helper function to upload a file to Telegram</summary>
/// <returns>an <see cref="InputFile"/> or <see cref="InputFileBig"/> than can be used in various requests</returns>
public Task<InputFileBase> UploadFileAsync(string pathname)
=> UploadFileAsync(File.OpenRead(pathname), Path.GetFileName(pathname));
public async Task<InputFileBase> UploadFileAsync(Stream stream, string filename)
{
using var md5 = MD5.Create();
using (stream)
{
long length = stream.Length;
var isBig = length >= 10 * 1024 * 1024;
const int partSize = 512 * 1024;
int file_total_parts = (int)((length - 1) / partSize) + 1;
long file_id = Helpers.RandomLong();
var bytes = new byte[Math.Min(partSize, length)];
int file_part = 0, read;
for (long bytesLeft = length; bytesLeft != 0; file_part++)
{
//TODO: parallelize several parts sending through a N-semaphore? (needs a reactor first)
read = await FullReadAsync(stream, bytes, (int)Math.Min(partSize, bytesLeft));
await CallAsync<bool>(isBig
? new Fn.Upload_SaveBigFilePart { bytes = bytes, file_id = file_id, file_part = file_part, file_total_parts = file_total_parts }
: new Fn.Upload_SaveFilePart { bytes = bytes, file_id = file_id, file_part = file_part });
if (!isBig) md5.TransformBlock(bytes, 0, read, null, 0);
bytesLeft -= read;
if (read < partSize && bytesLeft != 0) throw new ApplicationException($"Failed to fully read stream ({read},{bytesLeft})");
}
if (!isBig) md5.TransformFinalBlock(bytes, 0, 0);
return isBig ? new InputFileBig { id = file_id, parts = file_total_parts, name = filename }
: new InputFile { id = file_id, parts = file_total_parts, name = filename, md5_checksum = md5.Hash };
}
}
//TODO: include XML comments in nuget?
/// <summary>Helper function to send a text or media message more easily</summary>
/// <param name="peer">destination of message</param>
/// <param name="caption">media caption</param>
/// <param name="mediaFile"><see langword="null"/> or a media file already uploaded to TG <i>(see <see cref="UploadFileAsync">UploadFileAsync</see>)</i></param>
/// <param name="mimeType"><see langword="null"/> for automatic detection, <c>"photo"</c> for an inline photo, or a MIME type to send as a document</param>
public Task<UpdatesBase> SendMediaAsync(InputPeer peer, string caption, InputFileBase mediaFile, string mimeType = null, int reply_to_msg_id = 0, MessageEntity[] entities = null, DateTime schedule_date = default, bool disable_preview = false)
{
var filename = mediaFile is InputFile iFile ? iFile.name : (mediaFile as InputFileBig)?.name;
mimeType ??= Path.GetExtension(filename).ToLowerInvariant() switch
{
".jpg" or ".jpeg" or ".png" or ".bmp" => "photo",
".gif" => "image/gif",
".webp" => "image/webp",
".mp4" => "video/mp4",
".mp3" => "audio/mpeg",
".wav" => "audio/x-wav",
_ => "", // send as generic document with undefined MIME type
};
if (mimeType == "photo")
return SendMessageAsync(peer, caption, new InputMediaUploadedPhoto { file = mediaFile },
reply_to_msg_id, entities, schedule_date, disable_preview);
var attributes = filename == null ? Array.Empty<DocumentAttribute>() : new[] { new DocumentAttributeFilename { file_name = filename } };
return SendMessageAsync(peer, caption, new InputMediaUploadedDocument
{
file = mediaFile, mime_type = mimeType, attributes = attributes
}, reply_to_msg_id, entities, schedule_date, disable_preview);
}
/// <summary>Helper function to send a text or media message</summary>
/// <param name="peer">destination of message</param>
/// <param name="text">text, or media caption</param>
/// <param name="media">media specification or <see langword="null"/></param>
public Task<UpdatesBase> SendMessageAsync(InputPeer peer, string text, InputMedia media = null, int reply_to_msg_id = 0, MessageEntity[] entities = null, DateTime schedule_date = default, bool disable_preview = false)
{
ITLFunction<UpdatesBase> request = (media == null)
? new Fn.Messages_SendMessage
{
flags = GetFlags(),
peer = peer,
reply_to_msg_id = reply_to_msg_id,
message = text,
random_id = Helpers.RandomLong(),
entities = entities,
schedule_date = schedule_date
}
: new Fn.Messages_SendMedia
{
flags = (Fn.Messages_SendMedia.Flags)GetFlags(),
peer = peer,
reply_to_msg_id = reply_to_msg_id,
media = media,
message = text,
random_id = Helpers.RandomLong(),
entities = entities,
schedule_date = schedule_date
};
return CallAsync(request);
Fn.Messages_SendMessage.Flags GetFlags()
{
return ((reply_to_msg_id != 0) ? Fn.Messages_SendMessage.Flags.has_reply_to_msg_id : 0)
| (disable_preview ? Fn.Messages_SendMessage.Flags.no_webpage : 0)
// | (reply_markup != null ? Messages_SendMessage.Flags.has_reply_markup : 0)
| (entities != null ? Fn.Messages_SendMessage.Flags.has_entities : 0)
| (schedule_date != default ? Fn.Messages_SendMessage.Flags.has_schedule_date : 0);
}
}
#endregion
}
}

291
src/Encryption.cs Normal file
View file

@ -0,0 +1,291 @@
using System;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Numerics;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using TL;
namespace WTelegram
{
internal static class Encryption
{
internal static readonly RNGCryptoServiceProvider RNG = new();
private static readonly Dictionary<long, RSAPublicKey> PublicKeys = new();
internal static async Task CreateAuthorizationKey(Client client, Session session)
{
if (PublicKeys.Count == 0) LoadDefaultPublicKey();
//1)
var reqPQ = new Fn.ReqPQ() { nonce = new Int128(RNG) };
await client.SendAsync(reqPQ, false);
//2)
var reply = await client.RecvInternalAsync();
if (reply is not ResPQ resPQ) throw new ApplicationException($"Expected ResPQ but got {reply.GetType().Name}");
if (resPQ.nonce != reqPQ.nonce) throw new ApplicationException("Nonce mismatch");
var fingerprint = resPQ.server_public_key_fingerprints.FirstOrDefault(PublicKeys.ContainsKey);
if (fingerprint == 0) throw new ApplicationException("Couldn't match any server_public_key_fingerprints");
Helpers.Log(2, $"Selected public key with fingerprint {fingerprint:X}");
//3)
long retry_id = 0;
ulong pq = Helpers.FromBigEndian(resPQ.pq);
ulong p = Helpers.PQFactorize(pq);
ulong q = pq / p;
//4)
var new_nonce = new Int256(RNG);
var reqDHparams = MakeReqDHparam(fingerprint, PublicKeys[fingerprint], new PQInnerData
{
pq = resPQ.pq,
p = Helpers.ToBigEndian(p),
q = Helpers.ToBigEndian(q),
nonce = resPQ.nonce,
server_nonce = resPQ.server_nonce,
new_nonce = new_nonce,
});
await client.SendAsync(reqDHparams, false);
//5)
reply = await client.RecvInternalAsync();
if (reply is not ServerDHParams serverDHparams) throw new ApplicationException($"Expected ServerDHParams but got {reply.GetType().Name}");
var localTime = DateTimeOffset.UtcNow;
if (serverDHparams is not ServerDHParamsOk serverDHparamsOk) throw new ApplicationException("not server_DH_params_ok");
if (serverDHparamsOk.nonce != resPQ.nonce) throw new ApplicationException("Nonce mismatch");
if (serverDHparamsOk.server_nonce != resPQ.server_nonce) throw new ApplicationException("Server Nonce mismatch");
var (tmp_aes_key, tmp_aes_iv) = ConstructTmpAESKeyIV(resPQ.server_nonce, new_nonce);
var answer = AES_IGE_EncryptDecrypt(serverDHparamsOk.encrypted_answer, tmp_aes_key, tmp_aes_iv, false);
using var encryptedReader = new BinaryReader(new MemoryStream(answer));
var answerHash = encryptedReader.ReadBytes(20);
var answerObj = Schema.DeserializeValue(encryptedReader, typeof(object));
if (answerObj is not ServerDHInnerData serverDHinnerData) throw new ApplicationException("not server_DH_inner_data");
long padding = encryptedReader.BaseStream.Length - encryptedReader.BaseStream.Position;
if (padding >= 16) throw new ApplicationException("Too much pad");
if (!Enumerable.SequenceEqual(SHA1.HashData(answer.AsSpan(20..^(int)padding)), answerHash))
throw new ApplicationException("Answer SHA1 mismatch");
if (serverDHinnerData.nonce != resPQ.nonce) throw new ApplicationException("Nonce mismatch");
if (serverDHinnerData.server_nonce != resPQ.server_nonce) throw new ApplicationException("Server Nonce mismatch");
var g_a = new BigInteger(serverDHinnerData.g_a, true, true);
var dh_prime = new BigInteger(serverDHinnerData.dh_prime, true, true);
ValidityChecks(dh_prime, serverDHinnerData.g);
Helpers.Log(1, $"Server time: {serverDHinnerData.server_time} UTC");
session.ServerTicksOffset = (serverDHinnerData.server_time - localTime).Ticks;
//6)
var bData = new byte[256];
RNG.GetBytes(bData);
var b = new BigInteger(bData, true, true);
var g_b = BigInteger.ModPow(serverDHinnerData.g, b, dh_prime);
var setClientDHparams = MakeClientDHparams(tmp_aes_key, tmp_aes_iv, new ClientDHInnerData
{
nonce = resPQ.nonce,
server_nonce = resPQ.server_nonce,
retry_id = retry_id,
g_b = g_b.ToByteArray(true, true)
});
await client.SendAsync(setClientDHparams, false);
//7)
var gab = BigInteger.ModPow(g_a, b, dh_prime);
var authKey = gab.ToByteArray(true, true);
//8)
var authKeyHash = SHA1.HashData(authKey);
retry_id = BinaryPrimitives.ReadInt64LittleEndian(authKeyHash); // (auth_key_aux_hash)
//9)
reply = await client.RecvInternalAsync();
if (reply is not SetClientDHParamsAnswer setClientDHparamsAnswer) throw new ApplicationException($"Expected SetClientDHParamsAnswer but got {reply.GetType().Name}");
if (setClientDHparamsAnswer is not DHGenOk) throw new ApplicationException("not dh_gen_ok");
if (setClientDHparamsAnswer.nonce != resPQ.nonce) throw new ApplicationException("Nonce mismatch");
if (setClientDHparamsAnswer.server_nonce != resPQ.server_nonce) throw new ApplicationException("Server Nonce mismatch");
var expected_new_nonceN = new byte[32 + 1 + 8];
new_nonce.raw.CopyTo(expected_new_nonceN, 0);
expected_new_nonceN[32] = 1;
Array.Copy(authKeyHash, 0, expected_new_nonceN, 33, 8); // (auth_key_aux_hash)
if (!Enumerable.SequenceEqual(setClientDHparamsAnswer.new_nonce_hashN.raw, SHA1.HashData(expected_new_nonceN).Skip(4)))
throw new ApplicationException("setClientDHparamsAnswer.new_nonce_hashN mismatch");
session.AuthKeyID = BinaryPrimitives.ReadInt64LittleEndian(authKeyHash.AsSpan(12));
session.AuthKey = authKey;
session.Salt = BinaryPrimitives.ReadInt64LittleEndian(new_nonce.raw) ^ BinaryPrimitives.ReadInt64LittleEndian(resPQ.server_nonce.raw);
session.Save();
static (byte[] key, byte[] iv) ConstructTmpAESKeyIV(Int128 server_nonce, Int256 new_nonce)
{
byte[] tmp_aes_key = new byte[32], tmp_aes_iv = new byte[32];
using var sha1 = new SHA1Managed();
sha1.TransformBlock(new_nonce, 0, 32, null, 0);
sha1.TransformFinalBlock(server_nonce, 0, 16);
sha1.Hash.CopyTo(tmp_aes_key, 0); // tmp_aes_key := SHA1(new_nonce + server_nonce)
sha1.TransformBlock(server_nonce, 0, 16, null, 0);
sha1.TransformFinalBlock(new_nonce, 0, 32);
Array.Copy(sha1.Hash, 0, tmp_aes_key, 20, 12); // + SHA1(server_nonce, new_nonce)[0:12]
Array.Copy(sha1.Hash, 12, tmp_aes_iv, 0, 8); // tmp_aes_iv != SHA1(server_nonce, new_nonce)[12:8]
sha1.TransformBlock(new_nonce, 0, 32, null, 0);
sha1.TransformFinalBlock(new_nonce, 0, 32);
sha1.Hash.CopyTo(tmp_aes_iv, 8); // + SHA(new_nonce + new_nonce)
Array.Copy(new_nonce, 0, tmp_aes_iv, 28, 4); // + new_nonce[0:4]
return (tmp_aes_key, tmp_aes_iv);
}
}
private static void ValidityChecks(BigInteger p, int g)
{
//TODO: check whether p is a safe prime (meaning that both p and (p - 1) / 2 are prime)
// check that 2^2047 <= p < 2^2048
if (p.GetBitLength() != 2048) throw new ApplicationException("p is not 2048-bit number");
// check that g generates a cyclic subgroup of prime order (p - 1) / 2, i.e. is a quadratic residue mod p.
BigInteger mod_r;
if (g switch
{
2 => p % 8 != 7,
3 => p % 3 != 2,
4 => false,
5 => (mod_r = p % 5) != 1 && mod_r != 4,
6 => (mod_r = p % 24) != 19 && mod_r != 23,
7 => (mod_r = p % 7) != 3 && mod_r != 5 && mod_r != 6,
_ => true,
})
throw new ApplicationException("Bad prime mod 4g");
//TODO: check that g, g_a and g_b are greater than 1 and less than dh_prime - 1.
// We recommend checking that g_a and g_b are between 2^{2048-64} and dh_prime - 2^{2048-64} as well.
}
private static Fn.ReqDHParams MakeReqDHparam(long publicKey_fingerprint, RSAPublicKey publicKey, PQInnerData pqInnerData)
{
// the following code was the way TDLib did it (and seems still accepted) until they changed on 8 July 2021
using var clearStream = new MemoryStream(255);
clearStream.Position = 20; // skip SHA1 area (to be patched)
using var writer = new BinaryWriter(clearStream, Encoding.UTF8);
Schema.Serialize(writer, pqInnerData);
int clearLength = (int)clearStream.Length; // length before padding (= 20 + message_data_length)
if (clearLength > 255) throw new ApplicationException("PQInnerData too big");
byte[] clearBuffer = clearStream.GetBuffer();
RNG.GetBytes(clearBuffer, clearLength, 255 - clearLength);
SHA1.HashData(clearBuffer.AsSpan(20..clearLength), clearBuffer); // patch with SHA1
var encrypted_data = BigInteger.ModPow(new BigInteger(clearBuffer, true, true), // encrypt with RSA key
new BigInteger(publicKey.e, true, true), new BigInteger(publicKey.n, true, true)).ToByteArray(true, true);
return new Fn.ReqDHParams
{
nonce = pqInnerData.nonce,
server_nonce = pqInnerData.server_nonce,
p = pqInnerData.p,
q = pqInnerData.q,
public_key_fingerprint = publicKey_fingerprint,
encrypted_data = encrypted_data
};
}
private static Fn.SetClientDHParams MakeClientDHparams(byte[] tmp_aes_key, byte[] tmp_aes_iv, ClientDHInnerData clientDHinnerData)
{
// the following code was the way TDLib did it (and seems still accepted) until they changed on 8 July 2021
using var clearStream = new MemoryStream(384);
clearStream.Position = 20; // skip SHA1 area (to be patched)
using var writer = new BinaryWriter(clearStream, Encoding.UTF8);
Schema.Serialize(writer, clientDHinnerData);
int clearLength = (int)clearStream.Length; // length before padding (= 20 + message_data_length)
int padding = (0x7FFFFFF0 - clearLength) % 16;
clearStream.SetLength(clearLength + padding);
byte[] clearBuffer = clearStream.GetBuffer();
RNG.GetBytes(clearBuffer, clearLength, padding);
SHA1.HashData(clearBuffer.AsSpan(20..clearLength), clearBuffer);
var encrypted_data = AES_IGE_EncryptDecrypt(clearBuffer.AsSpan(0, clearLength + padding), tmp_aes_key, tmp_aes_iv, true);
return new Fn.SetClientDHParams
{
nonce = clientDHinnerData.nonce,
server_nonce = clientDHinnerData.server_nonce,
encrypted_data = encrypted_data
};
}
public static void LoadPublicKey(string pem)
{
using var rsa = RSA.Create();
rsa.ImportFromPem(pem);
var rsaParam = rsa.ExportParameters(false);
var publicKey = new RSAPublicKey { n = rsaParam.Modulus, e = rsaParam.Exponent };
var bareData = Schema.Serialize(publicKey).AsSpan(4); // bare serialization
var fingerprint = BinaryPrimitives.ReadInt64LittleEndian(SHA1.HashData(bareData).AsSpan(12)); // 64 lower-order bits of SHA1
PublicKeys[fingerprint] = publicKey;
Helpers.Log(1, $"Loaded a public key with fingerprint {fingerprint:X}");
}
private static void LoadDefaultPublicKey() // fingerprint C3B42B026CE86B21
{
LoadPublicKey(@"-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEAwVACPi9w23mF3tBkdZz+zwrzKOaaQdr01vAbU4E1pvkfj4sqDsm6
lyDONS789sVoD/xCS9Y0hkkC3gtL1tSfTlgCMOOul9lcixlEKzwKENj1Yz/s7daS
an9tqw3bfUV/nqgbhGX81v/+7RFAEd+RwFnK7a+XYl9sluzHRyVVaTTveB2GazTw
Efzk2DWgkBluml8OREmvfraX3bkHZJTKX4EQSjBbbdJ2ZXIsRrYOXfaA+xayEGB+
8hdlLmAjbCVfaigxX0CDqWeR1yFL9kwd9P0NsZRPsmoqVwMbMu7mStFai6aIhc3n
Slv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB
-----END RSA PUBLIC KEY-----");
}
internal static byte[] EncryptDecryptMessage(Span<byte> input, bool encrypt, byte[] authKey, byte[] clearSha1)
{
// first, construct AES key & IV
int x = encrypt ? 0 : 8;
byte[] aes_key = new byte[32], aes_iv = new byte[32];
using var sha1 = new SHA1Managed();
sha1.TransformBlock(clearSha1, 4, 16, null, 0); // msgKey
sha1.TransformFinalBlock(authKey, x, 32); // authKey[x:32]
var sha1_a = sha1.Hash;
sha1.TransformBlock(authKey, 32 + x, 16, null, 0); // authKey[32+x:16]
sha1.TransformBlock(clearSha1, 4, 16, null, 0); // msgKey
sha1.TransformFinalBlock(authKey, 48 + x, 16); // authKey[48+x:16]
var sha1_b = sha1.Hash;
sha1.TransformBlock(authKey, 64 + x, 32, null, 0); // authKey[64+x:32]
sha1.TransformFinalBlock(clearSha1, 4, 16); // msgKey
var sha1_c = sha1.Hash;
sha1.TransformBlock(clearSha1, 4, 16, null, 0); // msgKey
sha1.TransformFinalBlock(authKey, 96 + x, 32); // authKey[96+x:32]
var sha1_d = sha1.Hash;
Array.Copy(sha1_a, 0, aes_key, 0, 8);
Array.Copy(sha1_b, 8, aes_key, 8, 12);
Array.Copy(sha1_c, 4, aes_key, 20, 12);
Array.Copy(sha1_a, 8, aes_iv, 0, 12);
Array.Copy(sha1_b, 0, aes_iv, 12, 8);
Array.Copy(sha1_c, 16, aes_iv, 20, 4);
Array.Copy(sha1_d, 0, aes_iv, 24, 8);
return AES_IGE_EncryptDecrypt(input, aes_key, aes_iv, encrypt);
}
private static byte[] AES_IGE_EncryptDecrypt(Span<byte> input, byte[] aes_key, byte[] aes_iv, bool encrypt)
{
using var aes = Aes.Create();
aes.Mode = CipherMode.ECB;
aes.Padding = PaddingMode.Zeros;
if (aes.BlockSize != 128) throw new ApplicationException("AES Blocksize is not 16 bytes");
if (input.Length % 16 != 0) throw new ApplicationException("intput size not divisible by 16");
// code adapted from PHP implementation found at https://mgp25.com/AESIGE/
var output = new byte[input.Length];
var xPrev = aes_iv.AsSpan(encrypt ? 16 : 0, 16);
var yPrev = aes_iv.AsSpan(encrypt ? 0 : 16, 16);
var aesCrypto = encrypt ? aes.CreateEncryptor(aes_key, null) : aes.CreateDecryptor(aes_key, null);
using (aesCrypto)
{
byte[] yXOR = new byte[16];
for (int i = 0; i < input.Length; i += 16)
{
var x = input.Slice(i, 16);
var y = output.AsSpan(i, 16);
for (int j = 0; j < 16; j++)
yXOR[j] = (byte)(x[j] ^ yPrev[j]);
var yFinal = aesCrypto.TransformFinalBlock(yXOR, 0, 16);
for (int j = 0; j < 16; j++)
y[j] = (byte)(yFinal[j] ^ xPrev[j]);
xPrev = x;
yPrev = y;
}
}
return output;
}
}
}

379
src/Generator.cs Normal file
View file

@ -0,0 +1,379 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text.Json;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace WTelegram
{
public class Generator
{
//TODO: generate BinaryReader/Writer serialization directly to avoid using Reflection
//TODO: generate partial class with methods for functions instead of exposing request classes
readonly Dictionary<int, string> ctorToTypes = new();
readonly HashSet<string> allTypes = new();
readonly Dictionary<int, Dictionary<string, TypeInfo>> typeInfosByLayer = new();
Dictionary<string, TypeInfo> typeInfos;
int currentLayer;
string tabIndent;
public async Task FromWeb()
{
Console.WriteLine("Fetch web pages...");
//using var http = new HttpClient();
//var html = await http.GetStringAsync("https://core.telegram.org/api/layers");
var html = await Task.FromResult("#layer-121");
currentLayer = int.Parse(Regex.Match(html, @"#layer-(\d+)").Groups[1].Value);
//await File.WriteAllBytesAsync("TL.MTProto.json", await http.GetByteArrayAsync("https://core.telegram.org/schema/mtproto-json"));
//await File.WriteAllBytesAsync("TL.Schema.json", await http.GetByteArrayAsync("https://core.telegram.org/schema/json"));
//await File.WriteAllBytesAsync("TL.Secret.json", await http.GetByteArrayAsync("https://core.telegram.org/schema/end-to-end-json"));
FromJson("TL.MTProto.json", "TL.MTProto.cs", @"TL.Table.cs");
FromJson("TL.Schema.json", "TL.Schema.cs", @"TL.Table.cs");
FromJson("TL.Secret.json", "TL.Secret.cs", @"TL.Table.cs");
}
public void FromJson(string jsonPath, string outputCs, string tableCs = null)
{
Console.WriteLine("Parsing " + jsonPath);
var schema = JsonSerializer.Deserialize<SchemaJson>(File.ReadAllText(jsonPath));
using var sw = File.CreateText(outputCs);
sw.WriteLine("using System;");
sw.WriteLine();
sw.WriteLine("namespace TL");
sw.Write("{");
tabIndent = "\t";
var layers = schema.constructors.GroupBy(c => c.layer).OrderBy(g => g.Key);
foreach (var layer in layers)
{
typeInfos = typeInfosByLayer.GetOrCreate(layer.Key);
if (layer.Key != 0)
{
sw.WriteLine();
sw.WriteLine("\tnamespace Layer" + layer.Key);
sw.Write("\t{");
tabIndent += "\t";
}
string layerPrefix = layer.Key == 0 ? "" : $"Layer{layer.Key}.";
foreach (var ctor in layer)
{
if (ctorToTypes.ContainsKey(ctor.ID)) continue;
if (ctor.type is "Bool" or "Vector t") continue;
var structName = CSharpName(ctor.predicate);
ctorToTypes[ctor.ID] = layerPrefix + structName;
var typeInfo = typeInfos.GetOrCreate(ctor.type);
if (ctor.ID == 0x5BB8E511) { ctorToTypes[ctor.ID] = structName = ctor.predicate = ctor.type = "_Message"; }
if (typeInfo.ReturnName == null) typeInfo.ReturnName = CSharpName(ctor.type);
typeInfo.Structs.Add(ctor);
if (structName == typeInfo.ReturnName) typeInfo.SameName = ctor;
}
foreach (var (name, typeInfo) in typeInfos)
{
if (allTypes.Contains(typeInfo.ReturnName)) { typeInfo.NeedAbstract = -2; continue; }
if (typeInfo.SameName == null)
{
typeInfo.NeedAbstract = -1;
if (typeInfo.Structs.Count > 1)
{
List<Param> fakeCtorParams = new();
while (typeInfo.Structs[0].@params.Length > fakeCtorParams.Count)
{
fakeCtorParams.Add(typeInfo.Structs[0].@params[fakeCtorParams.Count]);
if (!typeInfo.Structs.All(ctor => HasPrefix(ctor, fakeCtorParams)))
{
fakeCtorParams.RemoveAt(fakeCtorParams.Count - 1);
break;
}
}
if (fakeCtorParams.Count > 0)
{
typeInfo.Structs.Insert(0, typeInfo.SameName = new Constructor
{ id = null, @params = fakeCtorParams.ToArray(), predicate = typeInfo.ReturnName, type = typeInfo.ReturnName });
typeInfo.NeedAbstract = fakeCtorParams.Count;
}
}
}
else if (typeInfo.Structs.Count > 1)
{
typeInfo.NeedAbstract = typeInfo.SameName.@params.Length;
foreach (var ctor in typeInfo.Structs)
{
if (ctor == typeInfo.SameName) continue;
if (!HasPrefix(ctor, typeInfo.SameName.@params)) { typeInfo.NeedAbstract = -1; typeInfo.ReturnName += "Base"; break; }
}
}
}
foreach (var typeInfo in typeInfos.Values)
WriteTypeInfo(sw, typeInfo, jsonPath, layerPrefix, false);
if (layer.Key != 0)
{
sw.WriteLine("\t}");
tabIndent = tabIndent[1..];
}
}
if (typeInfosByLayer[0]["Message"].SameName.ID == 0x5BB8E511) typeInfosByLayer[0].Remove("Message");
sw.WriteLine();
var methods = new List<TypeInfo>();
if (schema.methods.Length != 0)
{
typeInfos = typeInfosByLayer[0];
sw.WriteLine("\tpublic static partial class Fn // ---functions---");
sw.Write("\t{");
tabIndent = "\t\t";
foreach (var method in schema.methods)
{
var typeInfo = new TypeInfo { ReturnName = method.type };
typeInfo.Structs.Add(new Constructor { id = method.id, @params = method.@params, predicate = method.method, type = method.type });
methods.Add(typeInfo);
WriteTypeInfo(sw, typeInfo, jsonPath, "", true);
}
sw.WriteLine("\t}");
}
sw.WriteLine("}");
if (tableCs != null) UpdateTable(jsonPath, tableCs, methods);
}
void WriteTypeInfo(StreamWriter sw, TypeInfo typeInfo, string definedIn, string layerPrefix, bool isMethod)
{
var parentClass = typeInfo.NeedAbstract != 0 ? typeInfo.ReturnName : "ITLObject";
var genericType = typeInfo.ReturnName.Length == 1 ? $"<{typeInfo.ReturnName}>" : null;
if (isMethod) parentClass = $"ITLFunction<{MapType(typeInfo.ReturnName, "")}>";
bool needNewLine = true;
if (typeInfo.NeedAbstract == -1 && allTypes.Add(layerPrefix + parentClass))
{
needNewLine = false;
sw.WriteLine();
sw.WriteLine($"{tabIndent}public abstract class {parentClass} : ITLObject {{ }}");
}
int skipParams = 0;
foreach (var ctor in typeInfo.Structs)
{
string className = CSharpName(ctor.predicate) + genericType;
//if (typeInfo.ReturnName == "SendMessageAction") System.Diagnostics.Debugger.Break();
if (layerPrefix != "" && className == parentClass) { className += "_"; ctorToTypes[ctor.ID] = layerPrefix + className; }
if (!allTypes.Add(layerPrefix + className)) continue;
if (needNewLine) { needNewLine = false; sw.WriteLine(); }
if (ctor.id == null)
sw.Write($"{tabIndent}public abstract class {className} : ITLObject");
else
{
sw.Write($"{tabIndent}[TLDef(0x{ctor.ID:X8})] //{ctor.predicate}#{ctor.ID:x8} ");
if (genericType != null) sw.Write($"{{{typeInfo.ReturnName}:Type}} ");
foreach (var parm in ctor.@params) sw.Write($"{parm.name}:{parm.type} ");
sw.WriteLine($"= {ctor.type}");
sw.Write($"{tabIndent}public class {className} : ");
sw.Write(skipParams == 0 && typeInfo.NeedAbstract > 0 ? "ITLObject" : parentClass);
}
var parms = ctor.@params.Skip(skipParams).ToArray();
if (parms.Length == 0)
{
sw.WriteLine(" { }");
continue;
}
var hasFlagEnum = parms.Any(p => p.type.StartsWith("flags."));
bool multiline = hasFlagEnum || parms.Length > 1;
if (multiline)
{
sw.WriteLine();
sw.WriteLine(tabIndent + "{");
}
else
sw.Write(" { ");
if (hasFlagEnum)
{
var list = new SortedList<int, string>();
foreach (var parm in parms)
{
if (!parm.type.StartsWith("flags.") || !parm.type.EndsWith("?true")) continue;
var mask = 1 << int.Parse(parm.type[6..parm.type.IndexOf('?')]);
if (!list.ContainsKey(mask)) list[mask] = MapName(parm.name);
}
foreach (var parm in parms)
{
if (!parm.type.StartsWith("flags.") || parm.type.EndsWith("?true")) continue;
var mask = 1 << int.Parse(parm.type[6..parm.type.IndexOf('?')]);
if (list.ContainsKey(mask)) continue;
var name = MapName("has_" + parm.name);
if (list.Values.Contains(name)) name += "_field";
list[mask] = name;
}
string line = tabIndent + "\t[Flags] public enum Flags { ";
foreach (var (mask, name) in list)
{
var str = $"{name} = 0x{mask:X}, ";
if (line.Length + str.Length + tabIndent.Length * 3 >= 134) { sw.WriteLine(line); line = tabIndent + "\t\t"; }
line += str;
}
sw.WriteLine(line.TrimEnd(',', ' ') + " }");
}
foreach (var parm in parms)
{
if (parm.type.EndsWith("?true")) continue;
if (multiline) sw.Write(tabIndent + "\t");
if (parm.type == "#")
sw.Write($"public {(hasFlagEnum ? "Flags" : "int")} {parm.name};");
else
{
if (parm.type.StartsWith("flags."))
{
int qm = parm.type.IndexOf('?');
sw.Write($"[IfFlag({parm.type[6..qm]})] public {MapType(parm.type[(qm + 1)..], parm.name)} {MapName(parm.name)};");
}
else
sw.Write($"public {MapType(parm.type, parm.name)} {MapName(parm.name)};");
}
if (multiline) sw.WriteLine();
}
if (ctorNeedClone.Contains(className))
sw.WriteLine($"{tabIndent}\tpublic {className} Clone() => ({className})MemberwiseClone();");
if (multiline)
sw.WriteLine(tabIndent + "}");
else
sw.WriteLine(" }");
skipParams = typeInfo.NeedAbstract;
}
string MapName(string name) => name switch
{
"out" => "out_",
"static" => "static_",
"long" => "long_",
"default" => "default_",
"public" => "public_",
"params" => "params_",
"private" => "private_",
_ => name
};
string MapType(string type, string name)
{
if (type.StartsWith("Vector<", StringComparison.OrdinalIgnoreCase))
return MapType(type[7..^1], name) + "[]";
else if (type == "Bool")
return "bool";
else if (type == "bytes")
return "byte[]";
else if (type == "int128")
return "Int128";
else if (type == "int256")
return "Int256";
else if (type == "Object")
return "ITLObject";
else if (type == "!X")
return "ITLFunction<X>";
else if (typeInfos.TryGetValue(type, out var typeInfo))
return typeInfo.ReturnName;
else if (type == "int")
{
var name2 = '_' + name + '_';
if (name2.EndsWith("_date_") || name2.EndsWith("_time_") || name2 == "_expires_" || name2 == "_now_" || name2.StartsWith("_valid_"))
return "DateTime";
else
return "int";
}
else if (type == "string")
return name.StartsWith("md5") ? "byte[]" : "string";
else
return type;
}
}
void UpdateTable(string jsonPath, string tableCs, List<TypeInfo> methods)
{
var myTag = $"\t\t\t// from {Path.GetFileNameWithoutExtension(jsonPath)}:";
var seen_ids = new HashSet<int>();
using (var sr = new StreamReader(tableCs))
using (var sw = new StreamWriter(tableCs + ".new"))
{
string line;
while ((line = sr.ReadLine()) != null)
{
if (currentLayer != 0 && line.StartsWith("\t\tpublic const int Layer"))
sw.WriteLine($"\t\tpublic const int Layer = {currentLayer};\t\t\t\t\t// fetched {DateTime.UtcNow}");
else
sw.WriteLine(line);
if (line == myTag)
{
foreach (var ctor in ctorToTypes)
if (seen_ids.Add(ctor.Key))
sw.WriteLine($"\t\t\t[0x{ctor.Key:X8}] = typeof({ctor.Value}),");
while ((line = sr.ReadLine()) != null)
if (line.StartsWith("\t\t\t// "))
break;
sw.WriteLine(line);
}
else if (line.StartsWith("\t\t\t[0x"))
seen_ids.Add(int.Parse(line[6..14], System.Globalization.NumberStyles.HexNumber));
}
}
File.Replace(tableCs + ".new", tableCs, null);
}
static readonly HashSet<string> ctorNeedClone = new() { /*"User"*/ };
private static bool HasPrefix(Constructor ctor, IList<Param> prefixParams)
{
if (ctor.@params.Length < prefixParams.Count) return false;
for (int i = 0; i < prefixParams.Count; i++)
if (ctor.@params[i].name != prefixParams[i].name || ctor.@params[i].type != prefixParams[i].type)
return false;
return true;
}
private static string CSharpName(string name)
{
name = char.ToUpper(name[0]) + name[1..];
int i;
while ((i = name.IndexOf('_')) > 0)
name = name[..i] + char.ToUpper(name[i + 1]) + name[(i + 2)..];
while ((i = name.IndexOf('.')) > 0)
name = name[..i] + '_' + char.ToUpper(name[i + 1]) + name[(i + 2)..];
return name;
}
class TypeInfo
{
public string ReturnName;
public Constructor SameName;
public List<Constructor> Structs = new();
internal int NeedAbstract; // 0:no, -1:create auto, n:use first generated constructor and skip n params
}
#pragma warning disable IDE1006 // Naming Styles
public class SchemaJson
{
public Constructor[] constructors { get; set; }
public Method[] methods { get; set; }
}
public class Constructor
{
public string id { get; set; }
public string predicate { get; set; }
public Param[] @params { get; set; }
public string type { get; set; }
public int layer { get; set; }
public int ID => int.Parse(id);
}
public class Param
{
public string name { get; set; }
public string type { get; set; }
}
public class Method
{
public string id { get; set; }
public string method { get; set; }
public Param[] @params { get; set; }
public string type { get; set; }
}
#pragma warning restore IDE1006 // Naming Styles
}
}

132
src/Helpers.cs Normal file
View file

@ -0,0 +1,132 @@
using System;
using System.Collections.Generic;
namespace WTelegram
{
public static class Helpers
{
public static Action<int, string> Log { get; set; } = DefaultLogger;
public static readonly System.Text.Json.JsonSerializerOptions JsonOptions = new(System.Text.Json.JsonSerializerDefaults.Web) { IncludeFields = true, WriteIndented = true };
public static V GetOrCreate<K, V>(this Dictionary<K, V> dictionary, K key) where V : new()
=> dictionary.TryGetValue(key, out V value) ? value : dictionary[key] = new V();
private static readonly ConsoleColor[] LogLevelToColor = new[] { ConsoleColor.DarkGray, ConsoleColor.DarkCyan, ConsoleColor.Cyan,
ConsoleColor.Yellow, ConsoleColor.Red, ConsoleColor.Magenta, ConsoleColor.DarkBlue };
private static void DefaultLogger(int level, string message)
{
Console.ForegroundColor = LogLevelToColor[level];
Console.WriteLine(message);
Console.ResetColor();
}
public static long RandomLong()
{
Span<long> span = stackalloc long[1];
System.Security.Cryptography.RandomNumberGenerator.Fill(System.Runtime.InteropServices.MemoryMarshal.AsBytes(span));
return span[0];
}
public static byte[] ToBigEndian(ulong value) // variable-size buffer
{
int i;
var temp = value;
for (i = 1; (temp >>= 8) != 0; i++);
var result = new byte[i];
while (--i >= 0) { result[i] = (byte)value; value >>= 8; }
return result;
}
public static ulong FromBigEndian(byte[] bytes) // variable-size buffer
{
if (bytes.Length > 8) throw new ArgumentException($"expected bytes length <= 8 but got {bytes.Length}");
ulong result = 0;
foreach (byte b in bytes)
result = (result << 8) + b;
return result;
}
internal static ulong PQFactorize(ulong pq) // ported from https://github.com/tdlib/td/blob/master/tdutils/td/utils/crypto.cpp#L90
{
if (pq < 2) return 1;
var random = new Random();
ulong g = 0;
for (int i = 0, iter = 0; i < 3 || iter < 1000; i++)
{
ulong q = (ulong)random.Next(17, 32) % (pq - 1);
ulong x = ((ulong)random.Next() + (ulong)random.Next() << 31) % (pq - 1) + 1;
ulong y = x;
int lim = 1 << (Math.Min(5, i) + 18);
for (int j = 1; j < lim; j++)
{
iter++;
ulong a = x;
ulong b = x;
ulong c = q;
// c += a * b
while (b != 0)
{
if ((b & 1) != 0)
{
c += a;
if (c >= pq)
c -= pq;
}
a += a;
if (a >= pq)
a -= pq;
b >>= 1;
}
x = c;
ulong z = x < y ? pq + x - y : x - y;
g = gcd(z, pq);
if (g != 1)
break;
if ((j & (j - 1)) == 0)
y = x;
}
if (g > 1 && g < pq)
break;
}
if (g != 0)
{
ulong other = pq / g;
if (other < g)
g = other;
}
return g;
static ulong gcd(ulong a, ulong b)
{
if (a == 0) return b;
if (b == 0) return a;
int shift = 0;
while ((a & 1) == 0 && (b & 1) == 0)
{
a >>= 1;
b >>= 1;
shift++;
}
while (true)
{
while ((a & 1) == 0)
a >>= 1;
while ((b & 1) == 0)
b >>= 1;
if (a > b)
a -= b;
else if (b > a)
b -= a;
else
return a << shift;
}
}
}
}
}

93
src/Session.cs Normal file
View file

@ -0,0 +1,93 @@
using System;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text.Json;
namespace WTelegram
{
internal class Session
{
public long AuthKeyID;
public byte[] AuthKey;
public long Salt;
public long Id;
public int Seqno;
public long ServerTicksOffset;
public long LastSentMsgId;
public TL.DcOption DataCenter;
public byte[] User; // serialization of TL.User
public DateTime SessionStart => _sessionStart;
private readonly DateTime _sessionStart = DateTime.UtcNow;
private string _pathname;
private byte[] _apiHash; // used as AES key for encryption of session file
internal static Session LoadOrCreate(string pathname, byte[] apiHash)
{
if (File.Exists(pathname))
{
try
{
var session = Load(pathname, apiHash);
session._pathname = pathname;
session._apiHash = apiHash;
Helpers.Log(2, "Loaded previous session");
return session;
}
catch (Exception ex)
{
throw new ApplicationException($"Exception while reading session file: {ex.Message}\nDelete the file to start a new session", ex);
}
}
return new Session { _pathname = pathname, _apiHash = apiHash, Id = Helpers.RandomLong() };
}
internal static Session Load(string pathname, byte[] apiHash)
{
var input = File.ReadAllBytes(pathname);
using var aes = Aes.Create();
using var decryptor = aes.CreateDecryptor(apiHash, input[0..16]);
var utf8Json = decryptor.TransformFinalBlock(input, 16, input.Length - 16);
if (!SHA256.HashData(utf8Json.AsSpan(32)).SequenceEqual(utf8Json[0..32]))
throw new ApplicationException("Integrity check failed in session loading");
return JsonSerializer.Deserialize<Session>(utf8Json.AsSpan(32), Helpers.JsonOptions);
}
internal void Save()
{
var utf8Json = JsonSerializer.SerializeToUtf8Bytes(this, Helpers.JsonOptions);
var finalBlock = new byte[16];
var output = new byte[(16 + 32 + utf8Json.Length + 15) & ~15];
Encryption.RNG.GetBytes(output, 0, 16);
using var aes = Aes.Create();
using var encryptor = aes.CreateEncryptor(_apiHash, output[0..16]);
encryptor.TransformBlock(SHA256.HashData(utf8Json), 0, 32, output, 16);
encryptor.TransformBlock(utf8Json, 0, utf8Json.Length & ~15, output, 48);
utf8Json.AsSpan(utf8Json.Length & ~15).CopyTo(finalBlock);
encryptor.TransformFinalBlock(finalBlock, 0, utf8Json.Length & 15).CopyTo(output.AsMemory(48 + utf8Json.Length & ~15));
File.WriteAllBytes(_pathname, output);
}
internal (long msgId, int seqno) NewMsg(bool isContent)
{
long msgId = DateTime.UtcNow.Ticks + ServerTicksOffset - 621355968000000000L;
msgId = msgId * 428 + (msgId >> 24) * 25110956; // approximately unixtime*2^32 and divisible by 4
if (msgId <= LastSentMsgId) msgId = LastSentMsgId += 4; else LastSentMsgId = msgId;
int seqno = isContent ? Seqno++ * 2 + 1 : Seqno * 2;
Save();
return (msgId, seqno);
}
internal DateTime MsgIdToStamp(long serverMsgId)
=> new((serverMsgId >> 32) * 10000000 - ServerTicksOffset + 621355968000000000L, DateTimeKind.Utc);
internal void Reset(TL.DcOption newDC = null)
{
DataCenter = newDC;
AuthKeyID = Salt = Seqno = 0;
AuthKey = User = null;
}
}
}

297
src/TL.MTProto.cs Normal file
View file

@ -0,0 +1,297 @@
using System;
namespace TL
{
[TLDef(0x05162463)] //resPQ#05162463 nonce:int128 server_nonce:int128 pq:bytes server_public_key_fingerprints:Vector<long> = ResPQ
public class ResPQ : ITLObject
{
public Int128 nonce;
public Int128 server_nonce;
public byte[] pq;
public long[] server_public_key_fingerprints;
}
[TLDef(0x83C95AEC)] //p_q_inner_data# pq:string p:string q:string nonce:int128 server_nonce:int128 new_nonce:int256 = P_Q_inner_data
public class PQInnerData : ITLObject
{
public byte[] pq;
public byte[] p;
public byte[] q;
public Int128 nonce;
public Int128 server_nonce;
public Int256 new_nonce;
}
[TLDef(0xA9F55F95)] //p_q_inner_data_dc#a9f55f95 pq:bytes p:bytes q:bytes nonce:int128 server_nonce:int128 new_nonce:int256 dc:int = P_Q_inner_data
public class PQInnerDataDC : PQInnerData
{
public int dc;
}
[TLDef(0x56FDDF88)] //p_q_inner_data_temp_dc#56fddf88 pq:bytes p:bytes q:bytes nonce:int128 server_nonce:int128 new_nonce:int256 dc:int expires_in:int = P_Q_inner_data
public class PQInnerDataTempDC : PQInnerData
{
public int dc;
public int expires_in; // seconds
}
public abstract class ServerDHParams : ITLObject
{
public Int128 nonce;
public Int128 server_nonce;
}
[TLDef(0x79CB045D)] //server_DH_params_fail#79cb045d nonce:int128 server_nonce:int128 new_nonce_hash:int128 = Server_DH_Params
public class ServerDHParamsFail : ServerDHParams { public Int128 new_nonce_hash; }
[TLDef(0xD0E8075C)] //server_DH_params_ok#d0e8075c nonce:int128 server_nonce:int128 encrypted_answer:bytes = Server_DH_Params
public class ServerDHParamsOk : ServerDHParams { public byte[] encrypted_answer; }
[TLDef(0xB5890DBA)] //server_DH_inner_data#b5890dba nonce:int128 server_nonce:int128 g:int dh_prime:bytes g_a:bytes server_time:int = Server_DH_inner_data
public class ServerDHInnerData : ITLObject
{
public Int128 nonce;
public Int128 server_nonce;
public int g;
public byte[] dh_prime;
public byte[] g_a;
public DateTime server_time;
}
[TLDef(0x6643B654)] //client_DH_inner_data#6643b654 nonce:int128 server_nonce:int128 retry_id:long g_b:bytes = Client_DH_Inner_Data
public class ClientDHInnerData : ITLObject
{
public Int128 nonce;
public Int128 server_nonce;
public long retry_id;
public byte[] g_b;
}
public abstract class SetClientDHParamsAnswer : ITLObject
{
public Int128 nonce;
public Int128 server_nonce;
public Int128 new_nonce_hashN; // 16 low order bytes from SHA1(new_nonce + (01=ok, 02=retry, 03=fail) + 8 high order bytes from SHA1(auth_key))
}
[TLDef(0x3BCBF734)] //dh_gen_ok#3bcbf734 nonce:int128 server_nonce:int128 new_nonce_hash1:int128 = Set_client_DH_params_answer
public class DHGenOk : SetClientDHParamsAnswer { }
[TLDef(0x46DC1FB9)] //dh_gen_retry#46dc1fb9 nonce:int128 server_nonce:int128 new_nonce_hash2:int128 = Set_client_DH_params_answer
public class DHGenRetry : SetClientDHParamsAnswer { }
[TLDef(0xA69DAE02)] //dh_gen_fail#a69dae02 nonce:int128 server_nonce:int128 new_nonce_hash3:int128 = Set_client_DH_params_answer
public class DHGenFail : SetClientDHParamsAnswer { }
[TLDef(0x75A3F765)] //bind_auth_key_inner#75a3f765 nonce:long temp_auth_key_id:long perm_auth_key_id:long temp_session_id:long expires_at:int = BindAuthKeyInner
public class BindAuthKeyInner : ITLObject
{
public long nonce;
public long temp_auth_key_id;
public long perm_auth_key_id;
public long temp_session_id;
public DateTime expires_at;
}
[TLDef(0xF35C6D01)] //rpc_result#f35c6d01 req_msg_id:long result:Object = RpcResult
public class RpcResult : ITLObject
{
public long req_msg_id;
public object result;
}
[TLDef(0x2144CA19)] //rpc_error#2144ca19 error_code:int error_message:string = RpcError
public class RpcError : ITLObject
{
public int error_code;
public string error_message;
}
public abstract class RpcDropAnswer : ITLObject { }
[TLDef(0x5E2AD36E)] //rpc_answer_unknown#5e2ad36e = RpcDropAnswer
public class RpcAnswerUnknown : RpcDropAnswer { }
[TLDef(0xCD78E586)] //rpc_answer_dropped_running#cd78e586 = RpcDropAnswer
public class RpcAnswerDroppedRunning : RpcDropAnswer { }
[TLDef(0xA43AD8B7)] //rpc_answer_dropped#a43ad8b7 msg_id:long seq_no:int bytes:int = RpcDropAnswer
public class RpcAnswerDropped : RpcDropAnswer
{
public long msg_id;
public int seq_no;
public int bytes;
}
[TLDef(0x0949D9DC)] //future_salt#0949d9dc valid_since:int valid_until:int salt:long = FutureSalt
public class FutureSalt : ITLObject
{
public DateTime valid_since;
public DateTime valid_until;
public long salt;
}
[TLDef(0xAE500895)] //future_salts#ae500895 req_msg_id:long now:int salts:vector<future_salt> = FutureSalts
public class FutureSalts : ITLObject
{
public long req_msg_id;
public DateTime now;
public FutureSalt[] salts;
}
[TLDef(0x347773C5)] //pong#347773c5 msg_id:long ping_id:long = Pong
public class Pong : ITLObject
{
public long msg_id;
public long ping_id;
}
public abstract class DestroySessionRes : ITLObject { public long session_id; }
[TLDef(0xE22045FC)] //destroy_session_ok#e22045fc session_id:long = DestroySessionRes
public class DestroySessionOk : DestroySessionRes { }
[TLDef(0x62D350C9)] //destroy_session_none#62d350c9 session_id:long = DestroySessionRes
public class DestroySessionNone : DestroySessionRes { }
public abstract class NewSession : ITLObject { }
[TLDef(0x9EC20908)] //new_session_created#9ec20908 first_msg_id:long unique_id:long server_salt:long = NewSession
public class NewSessionCreated : NewSession
{
public long first_msg_id;
public long unique_id;
public long server_salt;
}
public abstract class MessageContainer : ITLObject { }
[TLDef(0x73F1F8DC)] //msg_container#73f1f8dc messages:vector<%Message> = MessageContainer
public class MsgContainer : MessageContainer { public _Message[] messages; }
#pragma warning disable IDE1006 // Naming Styles
//[TLDef(0x5BB8E511)] //message#5bb8e511 msg_id:long seqno:int bytes:int body:Object = Message
public class _Message
{
public long msg_id;
public int seqno;
public int bytes;
public ITLObject body;
}
#pragma warning restore IDE1006 // Naming Styles
public abstract class MessageCopy : ITLObject { }
[TLDef(0xE06046B2)] //msg_copy#e06046b2 orig_message:Message = MessageCopy
public class MsgCopy : MessageCopy { public _Message orig_message; }
[TLDef(0x3072CFA1)] //gzip_packed#3072cfa1 packed_data:bytes = Object
public class GzipPacked : ITLObject { public byte[] packed_data; }
[TLDef(0x62D6B459)] //msgs_ack#62d6b459 msg_ids:Vector<long> = MsgsAck
public class MsgsAck : ITLObject { public long[] msg_ids; }
[TLDef(0xA7EFF811)] //bad_msg_notification#a7eff811 bad_msg_id:long bad_msg_seqno:int error_code:int = BadMsgNotification
public class BadMsgNotification : ITLObject
{
public long bad_msg_id;
public int bad_msg_seqno;
public int error_code;
}
[TLDef(0xEDAB447B)] //bad_server_salt#edab447b bad_msg_id:long bad_msg_seqno:int error_code:int new_server_salt:long = BadMsgNotification
public class BadServerSalt : BadMsgNotification { public long new_server_salt; }
[TLDef(0x7D861A08)] //msg_resend_req#7d861a08 msg_ids:Vector<long> = MsgResendReq
public class MsgResendReq : ITLObject { public long[] msg_ids; }
[TLDef(0xDA69FB52)] //msgs_state_req#da69fb52 msg_ids:Vector<long> = MsgsStateReq
public class MsgsStateReq : ITLObject { public long[] msg_ids; }
[TLDef(0x04DEB57D)] //msgs_state_info#04deb57d req_msg_id:long info:bytes = MsgsStateInfo
public class MsgsStateInfo : ITLObject
{
public long req_msg_id;
public byte[] info;
}
[TLDef(0x8CC0D131)] //msgs_all_info#8cc0d131 msg_ids:Vector<long> info:bytes = MsgsAllInfo
public class MsgsAllInfo : ITLObject
{
public long[] msg_ids;
public byte[] info;
}
public abstract class MsgDetailedInfoBase : ITLObject { }
[TLDef(0x276D3EC6)] //msg_detailed_info#276d3ec6 msg_id:long answer_msg_id:long bytes:int status:int = MsgDetailedInfo
public class MsgDetailedInfo : MsgDetailedInfoBase
{
public long msg_id;
public long answer_msg_id;
public int bytes;
public int status;
}
[TLDef(0x809DB6DF)] //msg_new_detailed_info#809db6df answer_msg_id:long bytes:int status:int = MsgDetailedInfo
public class MsgNewDetailedInfo : MsgDetailedInfoBase
{
public long answer_msg_id;
public int bytes;
public int status;
}
[TLDef(0x7A19CB76)] //rsa_public_key n:string e:string = RSAPublicKey
public class RSAPublicKey : ITLObject
{
public byte[] n;
public byte[] e;
}
public abstract class DestroyAuthKeyRes : ITLObject { }
[TLDef(0xF660E1D4)] //destroy_auth_key_ok#f660e1d4 = DestroyAuthKeyRes
public class DestroyAuthKeyOk : DestroyAuthKeyRes { }
[TLDef(0x0A9F2259)] //destroy_auth_key_none#0a9f2259 = DestroyAuthKeyRes
public class DestroyAuthKeyNone : DestroyAuthKeyRes { }
[TLDef(0xEA109B13)] //destroy_auth_key_fail#ea109b13 = DestroyAuthKeyRes
public class DestroyAuthKeyFail : DestroyAuthKeyRes { }
public static partial class Fn // ---functions---
{
[TLDef(0x60469778)] //req_pq#60469778 nonce:int128 = ResPQ
public class ReqPQ : ITLFunction<ResPQ> { public Int128 nonce; }
[TLDef(0xBE7E8EF1)] //req_pq_multi#be7e8ef1 nonce:int128 = ResPQ
public class ReqPQmulti : ResPQ { }
[TLDef(0xD712E4BE)] //req_DH_params#d712e4be nonce:int128 server_nonce:int128 p:bytes q:bytes public_key_fingerprint:long encrypted_data:bytes = Server_DH_Params
public class ReqDHParams : ITLFunction<ServerDHParams>
{
public Int128 nonce;
public Int128 server_nonce;
public byte[] p;
public byte[] q;
public long public_key_fingerprint;
public byte[] encrypted_data;
}
[TLDef(0xF5045F1F)] //set_client_DH_params#f5045f1f nonce:int128 server_nonce:int128 encrypted_data:bytes = Set_client_DH_params_answer
public class SetClientDHParams : ITLFunction<SetClientDHParamsAnswer>
{
public Int128 nonce;
public Int128 server_nonce;
public byte[] encrypted_data;
}
[TLDef(0x58E4A740)] //rpc_drop_answer#58e4a740 req_msg_id:long = RpcDropAnswer
public class ReqRpcDropAnswer : ITLFunction<RpcDropAnswer> { public long req_msg_id; }
[TLDef(0xB921BD04)] //get_future_salts#b921bd04 num:int = FutureSalts
public class GetFutureSalts : ITLFunction<FutureSalts> { public int num; }
[TLDef(0x7ABE77EC)] //ping#7abe77ec ping_id:long = Pong
public class Ping : ITLFunction<Pong> { public long ping_id; }
[TLDef(0xF3427B8C)] //ping_delay_disconnect#f3427b8c ping_id:long disconnect_delay:int = Pong
public class PingDelayDisconnect : ITLFunction<Pong>
{
public long ping_id;
public int disconnect_delay; // seconds
}
[TLDef(0xE7512126)] //destroy_session#e7512126 session_id:long = DestroySessionRes
public class DestroySession : ITLFunction<DestroySessionRes> { public long session_id; }
[TLDef(0x9299359F)] //http_wait#9299359f max_delay:int wait_after:int max_wait:int = HttpWait
public class HttpWait : ITLObject
{
public int max_delay; // ms
public int wait_after; // ms
public int max_wait; // ms
}
[TLDef(0xD1435160)] //destroy_auth_key#d1435160 = DestroyAuthKeyRes
public class DestroyAuthKey : ITLFunction<DestroyAuthKeyRes> { }
}
}

7435
src/TL.Schema.cs Normal file

File diff suppressed because it is too large Load diff

362
src/TL.Secret.cs Normal file
View file

@ -0,0 +1,362 @@
using System;
namespace TL
{
namespace Layer8
{
public abstract class DecryptedMessageBase : ITLObject { }
[TLDef(0x1F814F1F)] //decryptedMessage#1f814f1f random_id:long random_bytes:bytes message:string media:DecryptedMessageMedia = DecryptedMessage
public class DecryptedMessage : DecryptedMessageBase
{
public long random_id;
public byte[] random_bytes;
public string message;
public DecryptedMessageMedia media;
}
[TLDef(0xAA48327D)] //decryptedMessageService#aa48327d random_id:long random_bytes:bytes action:DecryptedMessageAction = DecryptedMessage
public class DecryptedMessageService : DecryptedMessageBase
{
public long random_id;
public byte[] random_bytes;
public DecryptedMessageAction action;
}
public abstract class DecryptedMessageMedia : ITLObject { }
[TLDef(0x089F5C4A)] //decryptedMessageMediaEmpty#089f5c4a = DecryptedMessageMedia
public class DecryptedMessageMediaEmpty : DecryptedMessageMedia { }
[TLDef(0x32798A8C)] //decryptedMessageMediaPhoto#32798a8c thumb:bytes thumb_w:int thumb_h:int w:int h:int size:int key:bytes iv:bytes = DecryptedMessageMedia
public class DecryptedMessageMediaPhoto : DecryptedMessageMedia
{
public byte[] thumb;
public int thumb_w;
public int thumb_h;
public int w;
public int h;
public int size;
public byte[] key;
public byte[] iv;
}
[TLDef(0x4CEE6EF3)] //decryptedMessageMediaVideo#4cee6ef3 thumb:bytes thumb_w:int thumb_h:int duration:int w:int h:int size:int key:bytes iv:bytes = DecryptedMessageMedia
public class DecryptedMessageMediaVideo : DecryptedMessageMedia
{
public byte[] thumb;
public int thumb_w;
public int thumb_h;
public int duration;
public int w;
public int h;
public int size;
public byte[] key;
public byte[] iv;
}
[TLDef(0x35480A59)] //decryptedMessageMediaGeoPoint#35480a59 lat:double long:double = DecryptedMessageMedia
public class DecryptedMessageMediaGeoPoint : DecryptedMessageMedia
{
public double lat;
public double long_;
}
[TLDef(0x588A0A97)] //decryptedMessageMediaContact#588a0a97 phone_number:string first_name:string last_name:string user_id:int = DecryptedMessageMedia
public class DecryptedMessageMediaContact : DecryptedMessageMedia
{
public string phone_number;
public string first_name;
public string last_name;
public int user_id;
}
[TLDef(0xB095434B)] //decryptedMessageMediaDocument#b095434b thumb:bytes thumb_w:int thumb_h:int file_name:string mime_type:string size:int key:bytes iv:bytes = DecryptedMessageMedia
public class DecryptedMessageMediaDocument : DecryptedMessageMedia
{
public byte[] thumb;
public int thumb_w;
public int thumb_h;
public string file_name;
public string mime_type;
public int size;
public byte[] key;
public byte[] iv;
}
[TLDef(0x6080758F)] //decryptedMessageMediaAudio#6080758f duration:int size:int key:bytes iv:bytes = DecryptedMessageMedia
public class DecryptedMessageMediaAudio : DecryptedMessageMedia
{
public int duration;
public int size;
public byte[] key;
public byte[] iv;
}
public abstract class DecryptedMessageAction : ITLObject { }
[TLDef(0xA1733AEC)] //decryptedMessageActionSetMessageTTL#a1733aec ttl_seconds:int = DecryptedMessageAction
public class DecryptedMessageActionSetMessageTTL : DecryptedMessageAction { public int ttl_seconds; }
[TLDef(0x0C4F40BE)] //decryptedMessageActionReadMessages#0c4f40be random_ids:Vector<long> = DecryptedMessageAction
public class DecryptedMessageActionReadMessages : DecryptedMessageAction { public long[] random_ids; }
[TLDef(0x65614304)] //decryptedMessageActionDeleteMessages#65614304 random_ids:Vector<long> = DecryptedMessageAction
public class DecryptedMessageActionDeleteMessages : DecryptedMessageAction { public long[] random_ids; }
[TLDef(0x8AC1F475)] //decryptedMessageActionScreenshotMessages#8ac1f475 random_ids:Vector<long> = DecryptedMessageAction
public class DecryptedMessageActionScreenshotMessages : DecryptedMessageAction { public long[] random_ids; }
[TLDef(0x6719E45C)] //decryptedMessageActionFlushHistory#6719e45c = DecryptedMessageAction
public class DecryptedMessageActionFlushHistory : DecryptedMessageAction { }
}
namespace Layer17
{
public abstract class DecryptedMessageBase : ITLObject { }
[TLDef(0x204D3878)] //decryptedMessage#204d3878 random_id:long ttl:int message:string media:DecryptedMessageMedia = DecryptedMessage
public class DecryptedMessage : DecryptedMessageBase
{
public long random_id;
public int ttl;
public string message;
public DecryptedMessageMedia media;
}
[TLDef(0x73164160)] //decryptedMessageService#73164160 random_id:long action:DecryptedMessageAction = DecryptedMessage
public class DecryptedMessageService : DecryptedMessageBase
{
public long random_id;
public DecryptedMessageAction action;
}
public abstract class DecryptedMessageMedia : ITLObject { }
[TLDef(0x524A415D)] //decryptedMessageMediaVideo#524a415d thumb:bytes thumb_w:int thumb_h:int duration:int mime_type:string w:int h:int size:int key:bytes iv:bytes = DecryptedMessageMedia
public class DecryptedMessageMediaVideo : DecryptedMessageMedia
{
public byte[] thumb;
public int thumb_w;
public int thumb_h;
public int duration;
public string mime_type;
public int w;
public int h;
public int size;
public byte[] key;
public byte[] iv;
}
[TLDef(0x57E0A9CB)] //decryptedMessageMediaAudio#57e0a9cb duration:int mime_type:string size:int key:bytes iv:bytes = DecryptedMessageMedia
public class DecryptedMessageMediaAudio : DecryptedMessageMedia
{
public int duration;
public string mime_type;
public int size;
public byte[] key;
public byte[] iv;
}
[TLDef(0x1BE31789)] //decryptedMessageLayer#1be31789 random_bytes:bytes layer:int in_seq_no:int out_seq_no:int message:DecryptedMessage = DecryptedMessageLayer
public class DecryptedMessageLayer : ITLObject
{
public byte[] random_bytes;
public int layer;
public int in_seq_no;
public int out_seq_no;
public DecryptedMessageBase message;
}
[TLDef(0x92042FF7)] //sendMessageUploadVideoAction#92042ff7 = SendMessageAction
public class SendMessageUploadVideoAction : SendMessageAction { }
[TLDef(0xE6AC8A6F)] //sendMessageUploadAudioAction#e6ac8a6f = SendMessageAction
public class SendMessageUploadAudioAction : SendMessageAction { }
[TLDef(0x990A3C1A)] //sendMessageUploadPhotoAction#990a3c1a = SendMessageAction
public class SendMessageUploadPhotoAction : SendMessageAction { }
[TLDef(0x8FAEE98E)] //sendMessageUploadDocumentAction#8faee98e = SendMessageAction
public class SendMessageUploadDocumentAction : SendMessageAction { }
public abstract class DecryptedMessageAction : ITLObject { }
[TLDef(0x511110B0)] //decryptedMessageActionResend#511110b0 start_seq_no:int end_seq_no:int = DecryptedMessageAction
public class DecryptedMessageActionResend : DecryptedMessageAction
{
public int start_seq_no;
public int end_seq_no;
}
[TLDef(0xF3048883)] //decryptedMessageActionNotifyLayer#f3048883 layer:int = DecryptedMessageAction
public class DecryptedMessageActionNotifyLayer : DecryptedMessageAction { public int layer; }
[TLDef(0xCCB27641)] //decryptedMessageActionTyping#ccb27641 action:SendMessageAction = DecryptedMessageAction
public class DecryptedMessageActionTyping : DecryptedMessageAction { public SendMessageAction action; }
}
namespace Layer20
{
public abstract class DecryptedMessageAction : ITLObject { }
[TLDef(0xF3C9611B)] //decryptedMessageActionRequestKey#f3c9611b exchange_id:long g_a:bytes = DecryptedMessageAction
public class DecryptedMessageActionRequestKey : DecryptedMessageAction
{
public long exchange_id;
public byte[] g_a;
}
[TLDef(0x6FE1735B)] //decryptedMessageActionAcceptKey#6fe1735b exchange_id:long g_b:bytes key_fingerprint:long = DecryptedMessageAction
public class DecryptedMessageActionAcceptKey : DecryptedMessageAction
{
public long exchange_id;
public byte[] g_b;
public long key_fingerprint;
}
[TLDef(0xDD05EC6B)] //decryptedMessageActionAbortKey#dd05ec6b exchange_id:long = DecryptedMessageAction
public class DecryptedMessageActionAbortKey : DecryptedMessageAction { public long exchange_id; }
[TLDef(0xEC2E0B9B)] //decryptedMessageActionCommitKey#ec2e0b9b exchange_id:long key_fingerprint:long = DecryptedMessageAction
public class DecryptedMessageActionCommitKey : DecryptedMessageAction
{
public long exchange_id;
public long key_fingerprint;
}
[TLDef(0xA82FDD63)] //decryptedMessageActionNoop#a82fdd63 = DecryptedMessageAction
public class DecryptedMessageActionNoop : DecryptedMessageAction { }
}
namespace Layer23
{
[TLDef(0xFB0A5727)] //documentAttributeSticker#fb0a5727 = DocumentAttribute
public class DocumentAttributeSticker : DocumentAttribute { }
[TLDef(0x5910CCCB)] //documentAttributeVideo#5910cccb duration:int w:int h:int = DocumentAttribute
public class DocumentAttributeVideo : DocumentAttribute
{
public int duration;
public int w;
public int h;
}
[TLDef(0x051448E5)] //documentAttributeAudio#051448e5 duration:int = DocumentAttribute
public class DocumentAttributeAudio : DocumentAttribute { public int duration; }
[TLDef(0x7C596B46)] //fileLocationUnavailable#7c596b46 volume_id:long local_id:int secret:long = FileLocation
public class FileLocationUnavailable : FileLocation
{
public long volume_id;
public int local_id;
public long secret;
}
[TLDef(0x53D69076)] //fileLocation#53d69076 dc_id:int volume_id:long local_id:int secret:long = FileLocation
public class FileLocation_ : FileLocation
{
public int dc_id;
public long volume_id;
public int local_id;
public long secret;
}
public abstract class DecryptedMessageMedia : ITLObject { }
[TLDef(0xFA95B0DD)] //decryptedMessageMediaExternalDocument#fa95b0dd id:long access_hash:long date:int mime_type:string size:int thumb:PhotoSize dc_id:int attributes:Vector<DocumentAttribute> = DecryptedMessageMedia
public class DecryptedMessageMediaExternalDocument : DecryptedMessageMedia
{
public long id;
public long access_hash;
public DateTime date;
public string mime_type;
public int size;
public PhotoSize thumb;
public int dc_id;
public DocumentAttribute[] attributes;
}
}
namespace Layer45
{
[TLDef(0x36B091DE)] //decryptedMessage#36b091de flags:# random_id:long ttl:int message:string media:flags.9?DecryptedMessageMedia entities:flags.7?Vector<MessageEntity> via_bot_name:flags.11?string reply_to_random_id:flags.3?long = DecryptedMessage
public class DecryptedMessage : ITLObject
{
[Flags] public enum Flags { has_reply_to_random_id = 0x8, has_entities = 0x80, has_media = 0x200, has_via_bot_name = 0x800 }
public Flags flags;
public long random_id;
public int ttl;
public string message;
[IfFlag(9)] public DecryptedMessageMedia media;
[IfFlag(7)] public MessageEntity[] entities;
[IfFlag(11)] public string via_bot_name;
[IfFlag(3)] public long reply_to_random_id;
}
public abstract class DecryptedMessageMedia : ITLObject { }
[TLDef(0xF1FA8D78)] //decryptedMessageMediaPhoto#f1fa8d78 thumb:bytes thumb_w:int thumb_h:int w:int h:int size:int key:bytes iv:bytes caption:string = DecryptedMessageMedia
public class DecryptedMessageMediaPhoto : DecryptedMessageMedia
{
public byte[] thumb;
public int thumb_w;
public int thumb_h;
public int w;
public int h;
public int size;
public byte[] key;
public byte[] iv;
public string caption;
}
[TLDef(0x970C8C0E)] //decryptedMessageMediaVideo#970c8c0e thumb:bytes thumb_w:int thumb_h:int duration:int mime_type:string w:int h:int size:int key:bytes iv:bytes caption:string = DecryptedMessageMedia
public class DecryptedMessageMediaVideo : DecryptedMessageMedia
{
public byte[] thumb;
public int thumb_w;
public int thumb_h;
public int duration;
public string mime_type;
public int w;
public int h;
public int size;
public byte[] key;
public byte[] iv;
public string caption;
}
[TLDef(0x7AFE8AE2)] //decryptedMessageMediaDocument#7afe8ae2 thumb:bytes thumb_w:int thumb_h:int mime_type:string size:int key:bytes iv:bytes attributes:Vector<DocumentAttribute> caption:string = DecryptedMessageMedia
public class DecryptedMessageMediaDocument : DecryptedMessageMedia
{
public byte[] thumb;
public int thumb_w;
public int thumb_h;
public string mime_type;
public int size;
public byte[] key;
public byte[] iv;
public DocumentAttribute[] attributes;
public string caption;
}
[TLDef(0x8A0DF56F)] //decryptedMessageMediaVenue#8a0df56f lat:double long:double title:string address:string provider:string venue_id:string = DecryptedMessageMedia
public class DecryptedMessageMediaVenue : DecryptedMessageMedia
{
public double lat;
public double long_;
public string title;
public string address;
public string provider;
public string venue_id;
}
[TLDef(0xE50511D8)] //decryptedMessageMediaWebPage#e50511d8 url:string = DecryptedMessageMedia
public class DecryptedMessageMediaWebPage : DecryptedMessageMedia { public string url; }
[TLDef(0x3A556302)] //documentAttributeSticker#3a556302 alt:string stickerset:InputStickerSet = DocumentAttribute
public class DocumentAttributeSticker : DocumentAttribute
{
public string alt;
public InputStickerSet stickerset;
}
[TLDef(0xDED218E0)] //documentAttributeAudio#ded218e0 duration:int title:string performer:string = DocumentAttribute
public class DocumentAttributeAudio : DocumentAttribute
{
public int duration;
public string title;
public string performer;
}
}
namespace Layer46
{ }
namespace Layer66
{
[TLDef(0xBB718624)] //sendMessageUploadRoundAction#bb718624 = SendMessageAction
public class SendMessageUploadRoundAction : SendMessageAction { }
}
namespace Layer73
{
[TLDef(0x91CC4674)] //decryptedMessage#91cc4674 flags:# random_id:long ttl:int message:string media:flags.9?DecryptedMessageMedia entities:flags.7?Vector<MessageEntity> via_bot_name:flags.11?string reply_to_random_id:flags.3?long grouped_id:flags.17?long = DecryptedMessage
public class DecryptedMessage : ITLObject
{
[Flags] public enum Flags { has_reply_to_random_id = 0x8, has_entities = 0x80, has_media = 0x200, has_via_bot_name = 0x800,
has_grouped_id = 0x20000 }
public Flags flags;
public long random_id;
public int ttl;
public string message;
[IfFlag(9)] public Layer45.DecryptedMessageMedia media;
[IfFlag(7)] public MessageEntity[] entities;
[IfFlag(11)] public string via_bot_name;
[IfFlag(3)] public long reply_to_random_id;
[IfFlag(17)] public long grouped_id;
}
}
}

951
src/TL.Table.cs Normal file
View file

@ -0,0 +1,951 @@
using System;
using System.Collections.Generic;
namespace TL
{
static partial class Schema
{
public const int Layer = 121; // fetched 07/08/2021 01:34:33
public const int VectorCtor = 0x1CB5C415;
public const int NullCtor = 0x56730BCC;
internal readonly static Dictionary<uint, Type> Table = new()
{
// from TL.MTProto:
[0x05162463] = typeof(ResPQ),
[0xA9F55F95] = typeof(PQInnerDataDC),
[0x56FDDF88] = typeof(PQInnerDataTempDC),
[0xD0E8075C] = typeof(ServerDHParamsOk),
[0xB5890DBA] = typeof(ServerDHInnerData),
[0x6643B654] = typeof(ClientDHInnerData),
[0x3BCBF734] = typeof(DHGenOk),
[0x46DC1FB9] = typeof(DHGenRetry),
[0xA69DAE02] = typeof(DHGenFail),
[0x75A3F765] = typeof(BindAuthKeyInner),
[0xF35C6D01] = typeof(RpcResult),
[0x2144CA19] = typeof(RpcError),
[0x5E2AD36E] = typeof(RpcAnswerUnknown),
[0xCD78E586] = typeof(RpcAnswerDroppedRunning),
[0xA43AD8B7] = typeof(RpcAnswerDropped),
[0x0949D9DC] = typeof(FutureSalt),
[0xAE500895] = typeof(FutureSalts),
[0x347773C5] = typeof(Pong),
[0xE22045FC] = typeof(DestroySessionOk),
[0x62D350C9] = typeof(DestroySessionNone),
[0x9EC20908] = typeof(NewSessionCreated),
[0x73F1F8DC] = typeof(MsgContainer),
[0x5BB8E511] = typeof(_Message),
[0xE06046B2] = typeof(MsgCopy),
[0x3072CFA1] = typeof(GzipPacked),
[0x62D6B459] = typeof(MsgsAck),
[0xA7EFF811] = typeof(BadMsgNotification),
[0xEDAB447B] = typeof(BadServerSalt),
[0x7D861A08] = typeof(MsgResendReq),
[0xDA69FB52] = typeof(MsgsStateReq),
[0x04DEB57D] = typeof(MsgsStateInfo),
[0x8CC0D131] = typeof(MsgsAllInfo),
[0x276D3EC6] = typeof(MsgDetailedInfo),
[0x809DB6DF] = typeof(MsgNewDetailedInfo),
[0xF660E1D4] = typeof(DestroyAuthKeyOk),
[0x0A9F2259] = typeof(DestroyAuthKeyNone),
[0xEA109B13] = typeof(DestroyAuthKeyFail),
// from TL.Schema:
[0x3FEDD339] = typeof(True),
[0xC4B9F9BB] = typeof(Error),
[0x56730BCC] = typeof(Null),
[0x7F3B18EA] = typeof(InputPeerEmpty),
[0x7DA07EC9] = typeof(InputPeerSelf),
[0x179BE863] = typeof(InputPeerChat),
[0xB98886CF] = typeof(InputUserEmpty),
[0xF7C1B13F] = typeof(InputUserSelf),
[0xF392B7F4] = typeof(InputPhoneContact),
[0xF52FF27F] = typeof(InputFile),
[0x9664F57F] = typeof(InputMediaEmpty),
[0x1E287D04] = typeof(InputMediaUploadedPhoto),
[0xB3BA0635] = typeof(InputMediaPhoto),
[0xF9C44144] = typeof(InputMediaGeoPoint),
[0xF8AB7DFB] = typeof(InputMediaContact),
[0x1CA48F57] = typeof(InputChatPhotoEmpty),
[0xC642724E] = typeof(InputChatUploadedPhoto),
[0x8953AD37] = typeof(InputChatPhoto),
[0xE4C123D6] = typeof(InputGeoPointEmpty),
[0x48222FAF] = typeof(InputGeoPoint),
[0x1CD7BF0D] = typeof(InputPhotoEmpty),
[0x3BB3B94A] = typeof(InputPhoto),
[0xDFDAABE1] = typeof(InputFileLocation),
[0x9DB1BC6D] = typeof(PeerUser),
[0xBAD0E5BB] = typeof(PeerChat),
[0xAA963B05] = typeof(Storage_FileUnknown),
[0x40BC6F52] = typeof(Storage_FilePartial),
[0x007EFE0E] = typeof(Storage_FileJpeg),
[0xCAE1AADF] = typeof(Storage_FileGif),
[0x0A4F63C0] = typeof(Storage_FilePng),
[0xAE1E508D] = typeof(Storage_FilePdf),
[0x528A0677] = typeof(Storage_FileMp3),
[0x4B09EBBC] = typeof(Storage_FileMov),
[0xB3CEA0E4] = typeof(Storage_FileMp4),
[0x1081464C] = typeof(Storage_FileWebp),
[0x200250BA] = typeof(UserEmpty),
[0x4F11BAE1] = typeof(UserProfilePhotoEmpty),
[0x69D3AB26] = typeof(UserProfilePhoto),
[0x09D05049] = typeof(UserStatusEmpty),
[0xEDB93949] = typeof(UserStatusOnline),
[0x008C703F] = typeof(UserStatusOffline),
[0x9BA2D800] = typeof(ChatEmpty),
[0x3BDA1BDE] = typeof(Chat),
[0x07328BDB] = typeof(ChatForbidden),
[0x1B7C9DB3] = typeof(ChatFull),
[0xC8D7493E] = typeof(ChatParticipant),
[0xFC900C2B] = typeof(ChatParticipantsForbidden),
[0x3F460FED] = typeof(ChatParticipants),
[0x37C1011C] = typeof(ChatPhotoEmpty),
[0xD20B9F3C] = typeof(ChatPhoto),
[0x83E5DE54] = typeof(MessageEmpty),
[0x58AE39C9] = typeof(Message),
[0x286FA604] = typeof(MessageService),
[0x3DED6320] = typeof(MessageMediaEmpty),
[0x695150D7] = typeof(MessageMediaPhoto),
[0x56E0D474] = typeof(MessageMediaGeo),
[0xCBF24940] = typeof(MessageMediaContact),
[0x9F84F49E] = typeof(MessageMediaUnsupported),
[0xB6AEF7B0] = typeof(MessageActionEmpty),
[0xA6638B9A] = typeof(MessageActionChatCreate),
[0xB5A1CE5A] = typeof(MessageActionChatEditTitle),
[0x7FCB13A8] = typeof(MessageActionChatEditPhoto),
[0x95E3FBEF] = typeof(MessageActionChatDeletePhoto),
[0x488A7337] = typeof(MessageActionChatAddUser),
[0xB2AE9B0C] = typeof(MessageActionChatDeleteUser),
[0x2C171F72] = typeof(Dialog),
[0x2331B22D] = typeof(PhotoEmpty),
[0xFB197A65] = typeof(Photo),
[0x0E17E23C] = typeof(PhotoSizeEmpty),
[0x77BFB61B] = typeof(PhotoSize),
[0xE9A734FA] = typeof(PhotoCachedSize),
[0x1117DD5F] = typeof(GeoPointEmpty),
[0xB2A2F663] = typeof(GeoPoint),
[0x5E002502] = typeof(Auth_SentCode),
[0xCD050916] = typeof(Auth_Authorization),
[0xDF969C2D] = typeof(Auth_ExportedAuthorization),
[0xB8BC5B0C] = typeof(InputNotifyPeer),
[0x193B4417] = typeof(InputNotifyUsers),
[0x4A95E84E] = typeof(InputNotifyChats),
[0x9C3D198E] = typeof(InputPeerNotifySettings),
[0xAF509D20] = typeof(PeerNotifySettings),
[0x733F2961] = typeof(PeerSettings),
[0xA437C3ED] = typeof(WallPaper),
[0x58DBCAB8] = typeof(InputReportReasonSpam),
[0x1E22C78D] = typeof(InputReportReasonViolence),
[0x2E59D922] = typeof(InputReportReasonPornography),
[0xADF44EE3] = typeof(InputReportReasonChildAbuse),
[0xE1746D0A] = typeof(InputReportReasonOther),
[0xEDF17C12] = typeof(UserFull),
[0xF911C994] = typeof(Contact),
[0xD0028438] = typeof(ImportedContact),
[0xD3680C61] = typeof(ContactStatus),
[0xB74BA9D2] = typeof(Contacts_ContactsNotModified),
[0xEAE87E42] = typeof(Contacts_Contacts),
[0x77D01C3B] = typeof(Contacts_ImportedContacts),
[0x0ADE1591] = typeof(Contacts_Blocked),
[0xE1664194] = typeof(Contacts_BlockedSlice),
[0x15BA6C40] = typeof(Messages_Dialogs),
[0x71E094F3] = typeof(Messages_DialogsSlice),
[0x8C718E87] = typeof(Messages_Messages),
[0x3A54685E] = typeof(Messages_MessagesSlice),
[0x64FF9FD5] = typeof(Messages_Chats),
[0xE5D7D19C] = typeof(Messages_ChatFull),
[0xB45C69D1] = typeof(Messages_AffectedHistory),
[0x57E2F66C] = typeof(InputMessagesFilterEmpty),
[0x9609A51C] = typeof(InputMessagesFilterPhotos),
[0x9FC00E65] = typeof(InputMessagesFilterVideo),
[0x56E9F0E4] = typeof(InputMessagesFilterPhotoVideo),
[0x9EDDF188] = typeof(InputMessagesFilterDocument),
[0x7EF0DD87] = typeof(InputMessagesFilterUrl),
[0xFFC86587] = typeof(InputMessagesFilterGif),
[0x1F2B0AFD] = typeof(UpdateNewMessage),
[0x4E90BFD6] = typeof(UpdateMessageID),
[0xA20DB0E5] = typeof(UpdateDeleteMessages),
[0x5C486927] = typeof(UpdateUserTyping),
[0x9A65EA1F] = typeof(UpdateChatUserTyping),
[0x07761198] = typeof(UpdateChatParticipants),
[0x1BFBD823] = typeof(UpdateUserStatus),
[0xA7332B73] = typeof(UpdateUserName),
[0x95313B0C] = typeof(UpdateUserPhoto),
[0xA56C2A3E] = typeof(Updates_State),
[0x5D75A138] = typeof(Updates_DifferenceEmpty),
[0x00F49CA0] = typeof(Updates_Difference),
[0xA8FB1981] = typeof(Updates_DifferenceSlice),
[0xE317AF7E] = typeof(UpdatesTooLong),
[0x2296D2C8] = typeof(UpdateShortMessage),
[0x402D5DBB] = typeof(UpdateShortChatMessage),
[0x78D4DEC1] = typeof(UpdateShort),
[0x725B04C3] = typeof(UpdatesCombined),
[0x74AE4240] = typeof(Updates),
[0x8DCA6AA5] = typeof(Photos_Photos),
[0x15051F54] = typeof(Photos_PhotosSlice),
[0x20212CA8] = typeof(Photos_Photo),
[0x096A18D5] = typeof(Upload_File),
[0x18B7A10D] = typeof(DcOption),
[0x330B4067] = typeof(Config),
[0x8E1A1775] = typeof(NearestDc),
[0x1DA7158F] = typeof(Help_AppUpdate),
[0xC45A6536] = typeof(Help_NoAppUpdate),
[0x18CB9F78] = typeof(Help_InviteText),
[0x12BCBD9A] = typeof(UpdateNewEncryptedMessage),
[0x1710F156] = typeof(UpdateEncryptedChatTyping),
[0xB4A2E88D] = typeof(UpdateEncryption),
[0x38FE25B7] = typeof(UpdateEncryptedMessagesRead),
[0xAB7EC0A0] = typeof(EncryptedChatEmpty),
[0x3BF703DC] = typeof(EncryptedChatWaiting),
[0x62718A82] = typeof(EncryptedChatRequested),
[0xFA56CE36] = typeof(EncryptedChat),
[0x13D6DD27] = typeof(EncryptedChatDiscarded),
[0xF141B5E1] = typeof(InputEncryptedChat),
[0xC21F497E] = typeof(EncryptedFileEmpty),
[0x4A70994C] = typeof(EncryptedFile),
[0x1837C364] = typeof(InputEncryptedFileEmpty),
[0x64BD0306] = typeof(InputEncryptedFileUploaded),
[0x5A17B5E5] = typeof(InputEncryptedFile),
[0xF5235D55] = typeof(InputEncryptedFileLocation),
[0xED18C118] = typeof(EncryptedMessage),
[0x23734B06] = typeof(EncryptedMessageService),
[0xC0E24635] = typeof(Messages_DhConfigNotModified),
[0x2C221EDD] = typeof(Messages_DhConfig),
[0x560F8935] = typeof(Messages_SentEncryptedMessage),
[0x9493FF32] = typeof(Messages_SentEncryptedFile),
[0xFA4F0BB5] = typeof(InputFileBig),
[0x2DC173C8] = typeof(InputEncryptedFileBigUploaded),
[0xEA4B0E5C] = typeof(UpdateChatParticipantAdd),
[0x6E5F8C22] = typeof(UpdateChatParticipantDelete),
[0x8E5E9873] = typeof(UpdateDcOptions),
[0x5B38C6C1] = typeof(InputMediaUploadedDocument),
[0x23AB23D2] = typeof(InputMediaDocument),
[0x9CB070D7] = typeof(MessageMediaDocument),
[0x72F0EAAE] = typeof(InputDocumentEmpty),
[0x1ABFB575] = typeof(InputDocument),
[0xBAD07584] = typeof(InputDocumentFileLocation),
[0x36F8C871] = typeof(DocumentEmpty),
[0x1E87342B] = typeof(Document),
[0x17C6B5F6] = typeof(Help_Support),
[0x9FD40BD8] = typeof(NotifyPeer),
[0xB4C83B4C] = typeof(NotifyUsers),
[0xC007CEC3] = typeof(NotifyChats),
[0xBEC268EF] = typeof(UpdateNotifySettings),
[0x16BF744E] = typeof(SendMessageTypingAction),
[0xFD5EC8F5] = typeof(SendMessageCancelAction),
[0xA187D66F] = typeof(SendMessageRecordVideoAction),
[0xE9763AEC] = typeof(SendMessageUploadVideoAction),
[0xD52F73F7] = typeof(SendMessageRecordAudioAction),
[0xF351D7AB] = typeof(SendMessageUploadAudioAction),
[0xD1D34A26] = typeof(SendMessageUploadPhotoAction),
[0xAA0CD9E4] = typeof(SendMessageUploadDocumentAction),
[0x176F8BA1] = typeof(SendMessageGeoLocationAction),
[0x628CBC6F] = typeof(SendMessageChooseContactAction),
[0xB3134D9D] = typeof(Contacts_Found),
[0xEBE46819] = typeof(UpdateServiceNotification),
[0xE26F42F1] = typeof(UserStatusRecently),
[0x07BF09FC] = typeof(UserStatusLastWeek),
[0x77EBC742] = typeof(UserStatusLastMonth),
[0xEE3B272A] = typeof(UpdatePrivacy),
[0x4F96CB18] = typeof(InputPrivacyKeyStatusTimestamp),
[0xBC2EAB30] = typeof(PrivacyKeyStatusTimestamp),
[0x0D09E07B] = typeof(InputPrivacyValueAllowContacts),
[0x184B35CE] = typeof(InputPrivacyValueAllowAll),
[0x131CC67F] = typeof(InputPrivacyValueAllowUsers),
[0x0BA52007] = typeof(InputPrivacyValueDisallowContacts),
[0xD66B66C9] = typeof(InputPrivacyValueDisallowAll),
[0x90110467] = typeof(InputPrivacyValueDisallowUsers),
[0xFFFE1BAC] = typeof(PrivacyValueAllowContacts),
[0x65427B82] = typeof(PrivacyValueAllowAll),
[0x4D5BBE0C] = typeof(PrivacyValueAllowUsers),
[0xF888FA1A] = typeof(PrivacyValueDisallowContacts),
[0x8B73E763] = typeof(PrivacyValueDisallowAll),
[0x0C7F49B7] = typeof(PrivacyValueDisallowUsers),
[0x50A04E45] = typeof(Account_PrivacyRules),
[0xB8D0AFDF] = typeof(AccountDaysTTL),
[0x12B9417B] = typeof(UpdateUserPhone),
[0x6C37C15C] = typeof(DocumentAttributeImageSize),
[0x11B58939] = typeof(DocumentAttributeAnimated),
[0x6319D612] = typeof(DocumentAttributeSticker),
[0x0EF02CE6] = typeof(DocumentAttributeVideo),
[0x9852F9C6] = typeof(DocumentAttributeAudio),
[0x15590068] = typeof(DocumentAttributeFilename),
[0xF1749A22] = typeof(Messages_StickersNotModified),
[0xE4599BBD] = typeof(Messages_Stickers),
[0x12B299D4] = typeof(StickerPack),
[0xE86602C3] = typeof(Messages_AllStickersNotModified),
[0xEDFD405F] = typeof(Messages_AllStickers),
[0x9C974FDF] = typeof(UpdateReadHistoryInbox),
[0x2F2F21BF] = typeof(UpdateReadHistoryOutbox),
[0x84D19185] = typeof(Messages_AffectedMessages),
[0x7F891213] = typeof(UpdateWebPage),
[0xEB1477E8] = typeof(WebPageEmpty),
[0xC586DA1C] = typeof(WebPagePending),
[0xE89C45B2] = typeof(WebPage),
[0xA32DD600] = typeof(MessageMediaWebPage),
[0xAD01D61D] = typeof(Authorization),
[0x1250ABDE] = typeof(Account_Authorizations),
[0xAD2641F8] = typeof(Account_Password),
[0x9A5C33E5] = typeof(Account_PasswordSettings),
[0xC23727C9] = typeof(Account_PasswordInputSettings),
[0x137948A5] = typeof(Auth_PasswordRecovery),
[0xC13D1C11] = typeof(InputMediaVenue),
[0x2EC0533F] = typeof(MessageMediaVenue),
[0xA384B779] = typeof(ReceivedNotifyMessage),
[0x69DF3769] = typeof(ChatInviteEmpty),
[0xFC2E05BC] = typeof(ChatInviteExported),
[0x5A686D7C] = typeof(ChatInviteAlready),
[0xDFC2F58E] = typeof(ChatInvite),
[0xF89CF5E8] = typeof(MessageActionChatJoinedByLink),
[0x68C13933] = typeof(UpdateReadMessagesContents),
[0xFFB62B95] = typeof(InputStickerSetEmpty),
[0x9DE7A269] = typeof(InputStickerSetID),
[0x861CC8A0] = typeof(InputStickerSetShortName),
[0xEEB46F27] = typeof(StickerSet),
[0xB60A24A6] = typeof(Messages_StickerSet),
[0x938458C1] = typeof(User),
[0xC27AC8C7] = typeof(BotCommand),
[0x98E81D3A] = typeof(BotInfo),
[0xA2FA4880] = typeof(KeyboardButton),
[0x77608B83] = typeof(KeyboardButtonRow),
[0xA03E5B85] = typeof(ReplyKeyboardHide),
[0xF4108AA0] = typeof(ReplyKeyboardForceReply),
[0x3502758C] = typeof(ReplyKeyboardMarkup),
[0x7B8E7DE6] = typeof(InputPeerUser),
[0xD8292816] = typeof(InputUser),
[0xBB92BA95] = typeof(MessageEntityUnknown),
[0xFA04579D] = typeof(MessageEntityMention),
[0x6F635B0D] = typeof(MessageEntityHashtag),
[0x6CEF8AC7] = typeof(MessageEntityBotCommand),
[0x6ED02538] = typeof(MessageEntityUrl),
[0x64E475C2] = typeof(MessageEntityEmail),
[0xBD610BC9] = typeof(MessageEntityBold),
[0x826F8B60] = typeof(MessageEntityItalic),
[0x28A20571] = typeof(MessageEntityCode),
[0x73924BE0] = typeof(MessageEntityPre),
[0x76A6D327] = typeof(MessageEntityTextUrl),
[0x11F1331C] = typeof(UpdateShortSentMessage),
[0xEE8C1E86] = typeof(InputChannelEmpty),
[0xAFEB712E] = typeof(InputChannel),
[0xBDDDE532] = typeof(PeerChannel),
[0x20ADAEF8] = typeof(InputPeerChannel),
[0xD31A961E] = typeof(Channel),
[0x289DA732] = typeof(ChannelForbidden),
[0x7F077AD9] = typeof(Contacts_ResolvedPeer),
[0xF0E6672A] = typeof(ChannelFull),
[0x0AE30253] = typeof(MessageRange),
[0x64479808] = typeof(Messages_ChannelMessages),
[0x95D2AC92] = typeof(MessageActionChannelCreate),
[0xEB0467FB] = typeof(UpdateChannelTooLong),
[0xB6D45656] = typeof(UpdateChannel),
[0x62BA04D9] = typeof(UpdateNewChannelMessage),
[0x330B5424] = typeof(UpdateReadChannelInbox),
[0xC37521C9] = typeof(UpdateDeleteChannelMessages),
[0x98A12B4B] = typeof(UpdateChannelMessageViews),
[0x3E11AFFB] = typeof(Updates_ChannelDifferenceEmpty),
[0xA4BCC6FE] = typeof(Updates_ChannelDifferenceTooLong),
[0x2064674E] = typeof(Updates_ChannelDifference),
[0x94D42EE7] = typeof(ChannelMessagesFilterEmpty),
[0xCD77D957] = typeof(ChannelMessagesFilter),
[0x15EBAC1D] = typeof(ChannelParticipant),
[0xA3289A6D] = typeof(ChannelParticipantSelf),
[0x447DCA4B] = typeof(ChannelParticipantCreator),
[0xDE3F3C79] = typeof(ChannelParticipantsRecent),
[0xB4608969] = typeof(ChannelParticipantsAdmins),
[0xA3B54985] = typeof(ChannelParticipantsKicked),
[0xF56EE2A8] = typeof(Channels_ChannelParticipants),
[0xD0D9B163] = typeof(Channels_ChannelParticipant),
[0xDA13538A] = typeof(ChatParticipantCreator),
[0xE2D6E436] = typeof(ChatParticipantAdmin),
[0xB6901959] = typeof(UpdateChatParticipantAdmin),
[0x51BDB021] = typeof(MessageActionChatMigrateTo),
[0xB055EAEE] = typeof(MessageActionChannelMigrateFrom),
[0xB0D1865B] = typeof(ChannelParticipantsBots),
[0x780A0310] = typeof(Help_TermsOfService),
[0x688A30AA] = typeof(UpdateNewStickerSet),
[0x0BB2D201] = typeof(UpdateStickerSetsOrder),
[0x43AE3DEC] = typeof(UpdateStickerSets),
[0xE8025CA2] = typeof(Messages_SavedGifsNotModified),
[0x2E0709A5] = typeof(Messages_SavedGifs),
[0x9375341E] = typeof(UpdateSavedGifs),
[0x3380C786] = typeof(InputBotInlineMessageMediaAuto),
[0x3DCD7A87] = typeof(InputBotInlineMessageText),
[0x88BF9319] = typeof(InputBotInlineResult),
[0x764CF810] = typeof(BotInlineMessageMediaAuto),
[0x8C7F65E2] = typeof(BotInlineMessageText),
[0x11965F3A] = typeof(BotInlineResult),
[0x947CA848] = typeof(Messages_BotResults),
[0x54826690] = typeof(UpdateBotInlineQuery),
[0x0E48F964] = typeof(UpdateBotInlineSend),
[0x50F5C392] = typeof(InputMessagesFilterVoice),
[0x3751B49E] = typeof(InputMessagesFilterMusic),
[0xBDFB0426] = typeof(InputPrivacyKeyChatInvite),
[0x500E6DFA] = typeof(PrivacyKeyChatInvite),
[0x5DAB1AF4] = typeof(ExportedMessageLink),
[0x5F777DCE] = typeof(MessageFwdHeader),
[0x1B3F4DF7] = typeof(UpdateEditChannelMessage),
[0x94BD38ED] = typeof(MessageActionPinMessage),
[0x72A3158C] = typeof(Auth_CodeTypeSms),
[0x741CD3E3] = typeof(Auth_CodeTypeCall),
[0x226CCEFB] = typeof(Auth_CodeTypeFlashCall),
[0x3DBB5986] = typeof(Auth_SentCodeTypeApp),
[0xC000BBA2] = typeof(Auth_SentCodeTypeSms),
[0x5353E5A7] = typeof(Auth_SentCodeTypeCall),
[0xAB03C6D9] = typeof(Auth_SentCodeTypeFlashCall),
[0x258AFF05] = typeof(KeyboardButtonUrl),
[0x35BBDB6B] = typeof(KeyboardButtonCallback),
[0xB16A6C29] = typeof(KeyboardButtonRequestPhone),
[0xFC796B3F] = typeof(KeyboardButtonRequestGeoLocation),
[0x0568A748] = typeof(KeyboardButtonSwitchInline),
[0x48A30254] = typeof(ReplyInlineMarkup),
[0x36585EA4] = typeof(Messages_BotCallbackAnswer),
[0xE73547E1] = typeof(UpdateBotCallbackQuery),
[0x26B5DDE6] = typeof(Messages_MessageEditData),
[0xE40370A3] = typeof(UpdateEditMessage),
[0x96929A85] = typeof(InputBotInlineMessageMediaGeo),
[0x417BBF11] = typeof(InputBotInlineMessageMediaVenue),
[0xA6EDBFFD] = typeof(InputBotInlineMessageMediaContact),
[0x051846FD] = typeof(BotInlineMessageMediaGeo),
[0x8A86659C] = typeof(BotInlineMessageMediaVenue),
[0x18D1CDC2] = typeof(BotInlineMessageMediaContact),
[0xA8D864A7] = typeof(InputBotInlineResultPhoto),
[0xFFF8FDC4] = typeof(InputBotInlineResultDocument),
[0x17DB940B] = typeof(BotInlineMediaResult),
[0x890C3D89] = typeof(InputBotInlineMessageID),
[0xF9D27A5A] = typeof(UpdateInlineBotCallbackQuery),
[0x3C20629F] = typeof(InlineBotSwitchPM),
[0x3371C354] = typeof(Messages_PeerDialogs),
[0xEDCDC05B] = typeof(TopPeer),
[0xAB661B5B] = typeof(TopPeerCategoryBotsPM),
[0x148677E2] = typeof(TopPeerCategoryBotsInline),
[0x0637B7ED] = typeof(TopPeerCategoryCorrespondents),
[0xBD17A14A] = typeof(TopPeerCategoryGroups),
[0x161D9628] = typeof(TopPeerCategoryChannels),
[0xFB834291] = typeof(TopPeerCategoryPeers),
[0xDE266EF5] = typeof(Contacts_TopPeersNotModified),
[0x70B772A8] = typeof(Contacts_TopPeers),
[0x352DCA58] = typeof(MessageEntityMentionName),
[0x208E68C9] = typeof(InputMessageEntityMentionName),
[0x3A20ECB8] = typeof(InputMessagesFilterChatPhotos),
[0x25D6C9C7] = typeof(UpdateReadChannelOutbox),
[0xEE2BB969] = typeof(UpdateDraftMessage),
[0x1B0C841A] = typeof(DraftMessageEmpty),
[0xFD8E711F] = typeof(DraftMessage),
[0x9FBAB604] = typeof(MessageActionHistoryClear),
[0xC6DC0C66] = typeof(Messages_FeaturedStickersNotModified),
[0xB6ABC341] = typeof(Messages_FeaturedStickers),
[0x571D2742] = typeof(UpdateReadFeaturedStickers),
[0x0B17F890] = typeof(Messages_RecentStickersNotModified),
[0x22F3AFB3] = typeof(Messages_RecentStickers),
[0x9A422C20] = typeof(UpdateRecentStickers),
[0x4FCBA9C8] = typeof(Messages_ArchivedStickers),
[0x38641628] = typeof(Messages_StickerSetInstallResultSuccess),
[0x35E410A8] = typeof(Messages_StickerSetInstallResultArchive),
[0x6410A5D2] = typeof(StickerSetCovered),
[0xA229DD06] = typeof(UpdateConfig),
[0x3354678F] = typeof(UpdatePtsChanged),
[0xE5BBFE1A] = typeof(InputMediaPhotoExternal),
[0xFB52DC99] = typeof(InputMediaDocumentExternal),
[0x3407E51B] = typeof(StickerSetMultiCovered),
[0xAED6DBB2] = typeof(MaskCoords),
[0x9801D2F7] = typeof(DocumentAttributeHasStickers),
[0x4A992157] = typeof(InputStickeredMediaPhoto),
[0x0438865B] = typeof(InputStickeredMediaDocument),
[0xBDF9653B] = typeof(Game),
[0x4FA417F2] = typeof(InputBotInlineResultGame),
[0x4B425864] = typeof(InputBotInlineMessageGame),
[0xFDB19008] = typeof(MessageMediaGame),
[0xD33F43F3] = typeof(InputMediaGame),
[0x032C3E77] = typeof(InputGameID),
[0xC331E80A] = typeof(InputGameShortName),
[0x50F41CCF] = typeof(KeyboardButtonGame),
[0x92A72876] = typeof(MessageActionGameScore),
[0x58FFFCD0] = typeof(HighScore),
[0x9A3BFD99] = typeof(Messages_HighScores),
[0x4AFE8F6D] = typeof(Updates_DifferenceTooLong),
[0x40771900] = typeof(UpdateChannelWebPage),
[0x9CD81144] = typeof(Messages_ChatsSlice),
[0xDC3D824F] = typeof(TextEmpty),
[0x744694E0] = typeof(TextPlain),
[0x6724ABC4] = typeof(TextBold),
[0xD912A59C] = typeof(TextItalic),
[0xC12622C4] = typeof(TextUnderline),
[0x9BF8BB95] = typeof(TextStrike),
[0x6C3F19B9] = typeof(TextFixed),
[0x3C2884C1] = typeof(TextUrl),
[0xDE5A0DD6] = typeof(TextEmail),
[0x7E6260D7] = typeof(TextConcat),
[0x13567E8A] = typeof(PageBlockUnsupported),
[0x70ABC3FD] = typeof(PageBlockTitle),
[0x8FFA9A1F] = typeof(PageBlockSubtitle),
[0xBAAFE5E0] = typeof(PageBlockAuthorDate),
[0xBFD064EC] = typeof(PageBlockHeader),
[0xF12BB6E1] = typeof(PageBlockSubheader),
[0x467A0766] = typeof(PageBlockParagraph),
[0xC070D93E] = typeof(PageBlockPreformatted),
[0x48870999] = typeof(PageBlockFooter),
[0xDB20B188] = typeof(PageBlockDivider),
[0xCE0D37B0] = typeof(PageBlockAnchor),
[0xE4E88011] = typeof(PageBlockList),
[0x263D7C26] = typeof(PageBlockBlockquote),
[0x4F4456D3] = typeof(PageBlockPullquote),
[0x1759C560] = typeof(PageBlockPhoto),
[0x7C8FE7B6] = typeof(PageBlockVideo),
[0x39F23300] = typeof(PageBlockCover),
[0xA8718DC5] = typeof(PageBlockEmbed),
[0xF259A80B] = typeof(PageBlockEmbedPost),
[0x65A0FA4D] = typeof(PageBlockCollage),
[0x031F9590] = typeof(PageBlockSlideshow),
[0x7311CA11] = typeof(WebPageNotModified),
[0xFABADC5F] = typeof(InputPrivacyKeyPhoneCall),
[0x3D662B7B] = typeof(PrivacyKeyPhoneCall),
[0xDD6A8F48] = typeof(SendMessageGamePlayAction),
[0x85E42301] = typeof(PhoneCallDiscardReasonMissed),
[0xE095C1A0] = typeof(PhoneCallDiscardReasonDisconnect),
[0x57ADC690] = typeof(PhoneCallDiscardReasonHangup),
[0xFAF7E8C9] = typeof(PhoneCallDiscardReasonBusy),
[0x6E6FE51C] = typeof(UpdateDialogPinned),
[0xFA0F3CA2] = typeof(UpdatePinnedDialogs),
[0x7D748D04] = typeof(DataJSON),
[0x8317C0C3] = typeof(UpdateBotWebhookJSON),
[0x9B9240A6] = typeof(UpdateBotWebhookJSONQuery),
[0xCB296BF8] = typeof(LabeledPrice),
[0xC30AA358] = typeof(Invoice),
[0xF4E096C3] = typeof(InputMediaInvoice),
[0xEA02C27E] = typeof(PaymentCharge),
[0x8F31B327] = typeof(MessageActionPaymentSentMe),
[0x84551347] = typeof(MessageMediaInvoice),
[0x1E8CAAEB] = typeof(PostAddress),
[0x909C3F94] = typeof(PaymentRequestedInfo),
[0xAFD93FBB] = typeof(KeyboardButtonBuy),
[0x40699CD0] = typeof(MessageActionPaymentSent),
[0xCDC27A1F] = typeof(PaymentSavedCredentialsCard),
[0x1C570ED1] = typeof(WebDocument),
[0x9BED434D] = typeof(InputWebDocument),
[0xC239D686] = typeof(InputWebFileLocation),
[0x21E753BC] = typeof(Upload_WebFile),
[0x3F56AEA3] = typeof(Payments_PaymentForm),
[0xD1451883] = typeof(Payments_ValidatedRequestedInfo),
[0x4E5F810D] = typeof(Payments_PaymentResult),
[0x500911E1] = typeof(Payments_PaymentReceipt),
[0xFB8FE43C] = typeof(Payments_SavedInfo),
[0xC10EB2CF] = typeof(InputPaymentCredentialsSaved),
[0x3417D728] = typeof(InputPaymentCredentials),
[0xDB64FD34] = typeof(Account_TmpPassword),
[0xB6213CDF] = typeof(ShippingOption),
[0xE0CDC940] = typeof(UpdateBotShippingQuery),
[0x5D2F3AA9] = typeof(UpdateBotPrecheckoutQuery),
[0xFFA0A496] = typeof(InputStickerSetItem),
[0xAB0F6B1E] = typeof(UpdatePhoneCall),
[0x1E36FDED] = typeof(InputPhoneCall),
[0x5366C915] = typeof(PhoneCallEmpty),
[0x1B8F4AD1] = typeof(PhoneCallWaiting),
[0x87EABB53] = typeof(PhoneCallRequested),
[0x997C454A] = typeof(PhoneCallAccepted),
[0x8742AE7F] = typeof(PhoneCall),
[0x50CA4DE1] = typeof(PhoneCallDiscarded),
[0x9D4C17C0] = typeof(PhoneConnection),
[0xFC878FC8] = typeof(PhoneCallProtocol),
[0xEC82E140] = typeof(Phone_PhoneCall),
[0x80C99768] = typeof(InputMessagesFilterPhoneCalls),
[0x80E11A7F] = typeof(MessageActionPhoneCall),
[0x7A7C17A4] = typeof(InputMessagesFilterRoundVoice),
[0xB549DA53] = typeof(InputMessagesFilterRoundVideo),
[0x88F27FBC] = typeof(SendMessageRecordRoundAction),
[0x243E1C66] = typeof(SendMessageUploadRoundAction),
[0xF18CDA44] = typeof(Upload_FileCdnRedirect),
[0xEEA8E46E] = typeof(Upload_CdnFileReuploadNeeded),
[0xA99FCA4F] = typeof(Upload_CdnFile),
[0xC982EABA] = typeof(CdnPublicKey),
[0x5725E40A] = typeof(CdnConfig),
[0xEF1751B5] = typeof(PageBlockChannel),
[0xCAD181F6] = typeof(LangPackString),
[0x6C47AC9F] = typeof(LangPackStringPluralized),
[0x2979EEB2] = typeof(LangPackStringDeleted),
[0xF385C1F6] = typeof(LangPackDifference),
[0xEECA5CE3] = typeof(LangPackLanguage),
[0x46560264] = typeof(UpdateLangPackTooLong),
[0x56022F4D] = typeof(UpdateLangPack),
[0xCCBEBBAF] = typeof(ChannelParticipantAdmin),
[0x1C0FACAF] = typeof(ChannelParticipantBanned),
[0x1427A5E1] = typeof(ChannelParticipantsBanned),
[0x0656AC4B] = typeof(ChannelParticipantsSearch),
[0xE6DFB825] = typeof(ChannelAdminLogEventActionChangeTitle),
[0x55188A2E] = typeof(ChannelAdminLogEventActionChangeAbout),
[0x6A4AFC38] = typeof(ChannelAdminLogEventActionChangeUsername),
[0x434BD2AF] = typeof(ChannelAdminLogEventActionChangePhoto),
[0x1B7907AE] = typeof(ChannelAdminLogEventActionToggleInvites),
[0x26AE0971] = typeof(ChannelAdminLogEventActionToggleSignatures),
[0xE9E82C18] = typeof(ChannelAdminLogEventActionUpdatePinned),
[0x709B2405] = typeof(ChannelAdminLogEventActionEditMessage),
[0x42E047BB] = typeof(ChannelAdminLogEventActionDeleteMessage),
[0x183040D3] = typeof(ChannelAdminLogEventActionParticipantJoin),
[0xF89777F2] = typeof(ChannelAdminLogEventActionParticipantLeave),
[0xE31C34D8] = typeof(ChannelAdminLogEventActionParticipantInvite),
[0xE6D83D7E] = typeof(ChannelAdminLogEventActionParticipantToggleBan),
[0xD5676710] = typeof(ChannelAdminLogEventActionParticipantToggleAdmin),
[0x3B5A3E40] = typeof(ChannelAdminLogEvent),
[0xED8AF74D] = typeof(Channels_AdminLogResults),
[0xEA107AE4] = typeof(ChannelAdminLogEventsFilter),
[0x1E76A78C] = typeof(TopPeerCategoryPhoneCalls),
[0x804361EA] = typeof(PageBlockAudio),
[0x5CE14175] = typeof(PopularContact),
[0x4792929B] = typeof(MessageActionScreenshotTaken),
[0x9E8FA6D3] = typeof(Messages_FavedStickersNotModified),
[0xF37F2F16] = typeof(Messages_FavedStickers),
[0xE511996D] = typeof(UpdateFavedStickers),
[0x89893B45] = typeof(UpdateChannelReadMessagesContents),
[0xC1F8E69A] = typeof(InputMessagesFilterMyMentions),
[0x7084A7BE] = typeof(UpdateContactsReset),
[0xB1C3CAA7] = typeof(ChannelAdminLogEventActionChangeStickerSet),
[0xFAE69F56] = typeof(MessageActionCustomAction),
[0x0AA1C39F] = typeof(InputPaymentCredentialsApplePay),
[0xCA05D50E] = typeof(InputPaymentCredentialsAndroidPay),
[0xE7026D0D] = typeof(InputMessagesFilterGeo),
[0xE062DB83] = typeof(InputMessagesFilterContacts),
[0x70DB6837] = typeof(UpdateChannelAvailableMessages),
[0x5F5C95F1] = typeof(ChannelAdminLogEventActionTogglePreHistoryHidden),
[0x971FA843] = typeof(InputMediaGeoLive),
[0xB940C666] = typeof(MessageMediaGeoLive),
[0x46E1D13D] = typeof(RecentMeUrlUnknown),
[0x8DBC3336] = typeof(RecentMeUrlUser),
[0xA01B22F9] = typeof(RecentMeUrlChat),
[0xEB49081D] = typeof(RecentMeUrlChatInvite),
[0xBC0A57DC] = typeof(RecentMeUrlStickerSet),
[0x0E0310D7] = typeof(Help_RecentMeUrls),
[0xF0173FE9] = typeof(Channels_ChannelParticipantsNotModified),
[0x74535F21] = typeof(Messages_MessagesNotModified),
[0x1CC6E91F] = typeof(InputSingleMedia),
[0xCAC943F2] = typeof(WebAuthorization),
[0xED56C9FC] = typeof(Account_WebAuthorizations),
[0xA676A322] = typeof(InputMessageID),
[0xBAD88395] = typeof(InputMessageReplyTo),
[0x86872538] = typeof(InputMessagePinned),
[0x9B69E34B] = typeof(MessageEntityPhone),
[0x4C4E743F] = typeof(MessageEntityCashtag),
[0xABE9AFFE] = typeof(MessageActionBotAllowed),
[0xFCAAFEB7] = typeof(InputDialogPeer),
[0xE56DBF05] = typeof(DialogPeer),
[0x0D54B65D] = typeof(Messages_FoundStickerSetsNotModified),
[0x5108D648] = typeof(Messages_FoundStickerSets),
[0x6242C773] = typeof(FileHash),
[0xF9C8BCC6] = typeof(WebDocumentNoProxy),
[0x75588B3F] = typeof(InputClientProxy),
[0xE3309F7F] = typeof(Help_TermsOfServiceUpdateEmpty),
[0x28ECF961] = typeof(Help_TermsOfServiceUpdate),
[0x3334B0F0] = typeof(InputSecureFileUploaded),
[0x5367E5BE] = typeof(InputSecureFile),
[0xCBC7EE28] = typeof(InputSecureFileLocation),
[0x64199744] = typeof(SecureFileEmpty),
[0xE0277A62] = typeof(SecureFile),
[0x8AEABEC3] = typeof(SecureData),
[0x7D6099DD] = typeof(SecurePlainPhone),
[0x21EC5A5F] = typeof(SecurePlainEmail),
[0x9D2A81E3] = typeof(SecureValueTypePersonalDetails),
[0x3DAC6A00] = typeof(SecureValueTypePassport),
[0x06E425C4] = typeof(SecureValueTypeDriverLicense),
[0xA0D0744B] = typeof(SecureValueTypeIdentityCard),
[0x99A48F23] = typeof(SecureValueTypeInternalPassport),
[0xCBE31E26] = typeof(SecureValueTypeAddress),
[0xFC36954E] = typeof(SecureValueTypeUtilityBill),
[0x89137C0D] = typeof(SecureValueTypeBankStatement),
[0x8B883488] = typeof(SecureValueTypeRentalAgreement),
[0x99E3806A] = typeof(SecureValueTypePassportRegistration),
[0xEA02EC33] = typeof(SecureValueTypeTemporaryRegistration),
[0xB320AADB] = typeof(SecureValueTypePhone),
[0x8E3CA7EE] = typeof(SecureValueTypeEmail),
[0x187FA0CA] = typeof(SecureValue),
[0xDB21D0A7] = typeof(InputSecureValue),
[0xED1ECDB0] = typeof(SecureValueHash),
[0xE8A40BD9] = typeof(SecureValueErrorData),
[0x00BE3DFA] = typeof(SecureValueErrorFrontSide),
[0x868A2AA5] = typeof(SecureValueErrorReverseSide),
[0xE537CED6] = typeof(SecureValueErrorSelfie),
[0x7A700873] = typeof(SecureValueErrorFile),
[0x666220E9] = typeof(SecureValueErrorFiles),
[0x33F0EA47] = typeof(SecureCredentialsEncrypted),
[0xAD2E1CD8] = typeof(Account_AuthorizationForm),
[0x811F854F] = typeof(Account_SentEmailCode),
[0x1B287353] = typeof(MessageActionSecureValuesSentMe),
[0xD95C6154] = typeof(MessageActionSecureValuesSent),
[0x66AFA166] = typeof(Help_DeepLinkInfoEmpty),
[0x6A4EE832] = typeof(Help_DeepLinkInfo),
[0x1142BD56] = typeof(SavedPhoneContact),
[0x4DBA4501] = typeof(Account_Takeout),
[0x29BE5899] = typeof(InputTakeoutFileLocation),
[0xE16459C3] = typeof(UpdateDialogUnreadMark),
[0xF0E3E596] = typeof(Messages_DialogsNotModified),
[0x9F2221C9] = typeof(InputWebFileGeoPointLocation),
[0xB52C939D] = typeof(Contacts_TopPeersDisabled),
[0x9B89F93A] = typeof(InputReportReasonCopyright),
[0xD45AB096] = typeof(PasswordKdfAlgoUnknown),
[0x004A8537] = typeof(SecurePasswordKdfAlgoUnknown),
[0xBBF2DDA0] = typeof(SecurePasswordKdfAlgoPBKDF2HMACSHA512iter100000),
[0x86471D92] = typeof(SecurePasswordKdfAlgoSHA512),
[0x1527BCAC] = typeof(SecureSecretSettings),
[0x3A912D4A] = typeof(PasswordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow),
[0x9880F658] = typeof(InputCheckPasswordEmpty),
[0xD27FF082] = typeof(InputCheckPasswordSRP),
[0x869D758F] = typeof(SecureValueError),
[0xA1144770] = typeof(SecureValueErrorTranslationFile),
[0x34636DD8] = typeof(SecureValueErrorTranslationFiles),
[0x829D99DA] = typeof(SecureRequiredType),
[0x027477B4] = typeof(SecureRequiredTypeOneOf),
[0xBFB9F457] = typeof(Help_PassportConfigNotModified),
[0xA098D6AF] = typeof(Help_PassportConfig),
[0x1D1B1245] = typeof(InputAppEvent),
[0xC0DE1BD9] = typeof(JsonObjectValue),
[0x3F6D7B68] = typeof(JsonNull),
[0xC7345E6A] = typeof(JsonBool),
[0x2BE0DFA4] = typeof(JsonNumber),
[0xB71E767A] = typeof(JsonString),
[0xF7444763] = typeof(JsonArray),
[0x99C1D49D] = typeof(JsonObject),
[0xB1DB7C7E] = typeof(InputNotifyBroadcasts),
[0xD612E8EF] = typeof(NotifyBroadcasts),
[0xED6A8504] = typeof(TextSubscript),
[0xC7FB5E01] = typeof(TextSuperscript),
[0x034B8621] = typeof(TextMarked),
[0x1CCB966A] = typeof(TextPhone),
[0x081CCF4F] = typeof(TextImage),
[0x1E148390] = typeof(PageBlockKicker),
[0x34566B6A] = typeof(PageTableCell),
[0xE0C0C5E5] = typeof(PageTableRow),
[0xBF4DEA82] = typeof(PageBlockTable),
[0x6F747657] = typeof(PageCaption),
[0xB92FB6CD] = typeof(PageListItemText),
[0x25E073FC] = typeof(PageListItemBlocks),
[0x5E068047] = typeof(PageListOrderedItemText),
[0x98DD8936] = typeof(PageListOrderedItemBlocks),
[0x9A8AE1E1] = typeof(PageBlockOrderedList),
[0x76768BED] = typeof(PageBlockDetails),
[0xB390DC08] = typeof(PageRelatedArticle),
[0x16115A96] = typeof(PageBlockRelatedArticles),
[0xA44F3EF6] = typeof(PageBlockMap),
[0x98657F0D] = typeof(Page),
[0xDB9E70D2] = typeof(InputPrivacyKeyPhoneP2P),
[0x39491CC8] = typeof(PrivacyKeyPhoneP2P),
[0x35553762] = typeof(TextAnchor),
[0x8C05F1C9] = typeof(Help_SupportName),
[0xF3AE2EED] = typeof(Help_UserInfoEmpty),
[0x01EB3758] = typeof(Help_UserInfo),
[0xF3F25F76] = typeof(MessageActionContactSignUp),
[0xACA1657B] = typeof(UpdateMessagePoll),
[0x6CA9C2E9] = typeof(PollAnswer),
[0x86E18161] = typeof(Poll),
[0x3B6DDAD2] = typeof(PollAnswerVoters),
[0xBADCC1A3] = typeof(PollResults),
[0x0F94E5F1] = typeof(InputMediaPoll),
[0x4BD6E798] = typeof(MessageMediaPoll),
[0xF041E250] = typeof(ChatOnlines),
[0x47A971E0] = typeof(StatsURL),
[0xE0B0BC2E] = typeof(PhotoStrippedSize),
[0x5FB224D5] = typeof(ChatAdminRights),
[0x9F120418] = typeof(ChatBannedRights),
[0x54C01850] = typeof(UpdateChatDefaultBannedRights),
[0xE630B979] = typeof(InputWallPaper),
[0x72091C80] = typeof(InputWallPaperSlug),
[0xBB6AE88D] = typeof(ChannelParticipantsContacts),
[0x2DF5FC0A] = typeof(ChannelAdminLogEventActionDefaultBannedRights),
[0x8F079643] = typeof(ChannelAdminLogEventActionStopPoll),
[0x1C199183] = typeof(Account_WallPapersNotModified),
[0x702B65A9] = typeof(Account_WallPapers),
[0xDEBEBE83] = typeof(CodeSettings),
[0x05086CF8] = typeof(WallPaperSettings),
[0xE04232F3] = typeof(AutoDownloadSettings),
[0x63CACF26] = typeof(Account_AutoDownloadSettings),
[0xD5B3B9F9] = typeof(EmojiKeyword),
[0x236DF622] = typeof(EmojiKeywordDeleted),
[0x5CC761BD] = typeof(EmojiKeywordsDifference),
[0xA575739D] = typeof(EmojiURL),
[0xB3FB5361] = typeof(EmojiLanguage),
[0xA4DD4C08] = typeof(InputPrivacyKeyForwards),
[0x69EC56A3] = typeof(PrivacyKeyForwards),
[0x5719BACC] = typeof(InputPrivacyKeyProfilePhoto),
[0x96151FED] = typeof(PrivacyKeyProfilePhoto),
[0xBC7FC6CD] = typeof(FileLocationToBeDeprecated),
[0x40181FFE] = typeof(InputPhotoFileLocation),
[0xD83466F3] = typeof(InputPhotoLegacyFileLocation),
[0x27D69997] = typeof(InputPeerPhotoFileLocation),
[0x0DBAEAE9] = typeof(InputStickerSetThumb),
[0xFF544E65] = typeof(Folder),
[0x71BD134C] = typeof(DialogFolder),
[0x64600527] = typeof(InputDialogPeerFolder),
[0x514519E2] = typeof(DialogPeerFolder),
[0xFBD2C296] = typeof(InputFolderPeer),
[0xE9BAA668] = typeof(FolderPeer),
[0x19360DC0] = typeof(UpdateFolderPeers),
[0x2D117597] = typeof(InputUserFromMessage),
[0x2A286531] = typeof(InputChannelFromMessage),
[0x17BAE2E6] = typeof(InputPeerUserFromMessage),
[0x9C95F7BB] = typeof(InputPeerChannelFromMessage),
[0x0352DAFA] = typeof(InputPrivacyKeyPhoneNumber),
[0xD19AE46D] = typeof(PrivacyKeyPhoneNumber),
[0xA8406CA9] = typeof(TopPeerCategoryForwardUsers),
[0xFBEEC0F0] = typeof(TopPeerCategoryForwardChats),
[0xA26F881B] = typeof(ChannelAdminLogEventActionChangeLinkedChat),
[0xE844EBFF] = typeof(Messages_SearchCounter),
[0x10B78D29] = typeof(KeyboardButtonUrlAuth),
[0xD02E7FD4] = typeof(InputKeyboardButtonUrlAuth),
[0x92D33A0E] = typeof(UrlAuthResultRequest),
[0x8F8C0E4E] = typeof(UrlAuthResultAccepted),
[0xA9D6DB1F] = typeof(UrlAuthResultDefault),
[0x4C81C1BA] = typeof(InputPrivacyValueAllowChatParticipants),
[0xD82363AF] = typeof(InputPrivacyValueDisallowChatParticipants),
[0x18BE796B] = typeof(PrivacyValueAllowChatParticipants),
[0xACAE0690] = typeof(PrivacyValueDisallowChatParticipants),
[0x9C4E7E8B] = typeof(MessageEntityUnderline),
[0xBF0693D4] = typeof(MessageEntityStrike),
[0x020DF5D0] = typeof(MessageEntityBlockquote),
[0x6A7E7366] = typeof(UpdatePeerSettings),
[0xBFB5AD8B] = typeof(ChannelLocationEmpty),
[0x209B82DB] = typeof(ChannelLocation),
[0xCA461B5D] = typeof(PeerLocated),
[0xB4AFCFB0] = typeof(UpdatePeerLocated),
[0x0E6B76AE] = typeof(ChannelAdminLogEventActionChangeLocation),
[0xDBD4FEED] = typeof(InputReportReasonGeoIrrelevant),
[0x53909779] = typeof(ChannelAdminLogEventActionToggleSlowMode),
[0x44747E9A] = typeof(Auth_AuthorizationSignUpRequired),
[0xD8411139] = typeof(Payments_PaymentVerificationNeeded),
[0x028703C8] = typeof(InputStickerSetAnimatedEmoji),
[0x39A51DFB] = typeof(UpdateNewScheduledMessage),
[0x90866CEE] = typeof(UpdateDeleteScheduledMessages),
[0xD072ACB4] = typeof(RestrictionReason),
[0x3C5693E9] = typeof(InputTheme),
[0xF5890DF1] = typeof(InputThemeSlug),
[0x028F1114] = typeof(Theme),
[0xF41EB622] = typeof(Account_ThemesNotModified),
[0x7F676421] = typeof(Account_Themes),
[0x8216FBA3] = typeof(UpdateTheme),
[0xD1219BDD] = typeof(InputPrivacyKeyAddedByPhone),
[0x42FFD42B] = typeof(PrivacyKeyAddedByPhone),
[0x871FB939] = typeof(UpdateGeoLiveViewed),
[0x564FE691] = typeof(UpdateLoginToken),
[0x629F1980] = typeof(Auth_LoginToken),
[0x068E9916] = typeof(Auth_LoginTokenMigrateTo),
[0x390D5C5E] = typeof(Auth_LoginTokenSuccess),
[0x57E28221] = typeof(Account_ContentSettings),
[0xA927FEC5] = typeof(Messages_InactiveChats),
[0xC3A12462] = typeof(BaseThemeClassic),
[0xFBD81688] = typeof(BaseThemeDay),
[0xB7B31EA8] = typeof(BaseThemeNight),
[0x6D5F77EE] = typeof(BaseThemeTinted),
[0x5B11125A] = typeof(BaseThemeArctic),
[0x8427BBAC] = typeof(InputWallPaperNoFile),
[0x8AF40B25] = typeof(WallPaperNoFile),
[0xBD507CD1] = typeof(InputThemeSettings),
[0x9C14984A] = typeof(ThemeSettings),
[0x54B56617] = typeof(WebPageAttributeTheme),
[0x42F88F2C] = typeof(UpdateMessagePollVote),
[0xA28E5559] = typeof(MessageUserVote),
[0x36377430] = typeof(MessageUserVoteInputOption),
[0x0E8FE0DE] = typeof(MessageUserVoteMultiple),
[0x0823F649] = typeof(Messages_VotesList),
[0xBBC7515D] = typeof(KeyboardButtonRequestPoll),
[0x761E6AF4] = typeof(MessageEntityBankCard),
[0xF568028A] = typeof(BankCardOpenUrl),
[0x3E24E573] = typeof(Payments_BankCardData),
[0xF8EC284B] = typeof(PeerSelfLocated),
[0x7438F7E8] = typeof(DialogFilter),
[0x77744D4A] = typeof(DialogFilterSuggested),
[0x26FFDE7D] = typeof(UpdateDialogFilter),
[0xA5D72105] = typeof(UpdateDialogFilterOrder),
[0x3504914F] = typeof(UpdateDialogFilters),
[0xB637EDAF] = typeof(StatsDateRangeDays),
[0xCB43ACDE] = typeof(StatsAbsValueAndPrev),
[0xCBCE2FE0] = typeof(StatsPercentValue),
[0x4A27EB2D] = typeof(StatsGraphAsync),
[0xBEDC9822] = typeof(StatsGraphError),
[0x8EA464B6] = typeof(StatsGraph),
[0xAD4FC9BD] = typeof(MessageInteractionCounters),
[0xBDF78394] = typeof(Stats_BroadcastStats),
[0xE66FBF7B] = typeof(InputMediaDice),
[0x3F7EE58B] = typeof(MessageMediaDice),
[0xE67F520E] = typeof(InputStickerSetDice),
[0x98F6AC75] = typeof(Help_PromoDataEmpty),
[0x8C39793F] = typeof(Help_PromoData),
[0xE831C556] = typeof(VideoSize),
[0x2661BF09] = typeof(UpdatePhoneCallSignalingData),
[0x61695CB0] = typeof(ChatInvitePeek),
[0x18F3D0F7] = typeof(StatsGroupTopPoster),
[0x6014F412] = typeof(StatsGroupTopAdmin),
[0x31962A4C] = typeof(StatsGroupTopInviter),
[0xEF7FF916] = typeof(Stats_MegagroupStats),
[0xBEA2F424] = typeof(GlobalPrivacySettings),
[0x635FE375] = typeof(PhoneConnectionWebrtc),
[0x4203C5EF] = typeof(Help_CountryCode),
[0xC3878E23] = typeof(Help_Country),
[0x93CC1F32] = typeof(Help_CountriesListNotModified),
[0x87D0759E] = typeof(Help_CountriesList),
[0x455B853D] = typeof(MessageViews),
[0x6E8A84DF] = typeof(UpdateChannelMessageForwards),
[0x5AA86A51] = typeof(PhotoSizeProgressive),
[0xB6C4F543] = typeof(Messages_MessageViews),
[0x1CC7DE54] = typeof(UpdateReadChannelDiscussionInbox),
[0x4638A26C] = typeof(UpdateReadChannelDiscussionOutbox),
[0xF5DD8F9D] = typeof(Messages_DiscussionMessage),
[0xA6D57763] = typeof(MessageReplyHeader),
[0x4128FAAC] = typeof(MessageReplies),
[0x246A4B22] = typeof(UpdatePeerBlocked),
[0xE8FD8014] = typeof(PeerBlocked),
[0xFF2ABE9F] = typeof(UpdateChannelUserTyping),
[0xACFA1A7E] = typeof(InputMessageCallbackQuery),
[0xC3C6796B] = typeof(ChannelParticipantLeft),
[0xE04B5CEB] = typeof(ChannelParticipantsMentions),
[0xED85EAB5] = typeof(UpdatePinnedMessages),
[0x8588878B] = typeof(UpdatePinnedChannelMessages),
[0x1BB00451] = typeof(InputMessagesFilterPinned),
[0x8999F295] = typeof(Stats_MessageStats),
[0x98E0D697] = typeof(MessageActionGeoProximityReached),
[0xD8214D41] = typeof(PhotoPathSize),
// from TL.Secret:
[0x1F814F1F] = typeof(Layer8.DecryptedMessage),
[0xAA48327D] = typeof(Layer8.DecryptedMessageService),
[0x089F5C4A] = typeof(Layer8.DecryptedMessageMediaEmpty),
[0x32798A8C] = typeof(Layer8.DecryptedMessageMediaPhoto),
[0x4CEE6EF3] = typeof(Layer8.DecryptedMessageMediaVideo),
[0x35480A59] = typeof(Layer8.DecryptedMessageMediaGeoPoint),
[0x588A0A97] = typeof(Layer8.DecryptedMessageMediaContact),
[0xA1733AEC] = typeof(Layer8.DecryptedMessageActionSetMessageTTL),
[0xB095434B] = typeof(Layer8.DecryptedMessageMediaDocument),
[0x6080758F] = typeof(Layer8.DecryptedMessageMediaAudio),
[0x0C4F40BE] = typeof(Layer8.DecryptedMessageActionReadMessages),
[0x65614304] = typeof(Layer8.DecryptedMessageActionDeleteMessages),
[0x8AC1F475] = typeof(Layer8.DecryptedMessageActionScreenshotMessages),
[0x6719E45C] = typeof(Layer8.DecryptedMessageActionFlushHistory),
[0x204D3878] = typeof(Layer17.DecryptedMessage),
[0x73164160] = typeof(Layer17.DecryptedMessageService),
[0x524A415D] = typeof(Layer17.DecryptedMessageMediaVideo),
[0x57E0A9CB] = typeof(Layer17.DecryptedMessageMediaAudio),
[0x1BE31789] = typeof(Layer17.DecryptedMessageLayer),
[0x92042FF7] = typeof(Layer17.SendMessageUploadVideoAction),
[0xE6AC8A6F] = typeof(Layer17.SendMessageUploadAudioAction),
[0x990A3C1A] = typeof(Layer17.SendMessageUploadPhotoAction),
[0x8FAEE98E] = typeof(Layer17.SendMessageUploadDocumentAction),
[0x511110B0] = typeof(Layer17.DecryptedMessageActionResend),
[0xF3048883] = typeof(Layer17.DecryptedMessageActionNotifyLayer),
[0xCCB27641] = typeof(Layer17.DecryptedMessageActionTyping),
[0xF3C9611B] = typeof(Layer20.DecryptedMessageActionRequestKey),
[0x6FE1735B] = typeof(Layer20.DecryptedMessageActionAcceptKey),
[0xDD05EC6B] = typeof(Layer20.DecryptedMessageActionAbortKey),
[0xEC2E0B9B] = typeof(Layer20.DecryptedMessageActionCommitKey),
[0xA82FDD63] = typeof(Layer20.DecryptedMessageActionNoop),
[0xFB0A5727] = typeof(Layer23.DocumentAttributeSticker),
[0x5910CCCB] = typeof(Layer23.DocumentAttributeVideo),
[0x051448E5] = typeof(Layer23.DocumentAttributeAudio),
[0x7C596B46] = typeof(Layer23.FileLocationUnavailable),
[0x53D69076] = typeof(Layer23.FileLocation_),
[0xFA95B0DD] = typeof(Layer23.DecryptedMessageMediaExternalDocument),
[0x36B091DE] = typeof(Layer45.DecryptedMessage),
[0xF1FA8D78] = typeof(Layer45.DecryptedMessageMediaPhoto),
[0x970C8C0E] = typeof(Layer45.DecryptedMessageMediaVideo),
[0x7AFE8AE2] = typeof(Layer45.DecryptedMessageMediaDocument),
[0x3A556302] = typeof(Layer45.DocumentAttributeSticker),
[0xDED218E0] = typeof(Layer45.DocumentAttributeAudio),
[0x8A0DF56F] = typeof(Layer45.DecryptedMessageMediaVenue),
[0xE50511D8] = typeof(Layer45.DecryptedMessageMediaWebPage),
[0xBB718624] = typeof(Layer66.SendMessageUploadRoundAction),
[0x91CC4674] = typeof(Layer73.DecryptedMessage),
// The End
};
}
}

308
src/TL.cs Normal file
View file

@ -0,0 +1,308 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;
using WTelegram;
namespace TL
{
public interface ITLObject { }
public interface ITLFunction<R> : ITLObject { }
public static partial class Schema
{
internal static byte[] Serialize(ITLObject msg)
{
using var memStream = new MemoryStream(1024);
using (var writer = new BinaryWriter(memStream))
Serialize(writer, msg);
return memStream.ToArray();
}
internal static T Deserialize<T>(byte[] bytes) where T : ITLObject
{
using var memStream = new MemoryStream(bytes);
using var reader = new BinaryReader(memStream);
return Deserialize<T>(reader);
}
internal static void Serialize(BinaryWriter writer, ITLObject msg)
{
var type = msg.GetType();
var ctorNb = type.GetCustomAttribute<TLDefAttribute>().CtorNb;
writer.Write(ctorNb);
SerializeObject(writer, msg);
}
internal static T Deserialize<T>(BinaryReader reader) where T : ITLObject
{
var ctorNb = reader.ReadUInt32();
if (!Table.TryGetValue(ctorNb, out var realType))
throw new ApplicationException($"Cannot find type for ctor #{ctorNb:x}");
return (T)DeserializeObject(reader, realType);
}
internal static void SerializeObject(BinaryWriter writer, object obj)
{
var fields = obj.GetType().GetFields().GroupBy(f => f.DeclaringType).Reverse().SelectMany(g => g);
int flags = 0;
IfFlagAttribute ifFlag;
foreach (var field in fields)
{
if (((ifFlag = field.GetCustomAttribute<IfFlagAttribute>()) != null) && (flags & (1 << ifFlag.Bit)) == 0) continue;
object value = field.GetValue(obj);
if (value == null)
SerializeNull(writer, field.FieldType);
else
SerializeValue(writer, value);
if (field.Name.Equals("Flags", StringComparison.OrdinalIgnoreCase)) flags = (int)value;
}
}
internal static ITLObject DeserializeObject(BinaryReader reader, Type type)
{
var obj = Activator.CreateInstance(type);
var fields = obj.GetType().GetFields().GroupBy(f => f.DeclaringType).Reverse().SelectMany(g => g);
int flags = 0;
IfFlagAttribute ifFlag;
foreach (var field in fields)
{
if (((ifFlag = field.GetCustomAttribute<IfFlagAttribute>()) != null) && (flags & (1 << ifFlag.Bit)) == 0) continue;
object value = DeserializeValue(reader, field.FieldType);
field.SetValue(obj, value);
if (field.Name.Equals("Flags", StringComparison.OrdinalIgnoreCase)) flags = (int)value;
}
return type == typeof(GzipPacked) ? UnzipPacket((GzipPacked)obj) : (ITLObject)obj;
}
internal static void SerializeValue(BinaryWriter writer, object value)
{
var type = value.GetType();
switch (Type.GetTypeCode(type))
{
case TypeCode.Int32: writer.Write((int)value); break;
case TypeCode.UInt32: writer.Write((uint)value); break;
case TypeCode.Int64: writer.Write((long)value); break;
case TypeCode.UInt64: writer.Write((ulong)value); break;
case TypeCode.Double: writer.Write((double)value); break;
case TypeCode.String: SerializeBytes(writer, Encoding.UTF8.GetBytes((string)value)); break;
case TypeCode.DateTime: writer.Write((uint)(((DateTime)value).ToUniversalTime().Ticks / 10000000 - 62135596800L)); break;
case TypeCode.Boolean: writer.Write((bool)value ? 0x997275b5 : 0xbc799737); break;
case TypeCode.Object:
if (type.IsArray)
{
if (value is byte[] bytes)
SerializeBytes(writer, bytes);
else
SerializeVector(writer, (Array)value);
}
else if (value is Int128 int128)
writer.Write(int128);
else if (value is Int256 int256)
writer.Write(int256);
else if (type.IsValueType)
SerializeObject(writer, value);
else if (value is ITLObject tlObject)
Serialize(writer, tlObject);
else
ShouldntBeHere();
break;
default:
ShouldntBeHere();
break;
}
}
internal static object DeserializeValue(BinaryReader reader, Type type)
{
switch (Type.GetTypeCode(type))
{
case TypeCode.Int32: return reader.ReadInt32();
case TypeCode.UInt32: return reader.ReadUInt32();
case TypeCode.Int64: return reader.ReadInt64();
case TypeCode.UInt64: return reader.ReadUInt64();
case TypeCode.Double: return reader.ReadDouble();
case TypeCode.String: return Encoding.UTF8.GetString(DeserializeBytes(reader));
case TypeCode.DateTime: return new DateTime((reader.ReadUInt32() + 62135596800L) * 10000000, DateTimeKind.Utc);
case TypeCode.Boolean:
return reader.ReadUInt32() switch
{
0x997275b5 => true,
0xbc799737 => false,
var value => throw new ApplicationException($"Invalid boolean value #{value:x}")
};
case TypeCode.Object:
if (type.IsArray)
{
if (type == typeof(byte[]))
return DeserializeBytes(reader);
else if (type == typeof(_Message[]))
return DeserializeMessages(reader);
else
return DeserializeVector(reader, type);
}
else if (type == typeof(Int128))
return new Int128(reader);
else if (type == typeof(Int256))
return new Int256(reader);
else if (type.IsValueType)
return DeserializeObject(reader, type);
else
return Deserialize<ITLObject>(reader);
default:
ShouldntBeHere();
return null;
}
}
private static void SerializeVector(BinaryWriter writer, Array array)
{
writer.Write(VectorCtor);
int count = array.Length;
writer.Write(count);
for (int i = 0; i < count; i++)
SerializeValue(writer, array.GetValue(i));
}
private static object DeserializeVector(BinaryReader reader, Type type)
{
var ctorNb = reader.ReadInt32();
if (ctorNb != VectorCtor) throw new ApplicationException($"Cannot deserialize {type.Name} with ctor #{ctorNb:x}");
var elementType = type.GetElementType();
int count = reader.ReadInt32();
Array array = (Array)Activator.CreateInstance(type, count);
for (int i = 0; i < count; i++)
array.SetValue(DeserializeValue(reader, elementType), i);
return array;
}
private static void SerializeBytes(BinaryWriter writer, byte[] bytes)
{
int length = bytes.Length;
if (length < 254)
writer.Write((byte)length);
else
{
writer.Write(length << 8 | 254);
length += 3;
}
writer.Write(bytes);
while (++length % 4 != 0) writer.Write((byte)0);
}
private static byte[] DeserializeBytes(BinaryReader reader)
{
byte[] bytes;
int length = reader.ReadByte();
if (length < 254)
bytes = reader.ReadBytes(length);
else
{
length = reader.ReadInt16() + (reader.ReadByte() << 16);
bytes = reader.ReadBytes(length);
length += 3;
}
while (++length % 4 != 0) reader.ReadByte();
return bytes;
}
internal static void SerializeNull(BinaryWriter writer, Type type)
{
if (!type.IsArray)
writer.Write(NullCtor);
else if (type != typeof(byte[])) // null arrays are serialized as empty
writer.Write(VectorCtor);
writer.Write(0);
}
private static _Message[] DeserializeMessages(BinaryReader reader)
{
int count = reader.ReadInt32();
var array = new _Message[count];
for (int i = 0; i < count; i++)
{
array[i] = new _Message
{
msg_id = reader.ReadInt64(),
seqno = reader.ReadInt32(),
bytes = reader.ReadInt32(),
};
var pos = reader.BaseStream.Position;
try
{
array[i].body = (ITLObject)DeserializeValue(reader, typeof(ITLObject));
}
catch (Exception ex)
{
Helpers.Log(4, ex.ToString());
}
reader.BaseStream.Position = pos + array[i].bytes;
}
return array;
}
private static ITLObject UnzipPacket(GzipPacked obj)
{
using var reader = new BinaryReader(new GZipStream(new MemoryStream(obj.packed_data), CompressionMode.Decompress));
var result = Deserialize<ITLObject>(reader);
Helpers.Log(1, $" → {result.GetType().Name}");
return result;
}
#if DEBUG
private static void ShouldntBeHere() => System.Diagnostics.Debugger.Break();
#else
private static void ShouldntBeHere() => throw new NotImplementedException("You've reached an unexpected point in code");
#endif
}
[AttributeUsage(AttributeTargets.Class)]
public class TLDefAttribute : Attribute
{
public readonly uint CtorNb;
public TLDefAttribute(uint ctorNb) => CtorNb = ctorNb;
/*public TLDefAttribute(string def)
{
var hash = def.IndexOfAny(new[] { '#', ' ' });
CtorNb = def[hash] == ' ' ? Force.Crc32.Crc32Algorithm.Compute(System.Text.Encoding.UTF8.GetBytes(def))
: uint.Parse(def[(hash + 1)..def.IndexOf(' ', hash)], System.Globalization.NumberStyles.HexNumber);
}*/
}
[AttributeUsage(AttributeTargets.Field)]
public class IfFlagAttribute : Attribute
{
public readonly int Bit;
public IfFlagAttribute(int bit) => Bit = bit;
}
public struct Int128
{
public byte[] raw;
public Int128(BinaryReader reader) => raw = reader.ReadBytes(16);
public Int128(RNGCryptoServiceProvider rng) => rng.GetBytes(raw = new byte[16]);
public static bool operator ==(Int128 left, Int128 right) { for (int i = 0; i < 16; i++) if (left.raw[i] != right.raw[i]) return false; return true; }
public static bool operator !=(Int128 left, Int128 right) { for (int i = 0; i < 16; i++) if (left.raw[i] != right.raw[i]) return true; return false; }
public override bool Equals(object obj) => obj is Int128 other && this == other;
public override int GetHashCode() => HashCode.Combine(raw[0], raw[1]);
public static implicit operator byte[](Int128 int128) => int128.raw;
}
public struct Int256
{
public byte[] raw;
public Int256(BinaryReader reader) => raw = reader.ReadBytes(32);
public Int256(RNGCryptoServiceProvider rng) => rng.GetBytes(raw = new byte[32]);
public static bool operator ==(Int256 left, Int256 right) { for (int i = 0; i < 32; i++) if (left.raw[i] != right.raw[i]) return false; return true; }
public static bool operator !=(Int256 left, Int256 right) { for (int i = 0; i < 32; i++) if (left.raw[i] != right.raw[i]) return true; return false; }
public override bool Equals(object obj) => obj is Int256 other && this == other;
public override int GetHashCode() => HashCode.Combine(raw[0], raw[1]);
public static implicit operator byte[](Int256 int256) => int256.raw;
}
}

View file

@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFramework>net5.0</TargetFramework>
<RootNamespace>WTelegram</RootNamespace>
<Description>Telegram client library written 100% in C# and .NET Core</Description>
<Authors>Wizou</Authors>
<PackageProjectUrl>https://github.com/wiz0u/WTelegramClient</PackageProjectUrl>
</PropertyGroup>
<ItemGroup>
<None Remove=".gitattributes" />
<None Remove=".gitignore" />
<None Remove="azure-pipelines.yml" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Crc32.NET" Version="1.2.0" />
</ItemGroup>
</Project>

25
src/WTelegramClient.sln Normal file
View file

@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.31515.178
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WTelegramClient", "WTelegramClient.csproj", "{ABB3CB38-A5EC-4428-BB72-06C6BA99DD81}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{ABB3CB38-A5EC-4428-BB72-06C6BA99DD81}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{ABB3CB38-A5EC-4428-BB72-06C6BA99DD81}.Debug|Any CPU.Build.0 = Debug|Any CPU
{ABB3CB38-A5EC-4428-BB72-06C6BA99DD81}.Release|Any CPU.ActiveCfg = Release|Any CPU
{ABB3CB38-A5EC-4428-BB72-06C6BA99DD81}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {5A8D162E-4943-4748-A046-A17BA8C10ACC}
EndGlobalSection
EndGlobal