diff --git a/TLSharp.Core/DataCenterIPVersion.cs b/TLSharp.Core/DataCenterIPVersion.cs new file mode 100644 index 0000000..9b22794 --- /dev/null +++ b/TLSharp.Core/DataCenterIPVersion.cs @@ -0,0 +1,30 @@ +namespace TLSharp.Core +{ + /// + /// When the Telegram server responds with a set of addresses to connect to, DataCenterIPVersion indicates a preference + /// for how to choose the IP address to connect to + /// + public enum DataCenterIPVersion + { + /// + /// Prefers IPv6 addresses if any is passed by Telegram + /// + Default = 0, + /// + /// Takes only IPv4 addresses + /// + OnlyIPv4 = 1, + /// + /// Takes only IPv6 addresses + /// + OnlyIPv6 = 2, + /// + /// Connection to IPv4 addresses is preferred to IPv6 addresses + /// + PreferIPv4 = 3, + /// + /// Connection to IPv6 addresses is preferred to IPv6 addresses + /// + PreferIPv6 = 4, + } +} diff --git a/TLSharp.Core/TelegramClient.cs b/TLSharp.Core/TelegramClient.cs index 46e0ad4..997dfd5 100644 --- a/TLSharp.Core/TelegramClient.cs +++ b/TLSharp.Core/TelegramClient.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.Configuration; +using System.Data; using System.Linq; using System.Security.Cryptography; using System.Text; @@ -22,433 +24,459 @@ using TLAuthorization = TeleSharp.TL.Auth.TLAuthorization; namespace TLSharp.Core { - public class TelegramClient : IDisposable - { - private MtProtoSender sender; - private TcpTransport transport; - private string apiHash = String.Empty; - private int apiId = 0; - private Session session; - private List dcOptions; - private TcpClientConnectionHandler handler; - - public Session Session - { - get { return session; } - } - - public TelegramClient(int apiId, string apiHash, - ISessionStore store = null, string sessionUserId = "session", TcpClientConnectionHandler handler = null) - { - if (apiId == default(int)) - throw new MissingApiConfigurationException("API_ID"); - if (string.IsNullOrEmpty(apiHash)) - throw new MissingApiConfigurationException("API_HASH"); - - if (store == null) - store = new FileSessionStore(); - - this.apiHash = apiHash; - this.apiId = apiId; - this.handler = handler; - - session = Session.TryLoadOrCreateNew(store, sessionUserId); - transport = new TcpTransport (session.DataCenter.Address, session.DataCenter.Port, this.handler); - } - - public async Task ConnectAsync(bool reconnect = false, CancellationToken token = default(CancellationToken)) - { - token.ThrowIfCancellationRequested(); - - if (session.AuthKey == null || reconnect) - { - var result = await Authenticator.DoAuthentication(transport, token).ConfigureAwait(false); - session.AuthKey = result.AuthKey; - session.TimeOffset = result.TimeOffset; - } - - sender = new MtProtoSender(transport, session); - - //set-up layer - var config = new TLRequestGetConfig(); - var request = new TLRequestInitConnection() - { - ApiId = apiId, - AppVersion = "1.0.0", - DeviceModel = "PC", - LangCode = "en", - Query = config, - SystemVersion = "Win 10.0" - }; - var invokewithLayer = new TLRequestInvokeWithLayer() { Layer = 66, Query = request }; - await sender.Send(invokewithLayer, token).ConfigureAwait(false); - await sender.Receive(invokewithLayer, token).ConfigureAwait(false); - - dcOptions = ((TLConfig)invokewithLayer.Response).DcOptions.ToList(); - } - - private async Task ReconnectToDcAsync(int dcId, CancellationToken token = default(CancellationToken)) - { - token.ThrowIfCancellationRequested(); - - if (dcOptions == null || !dcOptions.Any()) - throw new InvalidOperationException($"Can't reconnect. Establish initial connection first."); - - TLExportedAuthorization exported = null; - if (session.TLUser != null) - { - TLRequestExportAuthorization exportAuthorization = new TLRequestExportAuthorization() { DcId = dcId }; - exported = await SendRequestAsync(exportAuthorization, token).ConfigureAwait(false); - } - - var dc = dcOptions.First(d => d.Id == dcId); - var dataCenter = new DataCenter (dcId, dc.IpAddress, dc.Port); - - transport = new TcpTransport(dc.IpAddress, dc.Port, handler); - session.DataCenter = dataCenter; - - await ConnectAsync(true, token).ConfigureAwait(false); - - if (session.TLUser != null) - { - TLRequestImportAuthorization importAuthorization = new TLRequestImportAuthorization() { Id = exported.Id, Bytes = exported.Bytes }; - var imported = await SendRequestAsync(importAuthorization, token).ConfigureAwait(false); - OnUserAuthenticated((TLUser)imported.User); - } - } - - private async Task RequestWithDcMigration(TLMethod request, CancellationToken token = default(CancellationToken)) - { - if (sender == null) - throw new InvalidOperationException("Not connected!"); - - var completed = false; - while(!completed) - { - try - { - await sender.Send(request, token).ConfigureAwait(false); - await sender.Receive(request, token).ConfigureAwait(false); - completed = true; - } - catch(DataCenterMigrationException e) - { - if (session.DataCenter.DataCenterId.HasValue && - session.DataCenter.DataCenterId.Value == e.DC) - { - throw new Exception($"Telegram server replied requesting a migration to DataCenter {e.DC} when this connection was already using this DataCenter", e); - } - - await ReconnectToDcAsync(e.DC, token).ConfigureAwait(false); - // prepare the request for another try - request.ConfirmReceived = false; - } - } - } - - public bool IsUserAuthorized() - { - return session.TLUser != null; - } - - public async Task IsPhoneRegisteredAsync(string phoneNumber, CancellationToken token = default(CancellationToken)) - { - if (String.IsNullOrWhiteSpace(phoneNumber)) - throw new ArgumentNullException(nameof(phoneNumber)); - - var authCheckPhoneRequest = new TLRequestCheckPhone() { PhoneNumber = phoneNumber }; - - await RequestWithDcMigration(authCheckPhoneRequest, token).ConfigureAwait(false); - - return authCheckPhoneRequest.Response.PhoneRegistered; - } - - public async Task SendCodeRequestAsync(string phoneNumber, CancellationToken token = default(CancellationToken)) - { - if (String.IsNullOrWhiteSpace(phoneNumber)) - throw new ArgumentNullException(nameof(phoneNumber)); - - var request = new TLRequestSendCode() { PhoneNumber = phoneNumber, ApiId = apiId, ApiHash = apiHash }; - - await RequestWithDcMigration(request, token).ConfigureAwait(false); - - return request.Response.PhoneCodeHash; - } - - public async Task MakeAuthAsync(string phoneNumber, string phoneCodeHash, string code, CancellationToken token = default(CancellationToken)) - { - if (String.IsNullOrWhiteSpace(phoneNumber)) - throw new ArgumentNullException(nameof(phoneNumber)); - if (String.IsNullOrWhiteSpace(phoneCodeHash)) - throw new ArgumentNullException(nameof(phoneCodeHash)); + public class TelegramClient : IDisposable + { + private MtProtoSender sender; + private TcpTransport transport; + private string apiHash = String.Empty; + private int apiId = 0; + private Session session; + private List dcOptions; + private TcpClientConnectionHandler handler; + private DataCenterIPVersion dcIpVersion; + + public Session Session + { + get { return session; } + } + + /// + /// Creates a new TelegramClient + /// + /// The API ID provided by Telegram. Get one at https://my.telegram.org + /// The API Hash provided by Telegram. Get one at https://my.telegram.org + /// An ISessionStore object that will handle the session + /// The name of the session that tracks login info about this TelegramClient connection + /// A delegate to invoke when a connection is needed and that will return a TcpClient that will be used to connect + /// Indicates the preferred IpAddress version to use to connect to a Telegram server + public TelegramClient(int apiId, string apiHash, + ISessionStore store = null, string sessionUserId = "session", TcpClientConnectionHandler handler = null, DataCenterIPVersion dcIpVersion = DataCenterIPVersion.Default) + { + if (apiId == default(int)) + throw new MissingApiConfigurationException("API_ID"); + if (string.IsNullOrEmpty(apiHash)) + throw new MissingApiConfigurationException("API_HASH"); + + if (store == null) + store = new FileSessionStore(); + + this.apiHash = apiHash; + this.apiId = apiId; + this.handler = handler; + this.dcIpVersion = dcIpVersion; + + session = Session.TryLoadOrCreateNew(store, sessionUserId); + transport = new TcpTransport(session.DataCenter.Address, session.DataCenter.Port, this.handler); + } + + public async Task ConnectAsync(bool reconnect = false, CancellationToken token = default(CancellationToken)) + { + token.ThrowIfCancellationRequested(); + + if (session.AuthKey == null || reconnect) + { + var result = await Authenticator.DoAuthentication(transport, token).ConfigureAwait(false); + session.AuthKey = result.AuthKey; + session.TimeOffset = result.TimeOffset; + } + + sender = new MtProtoSender(transport, session); + + //set-up layer + var config = new TLRequestGetConfig(); + var request = new TLRequestInitConnection() + { + ApiId = apiId, + AppVersion = "1.0.0", + DeviceModel = "PC", + LangCode = "en", + Query = config, + SystemVersion = "Win 10.0" + }; + var invokewithLayer = new TLRequestInvokeWithLayer() { Layer = 66, Query = request }; + await sender.Send(invokewithLayer, token).ConfigureAwait(false); + await sender.Receive(invokewithLayer, token).ConfigureAwait(false); + + dcOptions = ((TLConfig)invokewithLayer.Response).DcOptions.ToList(); + } + + private async Task ReconnectToDcAsync(int dcId, CancellationToken token = default(CancellationToken)) + { + token.ThrowIfCancellationRequested(); + + if (dcOptions == null || !dcOptions.Any()) + throw new InvalidOperationException($"Can't reconnect. Establish initial connection first."); + + TLExportedAuthorization exported = null; + if (session.TLUser != null) + { + TLRequestExportAuthorization exportAuthorization = new TLRequestExportAuthorization() { DcId = dcId }; + exported = await SendRequestAsync(exportAuthorization, token).ConfigureAwait(false); + } + + var dcs = dcOptions.Where(d => d.Id == dcId + && ( + (dcIpVersion == DataCenterIPVersion.Default) // any + || (d.Ipv6 && dcIpVersion == DataCenterIPVersion.OnlyIPv6) // selects only ipv6 addresses + || (!d.Ipv6 && dcIpVersion == DataCenterIPVersion.OnlyIPv4) // selects only ipv4 addresses + || dcIpVersion == DataCenterIPVersion.PreferIPv4 // we can take both types of address + || dcIpVersion == DataCenterIPVersion.PreferIPv6 // we can take both types of address + ) + ).OrderBy(d => d.Ipv6); + + if (dcs.Count() == 0) + throw new Exception($"Telegram server didn't provide us with any IPAddress that matches your preferences. If you chose OnlyIPvX, try switch to PreferIPvX instead."); + + var dc = dcIpVersion == DataCenterIPVersion.PreferIPv4 ? dcs.First() : dcs.Last(); // ipv4 addresses are at the beginning of the list because it was ordered + + var dataCenter = new DataCenter(dcId, dc.IpAddress, dc.Port); + + transport = new TcpTransport(dc.IpAddress, dc.Port, handler); + session.DataCenter = dataCenter; + + await ConnectAsync(true, token).ConfigureAwait(false); + + if (session.TLUser != null) + { + TLRequestImportAuthorization importAuthorization = new TLRequestImportAuthorization() { Id = exported.Id, Bytes = exported.Bytes }; + var imported = await SendRequestAsync(importAuthorization, token).ConfigureAwait(false); + OnUserAuthenticated((TLUser)imported.User); + } + } + + private async Task RequestWithDcMigration(TLMethod request, CancellationToken token = default(CancellationToken)) + { + if (sender == null) + throw new InvalidOperationException("Not connected!"); + + var completed = false; + while (!completed) + { + try + { + await sender.Send(request, token).ConfigureAwait(false); + await sender.Receive(request, token).ConfigureAwait(false); + completed = true; + } + catch (DataCenterMigrationException e) + { + if (session.DataCenter.DataCenterId.HasValue && + session.DataCenter.DataCenterId.Value == e.DC) + { + throw new Exception($"Telegram server replied requesting a migration to DataCenter {e.DC} when this connection was already using this DataCenter", e); + } - if (String.IsNullOrWhiteSpace(code)) - throw new ArgumentNullException(nameof(code)); - - var request = new TLRequestSignIn() { PhoneNumber = phoneNumber, PhoneCodeHash = phoneCodeHash, PhoneCode = code }; + await ReconnectToDcAsync(e.DC, token).ConfigureAwait(false); + // prepare the request for another try + request.ConfirmReceived = false; + } + } + } - await RequestWithDcMigration(request, token).ConfigureAwait(false); + public bool IsUserAuthorized() + { + return session.TLUser != null; + } - OnUserAuthenticated(((TLUser)request.Response.User)); + public async Task IsPhoneRegisteredAsync(string phoneNumber, CancellationToken token = default(CancellationToken)) + { + if (String.IsNullOrWhiteSpace(phoneNumber)) + throw new ArgumentNullException(nameof(phoneNumber)); - return ((TLUser)request.Response.User); - } - - public async Task GetPasswordSetting(CancellationToken token = default(CancellationToken)) - { - var request = new TLRequestGetPassword(); + var authCheckPhoneRequest = new TLRequestCheckPhone() { PhoneNumber = phoneNumber }; - await RequestWithDcMigration(request, token).ConfigureAwait(false); + await RequestWithDcMigration(authCheckPhoneRequest, token).ConfigureAwait(false); - return (TLPassword)request.Response; - } + return authCheckPhoneRequest.Response.PhoneRegistered; + } - public async Task MakeAuthWithPasswordAsync(TLPassword password, string password_str, CancellationToken token = default(CancellationToken)) - { - token.ThrowIfCancellationRequested(); - - byte[] password_Bytes = Encoding.UTF8.GetBytes(password_str); - IEnumerable rv = password.CurrentSalt.Concat(password_Bytes).Concat(password.CurrentSalt); - - SHA256Managed hashstring = new SHA256Managed(); - var password_hash = hashstring.ComputeHash(rv.ToArray()); - - var request = new TLRequestCheckPassword() { PasswordHash = password_hash }; - - await RequestWithDcMigration(request, token).ConfigureAwait(false); - - OnUserAuthenticated((TLUser)request.Response.User); - - return (TLUser)request.Response.User; - } - - public async Task SignUpAsync(string phoneNumber, string phoneCodeHash, string code, string firstName, string lastName, CancellationToken token = default(CancellationToken)) - { - var request = new TLRequestSignUp() { PhoneNumber = phoneNumber, PhoneCode = code, PhoneCodeHash = phoneCodeHash, FirstName = firstName, LastName = lastName }; - - await RequestWithDcMigration(request, token).ConfigureAwait(false); - - OnUserAuthenticated((TLUser)request.Response.User); - - return (TLUser)request.Response.User; - } - - public async Task SendRequestAsync(TLMethod methodToExecute, CancellationToken token = default(CancellationToken)) - { - await RequestWithDcMigration(methodToExecute, token).ConfigureAwait(false); - - var result = methodToExecute.GetType().GetProperty("Response").GetValue(methodToExecute); - - return (T)result; - } - - internal async Task SendAuthenticatedRequestAsync (TLMethod methodToExecute, CancellationToken token = default(CancellationToken)) - { - if (!IsUserAuthorized()) - throw new InvalidOperationException("Authorize user first!"); - - return await SendRequestAsync(methodToExecute, token) - .ConfigureAwait(false); - } - - public async Task UpdateUsernameAsync(string username, CancellationToken token = default(CancellationToken)) - { - var req = new TLRequestUpdateUsername { Username = username }; - - return await SendAuthenticatedRequestAsync(req, token) - .ConfigureAwait(false); - } - - public async Task CheckUsernameAsync(string username, CancellationToken token = default(CancellationToken)) - { - var req = new TLRequestCheckUsername { Username = username }; - - return await SendAuthenticatedRequestAsync(req, token) - .ConfigureAwait(false); - } - - public async Task ImportContactsAsync(IReadOnlyList contacts, CancellationToken token = default(CancellationToken)) - { - var req = new TLRequestImportContacts { Contacts = new TLVector(contacts)}; - - return await SendAuthenticatedRequestAsync(req, token) - .ConfigureAwait(false); - } - - public async Task DeleteContactsAsync(IReadOnlyList users, CancellationToken token = default(CancellationToken)) - { - var req = new TLRequestDeleteContacts {Id = new TLVector(users)}; - - return await SendAuthenticatedRequestAsync(req, token) - .ConfigureAwait(false); - } - - public async Task DeleteContactAsync(TLAbsInputUser user, CancellationToken token = default(CancellationToken)) - { - var req = new TLRequestDeleteContact {Id = user}; - - return await SendAuthenticatedRequestAsync(req, token) - .ConfigureAwait(false); - } - - public async Task GetContactsAsync(CancellationToken token = default(CancellationToken)) - { - var req = new TLRequestGetContacts() { Hash = "" }; - - return await SendAuthenticatedRequestAsync(req, token) - .ConfigureAwait(false); - } - - public async Task SendMessageAsync(TLAbsInputPeer peer, string message, CancellationToken token = default(CancellationToken)) - { - return await SendAuthenticatedRequestAsync( - new TLRequestSendMessage() - { - Peer = peer, - Message = message, - RandomId = Helpers.GenerateRandomLong() - }, token) - .ConfigureAwait(false); - } - - public async Task SendTypingAsync(TLAbsInputPeer peer, CancellationToken token = default(CancellationToken)) - { - var req = new TLRequestSetTyping() - { - Action = new TLSendMessageTypingAction(), - Peer = peer - }; - return await SendAuthenticatedRequestAsync(req, token) - .ConfigureAwait(false); - } - - public async Task GetUserDialogsAsync(int offsetDate = 0, int offsetId = 0, TLAbsInputPeer offsetPeer = null, int limit = 100, CancellationToken token = default(CancellationToken)) - { - if (offsetPeer == null) - offsetPeer = new TLInputPeerSelf(); - - var req = new TLRequestGetDialogs() - { - OffsetDate = offsetDate, - OffsetId = offsetId, - OffsetPeer = offsetPeer, - Limit = limit - }; - return await SendAuthenticatedRequestAsync(req, token) - .ConfigureAwait(false); - } - - public async Task SendUploadedPhoto(TLAbsInputPeer peer, TLAbsInputFile file, string caption, CancellationToken token = default(CancellationToken)) - { - return await SendAuthenticatedRequestAsync(new TLRequestSendMedia() - { - RandomId = Helpers.GenerateRandomLong(), - Background = false, - ClearDraft = false, - Media = new TLInputMediaUploadedPhoto() { File = file, Caption = caption }, - Peer = peer - }, token) - .ConfigureAwait(false); - } - - public async Task SendUploadedDocument( - TLAbsInputPeer peer, TLAbsInputFile file, string caption, string mimeType, TLVector attributes, CancellationToken token = default(CancellationToken)) - { - return await SendAuthenticatedRequestAsync(new TLRequestSendMedia() - { - RandomId = Helpers.GenerateRandomLong(), - Background = false, - ClearDraft = false, - Media = new TLInputMediaUploadedDocument() - { - File = file, - Caption = caption, - MimeType = mimeType, - Attributes = attributes - }, - Peer = peer - }, token) - .ConfigureAwait(false); - } - - public async Task GetFile(TLAbsInputFileLocation location, int filePartSize, int offset = 0, CancellationToken token = default(CancellationToken)) - { - TLFile result = await SendAuthenticatedRequestAsync(new TLRequestGetFile - { - Location = location, - Limit = filePartSize, - Offset = offset - }, token) - .ConfigureAwait(false); - return result; - } - - public async Task SendPingAsync(CancellationToken token = default(CancellationToken)) - { - await sender.SendPingAsync(token) - .ConfigureAwait(false); - } - - public async Task GetHistoryAsync(TLAbsInputPeer peer, int offsetId = 0, int offsetDate = 0, int addOffset = 0, int limit = 100, int maxId = 0, int minId = 0, CancellationToken token = default(CancellationToken)) - { - var req = new TLRequestGetHistory() - { - Peer = peer, - OffsetId = offsetId, - OffsetDate = offsetDate, - AddOffset = addOffset, - Limit = limit, - MaxId = maxId, - MinId = minId - }; - return await SendAuthenticatedRequestAsync(req, token) - .ConfigureAwait(false); - } - - /// - /// Serch user or chat. API: contacts.search#11f812d8 q:string limit:int = contacts.Found; - /// - /// User or chat name - /// Max result count - /// - public async Task SearchUserAsync(string q, int limit = 10, CancellationToken token = default(CancellationToken)) - { - var r = new TeleSharp.TL.Contacts.TLRequestSearch - { - Q = q, - Limit = limit - }; - - return await SendAuthenticatedRequestAsync(r, token) - .ConfigureAwait(false); - } - - private void OnUserAuthenticated(TLUser TLUser) - { - session.TLUser = TLUser; - session.SessionExpires = int.MaxValue; - - session.Save(); - } - - public bool IsConnected - { - get - { - if (transport == null) - return false; - return transport.IsConnected; - } - } - - public void Dispose() - { - if (transport != null) - { - transport.Dispose(); - transport = null; - } - } - } + public async Task SendCodeRequestAsync(string phoneNumber, CancellationToken token = default(CancellationToken)) + { + if (String.IsNullOrWhiteSpace(phoneNumber)) + throw new ArgumentNullException(nameof(phoneNumber)); + + var request = new TLRequestSendCode() { PhoneNumber = phoneNumber, ApiId = apiId, ApiHash = apiHash }; + + await RequestWithDcMigration(request, token).ConfigureAwait(false); + + return request.Response.PhoneCodeHash; + } + + public async Task MakeAuthAsync(string phoneNumber, string phoneCodeHash, string code, CancellationToken token = default(CancellationToken)) + { + if (String.IsNullOrWhiteSpace(phoneNumber)) + throw new ArgumentNullException(nameof(phoneNumber)); + + if (String.IsNullOrWhiteSpace(phoneCodeHash)) + throw new ArgumentNullException(nameof(phoneCodeHash)); + + if (String.IsNullOrWhiteSpace(code)) + throw new ArgumentNullException(nameof(code)); + + var request = new TLRequestSignIn() { PhoneNumber = phoneNumber, PhoneCodeHash = phoneCodeHash, PhoneCode = code }; + + await RequestWithDcMigration(request, token).ConfigureAwait(false); + + OnUserAuthenticated(((TLUser)request.Response.User)); + + return ((TLUser)request.Response.User); + } + + public async Task GetPasswordSetting(CancellationToken token = default(CancellationToken)) + { + var request = new TLRequestGetPassword(); + + await RequestWithDcMigration(request, token).ConfigureAwait(false); + + return (TLPassword)request.Response; + } + + public async Task MakeAuthWithPasswordAsync(TLPassword password, string password_str, CancellationToken token = default(CancellationToken)) + { + token.ThrowIfCancellationRequested(); + + byte[] password_Bytes = Encoding.UTF8.GetBytes(password_str); + IEnumerable rv = password.CurrentSalt.Concat(password_Bytes).Concat(password.CurrentSalt); + + SHA256Managed hashstring = new SHA256Managed(); + var password_hash = hashstring.ComputeHash(rv.ToArray()); + + var request = new TLRequestCheckPassword() { PasswordHash = password_hash }; + + await RequestWithDcMigration(request, token).ConfigureAwait(false); + + OnUserAuthenticated((TLUser)request.Response.User); + + return (TLUser)request.Response.User; + } + + public async Task SignUpAsync(string phoneNumber, string phoneCodeHash, string code, string firstName, string lastName, CancellationToken token = default(CancellationToken)) + { + var request = new TLRequestSignUp() { PhoneNumber = phoneNumber, PhoneCode = code, PhoneCodeHash = phoneCodeHash, FirstName = firstName, LastName = lastName }; + + await RequestWithDcMigration(request, token).ConfigureAwait(false); + + OnUserAuthenticated((TLUser)request.Response.User); + + return (TLUser)request.Response.User; + } + + public async Task SendRequestAsync(TLMethod methodToExecute, CancellationToken token = default(CancellationToken)) + { + await RequestWithDcMigration(methodToExecute, token).ConfigureAwait(false); + + var result = methodToExecute.GetType().GetProperty("Response").GetValue(methodToExecute); + + return (T)result; + } + + internal async Task SendAuthenticatedRequestAsync(TLMethod methodToExecute, CancellationToken token = default(CancellationToken)) + { + if (!IsUserAuthorized()) + throw new InvalidOperationException("Authorize user first!"); + + return await SendRequestAsync(methodToExecute, token) + .ConfigureAwait(false); + } + + public async Task UpdateUsernameAsync(string username, CancellationToken token = default(CancellationToken)) + { + var req = new TLRequestUpdateUsername { Username = username }; + + return await SendAuthenticatedRequestAsync(req, token) + .ConfigureAwait(false); + } + + public async Task CheckUsernameAsync(string username, CancellationToken token = default(CancellationToken)) + { + var req = new TLRequestCheckUsername { Username = username }; + + return await SendAuthenticatedRequestAsync(req, token) + .ConfigureAwait(false); + } + + public async Task ImportContactsAsync(IReadOnlyList contacts, CancellationToken token = default(CancellationToken)) + { + var req = new TLRequestImportContacts { Contacts = new TLVector(contacts) }; + + return await SendAuthenticatedRequestAsync(req, token) + .ConfigureAwait(false); + } + + public async Task DeleteContactsAsync(IReadOnlyList users, CancellationToken token = default(CancellationToken)) + { + var req = new TLRequestDeleteContacts { Id = new TLVector(users) }; + + return await SendAuthenticatedRequestAsync(req, token) + .ConfigureAwait(false); + } + + public async Task DeleteContactAsync(TLAbsInputUser user, CancellationToken token = default(CancellationToken)) + { + var req = new TLRequestDeleteContact { Id = user }; + + return await SendAuthenticatedRequestAsync(req, token) + .ConfigureAwait(false); + } + + public async Task GetContactsAsync(CancellationToken token = default(CancellationToken)) + { + var req = new TLRequestGetContacts() { Hash = "" }; + + return await SendAuthenticatedRequestAsync(req, token) + .ConfigureAwait(false); + } + + public async Task SendMessageAsync(TLAbsInputPeer peer, string message, CancellationToken token = default(CancellationToken)) + { + return await SendAuthenticatedRequestAsync( + new TLRequestSendMessage() + { + Peer = peer, + Message = message, + RandomId = Helpers.GenerateRandomLong() + }, token) + .ConfigureAwait(false); + } + + public async Task SendTypingAsync(TLAbsInputPeer peer, CancellationToken token = default(CancellationToken)) + { + var req = new TLRequestSetTyping() + { + Action = new TLSendMessageTypingAction(), + Peer = peer + }; + return await SendAuthenticatedRequestAsync(req, token) + .ConfigureAwait(false); + } + + public async Task GetUserDialogsAsync(int offsetDate = 0, int offsetId = 0, TLAbsInputPeer offsetPeer = null, int limit = 100, CancellationToken token = default(CancellationToken)) + { + if (offsetPeer == null) + offsetPeer = new TLInputPeerSelf(); + + var req = new TLRequestGetDialogs() + { + OffsetDate = offsetDate, + OffsetId = offsetId, + OffsetPeer = offsetPeer, + Limit = limit + }; + return await SendAuthenticatedRequestAsync(req, token) + .ConfigureAwait(false); + } + + public async Task SendUploadedPhoto(TLAbsInputPeer peer, TLAbsInputFile file, string caption, CancellationToken token = default(CancellationToken)) + { + return await SendAuthenticatedRequestAsync(new TLRequestSendMedia() + { + RandomId = Helpers.GenerateRandomLong(), + Background = false, + ClearDraft = false, + Media = new TLInputMediaUploadedPhoto() { File = file, Caption = caption }, + Peer = peer + }, token) + .ConfigureAwait(false); + } + + public async Task SendUploadedDocument( + TLAbsInputPeer peer, TLAbsInputFile file, string caption, string mimeType, TLVector attributes, CancellationToken token = default(CancellationToken)) + { + return await SendAuthenticatedRequestAsync(new TLRequestSendMedia() + { + RandomId = Helpers.GenerateRandomLong(), + Background = false, + ClearDraft = false, + Media = new TLInputMediaUploadedDocument() + { + File = file, + Caption = caption, + MimeType = mimeType, + Attributes = attributes + }, + Peer = peer + }, token) + .ConfigureAwait(false); + } + + public async Task GetFile(TLAbsInputFileLocation location, int filePartSize, int offset = 0, CancellationToken token = default(CancellationToken)) + { + TLFile result = await SendAuthenticatedRequestAsync(new TLRequestGetFile + { + Location = location, + Limit = filePartSize, + Offset = offset + }, token) + .ConfigureAwait(false); + return result; + } + + public async Task SendPingAsync(CancellationToken token = default(CancellationToken)) + { + await sender.SendPingAsync(token) + .ConfigureAwait(false); + } + + public async Task GetHistoryAsync(TLAbsInputPeer peer, int offsetId = 0, int offsetDate = 0, int addOffset = 0, int limit = 100, int maxId = 0, int minId = 0, CancellationToken token = default(CancellationToken)) + { + var req = new TLRequestGetHistory() + { + Peer = peer, + OffsetId = offsetId, + OffsetDate = offsetDate, + AddOffset = addOffset, + Limit = limit, + MaxId = maxId, + MinId = minId + }; + return await SendAuthenticatedRequestAsync(req, token) + .ConfigureAwait(false); + } + + /// + /// Serch user or chat. API: contacts.search#11f812d8 q:string limit:int = contacts.Found; + /// + /// User or chat name + /// Max result count + /// + public async Task SearchUserAsync(string q, int limit = 10, CancellationToken token = default(CancellationToken)) + { + var r = new TeleSharp.TL.Contacts.TLRequestSearch + { + Q = q, + Limit = limit + }; + + return await SendAuthenticatedRequestAsync(r, token) + .ConfigureAwait(false); + } + + private void OnUserAuthenticated(TLUser TLUser) + { + session.TLUser = TLUser; + session.SessionExpires = int.MaxValue; + + session.Save(); + } + + public bool IsConnected + { + get + { + if (transport == null) + return false; + return transport.IsConnected; + } + } + + public void Dispose() + { + if (transport != null) + { + transport.Dispose(); + transport = null; + } + } + } }