diff --git a/src/TgSharp.Core/TelegramClient.cs b/src/TgSharp.Core/TelegramClient.cs index 953a6cec..84846969 100644 --- a/src/TgSharp.Core/TelegramClient.cs +++ b/src/TgSharp.Core/TelegramClient.cs @@ -8,6 +8,7 @@ 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; @@ -17,6 +18,7 @@ using TgSharp.Core.MTProto.Crypto; using TgSharp.Core.Network; using TgSharp.Core.Network.Exceptions; +using TgSharp.Core.Types; using TgSharp.Core.Utils; using TLAuthorization = TeleSharp.TL.Auth.TLAuthorization; @@ -24,6 +26,11 @@ namespace TgSharp.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; @@ -31,7 +38,7 @@ public class TelegramClient : IDisposable private Session session; private List dcOptions; private TcpClientConnectionHandler handler; - private DataCenterIPVersion dcIpVersion; + private DataCenterIPVersions dcIpVersion; public Session Session { @@ -49,7 +56,7 @@ public Session Session /// 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) + DataCenterIPVersions dcIpVersion = DataCenterIPVersions.Default) { if (apiId == default(int)) throw new MissingApiConfigurationException("API_ID"); @@ -114,20 +121,20 @@ public TelegramClient(int apiId, string apiHash, } IEnumerable dcs; - if (dcIpVersion == DataCenterIPVersion.OnlyIPv6) + if (dcIpVersion == DataCenterIPVersions.OnlyIPv6) dcs = dcOptions.Where(d => d.Id == dcId && d.Ipv6); // selects only ipv6 addresses - else if (dcIpVersion == DataCenterIPVersion.OnlyIPv4) + else if (dcIpVersion == DataCenterIPVersions.OnlyIPv4) dcs = dcOptions.Where(d => d.Id == dcId && !d.Ipv6); // selects only ipv4 addresses else dcs = dcOptions.Where(d => d.Id == dcId); // any TLDcOption dc; - if (dcIpVersion != DataCenterIPVersion.Default) + if (dcIpVersion != DataCenterIPVersions.Default) { if (!dcs.Any()) 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."); dcs = dcs.OrderBy(d => d.Ipv6); - dc = dcIpVersion == DataCenterIPVersion.PreferIPv4 ? dcs.First() : dcs.Last(); // ipv4 addresses are at the beginning of the list because it was ordered + dc = dcIpVersion == DataCenterIPVersions.PreferIPv4 ? dcs.First() : dcs.Last(); // ipv4 addresses are at the beginning of the list because it was ordered } else dc = dcs.First(); @@ -284,7 +291,7 @@ public bool IsUserAuthorized() public async Task UpdateUsernameAsync(string username, CancellationToken token = default(CancellationToken)) { - var req = new TLRequestUpdateUsername { Username = username }; + var req = new TeleSharp.TL.Account.TLRequestUpdateUsername { Username = username }; return await SendAuthenticatedRequestAsync(req, token) .ConfigureAwait(false); @@ -292,7 +299,7 @@ public bool IsUserAuthorized() public async Task CheckUsernameAsync(string username, CancellationToken token = default(CancellationToken)) { - var req = new TLRequestCheckUsername { Username = username }; + var req = new TeleSharp.TL.Account.TLRequestCheckUsername { Username = username }; return await SendAuthenticatedRequestAsync(req, token) .ConfigureAwait(false); @@ -454,6 +461,306 @@ await sender.SendPingAsync(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)((TLAuthorization)request.Response).User)); + return ((TLUser)((TLAuthorization)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) + foreach (var id in exceptdIds) + ichats.Add(id); + var chats = await SendRequestAsync(new TLRequestGetAllChats() { ExceptIds = ichats }, token).ConfigureAwait(false); + return chats as TLChats; + } + + /// + /// 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) + foreach (var channelId in channelIds) + channels.Add(new TLInputChannel() { ChannelId = channelId }); + 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: + // filter = new TLChannelParticipantsBanned(); + // break; + + //// case ParticipantFilterTypes.Restricted: + //// filter = new tlchannelparticipants + + //case ParticipantFilterTypes.Contacts: + // filter = new TLChannelParticipantsContacts(); + // break; + + //case ParticipantFilterTypes.Search: + // filter = new TLChannelParticipantsSearch(); + // break; + + default: + throw new NotImplementedException($"{partType} not implemented yet"); + } + + int total = 0; + int found = stIdx < 0 ? 0 : stIdx; + pageSize = pageSize < 0 ? DEFAULT_PAGE_SIZE : pageSize; + + 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; + 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(); + foreach (var user in users) + absUsers.Add(new TLInputUser() { UserId = user }); + + var req = new TLRequestInviteToChannel() + { + Channel = new TLInputChannel() + { + ChannelId = channelId, + AccessHash = accessHash + }, + Users = absUsers + }; + var updates = await SendRequestAsync(req, token).ConfigureAwait(false); + + return updates; + } + + /// + /// Joins a chat invite and returns the updates + /// This method can't be used by a bot. + /// For a list of possible errors + /// + /// hash from t.me/joinchat/hash + /// The updates + public async Task ImportChatInvite(string hash, CancellationToken token = default(CancellationToken)) + { + TLRequestImportChatInvite req = new TLRequestImportChatInvite() + { + Hash = hash + }; + var updates = await SendRequestAsync(req, token).ConfigureAwait(false); + return updates; + } + + /// + /// Resolves a given Telegram username to the corresponding object. A username can be a person's username or a group or a channel name. + /// + /// The telegram @username to resolve, without the @. It can be a person's username or a group or a channel name. + /// + /// + public async Task ResolveUsername(string username, CancellationToken token = default(CancellationToken)) + { + if (string.IsNullOrWhiteSpace(username)) return null; + + TLRequestResolveUsername req = new TLRequestResolveUsername() + { + Username = username + }; + var ret = await SendRequestAsync(req, token).ConfigureAwait(false); + return ret; + } + + /// + /// Checks whether or not the authenticated user has joined the chat already + /// + /// Invite hash in t.me/joinchat/hash + /// + /// + public async Task CheckChatInvite(string hash, CancellationToken token = default(CancellationToken)) + { + if (string.IsNullOrWhiteSpace(hash)) return null; + + TLRequestCheckChatInvite req = new TLRequestCheckChatInvite() + { + Hash = hash + }; + var ret = await SendAuthenticatedRequestAsync(req, token).ConfigureAwait(false); + return ret; + } + private void OnUserAuthenticated(TLUser TLUser) { session.TLUser = TLUser; @@ -466,7 +773,7 @@ public bool IsConnected { get { - if (transport == null) + if (transport == null || sender == null) return false; return transport.IsConnected; } diff --git a/src/TgSharp.Core/TgSharp.Core.csproj b/src/TgSharp.Core/TgSharp.Core.csproj index da7dd3f0..6c1ab0b2 100644 --- a/src/TgSharp.Core/TgSharp.Core.csproj +++ b/src/TgSharp.Core/TgSharp.Core.csproj @@ -45,7 +45,7 @@ - + @@ -71,11 +71,12 @@ + + - diff --git a/src/TgSharp.Core/DataCenterIPVersion.cs b/src/TgSharp.Core/Types/DataCenterIPVersions.cs similarity index 92% rename from src/TgSharp.Core/DataCenterIPVersion.cs rename to src/TgSharp.Core/Types/DataCenterIPVersions.cs index 4676ee11..cf21765d 100644 --- a/src/TgSharp.Core/DataCenterIPVersion.cs +++ b/src/TgSharp.Core/Types/DataCenterIPVersions.cs @@ -1,10 +1,10 @@ -namespace TgSharp.Core +namespace TgSharp.Core.Types { /// /// 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 + public enum DataCenterIPVersions { /// /// Picks the first available address passed by Telegram @@ -27,4 +27,6 @@ public enum DataCenterIPVersion /// PreferIPv6, } + + } diff --git a/src/TgSharp.Core/Types/ParticipantFilterTypes.cs b/src/TgSharp.Core/Types/ParticipantFilterTypes.cs new file mode 100644 index 00000000..b14c6f7a --- /dev/null +++ b/src/TgSharp.Core/Types/ParticipantFilterTypes.cs @@ -0,0 +1,16 @@ +namespace TgSharp.Core.Types +{ + public enum ParticipantFilterTypes + { + Recents, + Restricted, + Admins, + Bots, + Search, + Contacts, + Kicked, + Banned + } + + +}