using System; using System.Collections.Generic; using System.Linq; using System.Security.Cryptography; using System.Text; using System.Threading; using System.Threading.Tasks; using TeleSharp.TL; using TeleSharp.TL.Account; using TeleSharp.TL.Auth; using TeleSharp.TL.Channels; using TeleSharp.TL.Contacts; using TeleSharp.TL.Help; using TeleSharp.TL.Messages; using TeleSharp.TL.Upload; using TLSharp.Core.Auth; using TLSharp.Core.Exceptions; using TLSharp.Core.MTProto.Crypto; using TLSharp.Core.Network; using TLSharp.Core.Network.Exceptions; using TLSharp.Core.Types; using TLSharp.Core.Utils; using TLAuthorization = TeleSharp.TL.Auth.TLAuthorization; using TLRequestUpdateUsername = TeleSharp.TL.Account.TLRequestUpdateUsername; namespace TLSharp.Core { public class TelegramClient : IDisposable { /// /// When pagination is needed, this is the default page size /// public const int DEFAULT_PAGE_SIZE = 200; 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)); 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 TeleSharp.TL.Account.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); } /// /// Authenticates a Bot /// /// The token of the bot to authenticate /// /// The TLUser descriptor public async Task MakeAuthBotAsync(string botAuthToken, CancellationToken token = default(CancellationToken)) { if (String.IsNullOrWhiteSpace(botAuthToken)) { throw new ArgumentNullException(nameof(botAuthToken)); } var request = new TLRequestImportBotAuthorization() { BotAuthToken = botAuthToken, ApiId = apiId, ApiHash = apiHash }; await RequestWithDcMigration(request, token).ConfigureAwait(false); OnUserAuthenticated(((TLUser)request.Response.User)); return ((TLUser)request.Response.User); } /// /// Gets the full information of a specified chat /// /// The ID of the chat we want the info of /// /// public async Task GetFullChat(int chatId, CancellationToken token = default(CancellationToken)) { var req = new TLRequestGetFullChat() { ChatId = chatId }; var fchat = await SendRequestAsync(req, token).ConfigureAwait(false); return fchat; } /// /// Gets the list of chats and channels opened by the authenticated user. /// Throws an exception if the authenticated user is a bot. /// /// /// The list of chats opened by the authenticated user public async Task GetAllChats(CancellationToken token = default(CancellationToken)) { return await GetAllChats(null, token); } /// /// Gets the list of chats and channels opened by the authenticated user except the passed ones. /// Throws an exception if the authenticated user is a bot. /// /// The IDs of the chats to be returned /// /// The list of chats opened by the authenticated user public async Task GetAllChats(int[] exceptdIds, CancellationToken token = default(CancellationToken)) { var ichats = new TeleSharp.TL.TLVector(); // we can't pass a null argument to the TLRequestGetChats if (exceptdIds != null) Array.ForEach(exceptdIds, x => ichats.Add(x)); var chats = await SendRequestAsync(new TLRequestGetAllChats() { ExceptIds = ichats }, token).ConfigureAwait(false); return chats; } /// /// Gets the information about a channel /// /// The channel to get the info of /// /// public async Task GetFullChannel(TLChannel channel, CancellationToken token = default(CancellationToken)) { if (channel == null) return null; return await GetFullChannel(channel.Id, (long)channel.AccessHash, token).ConfigureAwait(false); } /// /// Gets the information about a channel /// /// The ID of the channel /// /// public async Task GetFullChannel(int channelId, long accessHash, CancellationToken token = default(CancellationToken)) { var req = new TLRequestGetFullChannel() { Channel = new TLInputChannel() { ChannelId = channelId, AccessHash = accessHash } }; var fchat = await SendRequestAsync(req, token).ConfigureAwait(false); return fchat; } /// /// Gets the channels having the supplied IDs /// /// The IDs of the channels to be retrieved /// /// public async Task GetChannels(int[] channelIds, CancellationToken token = default(CancellationToken)) { var channels = new TLVector(); // we can't pass a null argument to the TLRequestGetChats if (channelIds != null) Array.ForEach(channelIds, x => channels.Add(new TLInputChannel() { ChannelId = x })); var req = new TLRequestGetChannels() { Id = channels }; var fchat = await SendRequestAsync(req, token).ConfigureAwait(false); return fchat; } /// /// Gets the participants of the channel having the supplied type. /// The method will auto-paginate results and return all the participants /// /// The TLChannel whose participants are requested /// The index to start fetching from. -1 will automatically fetch all the results /// How many results to be fetch on each iteration. /// Values smaller than 0 are ignored. If stIdx is set, a number of results smaller than pageSize might be returned by Telegram. /// The type of the participants to get. Choose Recents not to filter /// /// public async Task GetParticipants(TLChannel channel, int stIdx = -1, int pageSize = -1, ParticipantFilterTypes partType = ParticipantFilterTypes.Recents, CancellationToken token = default(CancellationToken)) { if (channel == null) return null; return await GetParticipants(channel.Id, (long)channel.AccessHash, stIdx, pageSize, partType, token).ConfigureAwait(false); } /// /// Gets the participants of the channel having the supplied type. /// The method will auto-paginate results and return all the participants /// /// The id of the channel whose participants are requested /// The access hash of the channel whose participants are requested /// The index to start fetching from. -1 will automatically fetch all the results /// How many results to be fetch on each iteration. /// Values smaller than 0 are ignored. If stIdx is set, a number of results smaller than pageSize might be returned by Telegram. /// The type of the participants to get. Choose Recents not to filter /// /// public async Task GetParticipants(int channelId, long accessHash, int stIdx = -1, int pageSize = -1, ParticipantFilterTypes partType = ParticipantFilterTypes.Recents, CancellationToken token = default(CancellationToken)) { TLAbsChannelParticipantsFilter filter; switch (partType) { case ParticipantFilterTypes.Admins: filter = new TLChannelParticipantsAdmins(); break; case ParticipantFilterTypes.Kicked: filter = new TLChannelParticipantsKicked(); break; case ParticipantFilterTypes.Bots: filter = new TLChannelParticipantsBots(); break; case ParticipantFilterTypes.Recents: filter = new TLChannelParticipantsRecent(); break; case ParticipantFilterTypes.Banned: case ParticipantFilterTypes.Restricted: case ParticipantFilterTypes.Contacts: case ParticipantFilterTypes.Search: default: throw new NotImplementedException($"{partType} not implemented yet"); } int total = 0; int found = stIdx < 0 ? 0 : stIdx; pageSize = pageSize < 0 ? DEFAULT_PAGE_SIZE : pageSize; List results = new List(); TLChannelParticipants ret = new TLChannelParticipants(); ret.Participants = new TLVector(); ret.Users = new TLVector(); do { var req = new TLRequestGetParticipants() { Channel = new TLInputChannel() { ChannelId = channelId, AccessHash = accessHash }, Filter = filter, Offset = found, Limit = pageSize }; var fchat = await SendRequestAsync(req, token).ConfigureAwait(false); total = fchat.Count; found += fchat.Participants.Count; results.Add(fchat); foreach (var p in fchat.Participants) ret.Participants.Add(p); foreach (var u in fchat.Users) ret.Users.Add(u); } while (found < total && stIdx == -1); ret.Count = ret.Participants.Count; return ret; } /// /// Invites the passed users to the specified channel /// /// The descriptor of the channel to invite the users to /// /// public async Task InviteToChannel(TLChannel channel, int[] users, CancellationToken token = default(CancellationToken)) { return await InviteToChannel(channel.Id, (long)channel.AccessHash, users, token); } /// /// Invites the passed users to the specified channel /// /// The id of the channel to invite the users to /// The access hash of the channel to invite the users to /// /// public async Task InviteToChannel(int channelId, long accessHash, int[] users, CancellationToken token = default(CancellationToken)) { TLVector absUsers = new TLVector(); Array.ForEach(users, u => absUsers.Add(new TLInputUser() { UserId = u })); var req = new TLRequestInviteToChannel() { Channel = new TLInputChannel() { ChannelId = channelId, AccessHash = accessHash }, Users = absUsers }; var updates = await SendRequestAsync(req, token).ConfigureAwait(false); return updates; } /// /// 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; } } } }