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 { + /// <summary> + /// When pagination is needed, this is the default page size + /// </summary> + 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<TLDcOption> dcOptions; private TcpClientConnectionHandler handler; - private DataCenterIPVersion dcIpVersion; + private DataCenterIPVersions dcIpVersion; public Session Session { @@ -49,7 +56,7 @@ public Session Session /// <param name="dcIpVersion">Indicates the preferred IpAddress version to use to connect to a Telegram server</param> 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<TLDcOption> 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<TLUser> 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<TLUser>(req, token) .ConfigureAwait(false); @@ -292,7 +299,7 @@ public bool IsUserAuthorized() public async Task<bool> 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<bool>(req, token) .ConfigureAwait(false); @@ -454,6 +461,306 @@ await sender.SendPingAsync(token) .ConfigureAwait(false); } + /// <summary> + /// Authenticates a Bot + /// </summary> + /// <param name="botAuthToken">The token of the bot to authenticate</param> + /// <param name="token"></param> + /// <returns>The TLUser descriptor</returns> + public async Task<TLUser> 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); + } + + /// <summary> + /// Gets the full information of a specified chat + /// </summary> + /// <param name="chatId">The ID of the chat we want the info of</param> + /// <param name="token"></param> + /// <returns></returns> + public async Task<TeleSharp.TL.Messages.TLChatFull> GetFullChat(int chatId, CancellationToken token = default(CancellationToken)) + { + var req = new TLRequestGetFullChat() { ChatId = chatId }; + var fchat = await SendRequestAsync<TeleSharp.TL.Messages.TLChatFull>(req, token).ConfigureAwait(false); + + return fchat; + } + + /// <summary> + /// Gets the list of chats and channels opened by the authenticated user. + /// Throws an exception if the authenticated user is a bot. + /// </summary> + /// <param name="token"></param> + /// <returns>The list of chats opened by the authenticated user</returns> + public async Task<TLChats> GetAllChats(CancellationToken token = default(CancellationToken)) + { + return await GetAllChats(null, token); + } + + /// <summary> + /// 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. + /// </summary> + /// <param name="ids">The IDs of the chats to be returned</param> + /// <param name="token"></param> + /// <returns>The list of chats opened by the authenticated user</returns> + public async Task<TLChats> GetAllChats(int[] exceptdIds, CancellationToken token = default(CancellationToken)) + { + var ichats = new TeleSharp.TL.TLVector<int>(); // 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<TLChats>(new TLRequestGetAllChats() { ExceptIds = ichats }, token).ConfigureAwait(false); + return chats as TLChats; + } + + /// <summary> + /// Gets the information about a channel + /// </summary> + /// <param name="channel">The channel to get the info of</param> + /// <param name="token"></param> + /// <returns></returns> + public async Task<TeleSharp.TL.Messages.TLChatFull> GetFullChannel(TLChannel channel, CancellationToken token = default(CancellationToken)) + { + if (channel == null) return null; + return await GetFullChannel(channel.Id, (long)channel.AccessHash, token).ConfigureAwait(false); + } + + /// <summary> + /// Gets the information about a channel + /// </summary> + /// <param name="channelId">The ID of the channel</param> + /// <param name="token"></param> + /// <returns></returns> + public async Task<TeleSharp.TL.Messages.TLChatFull> GetFullChannel(int channelId, long accessHash, CancellationToken token = default(CancellationToken)) + { + var req = new TLRequestGetFullChannel() { Channel = new TLInputChannel() { ChannelId = channelId, AccessHash = accessHash } }; + var fchat = await SendRequestAsync<TeleSharp.TL.Messages.TLChatFull>(req, token).ConfigureAwait(false); + + return fchat; + } + + /// <summary> + /// Gets the channels having the supplied IDs + /// </summary> + /// <param name="channelIds">The IDs of the channels to be retrieved</param> + /// <param name="token"></param> + /// <returns></returns> + public async Task<TeleSharp.TL.Messages.TLChats> GetChannels(int[] channelIds, CancellationToken token = default(CancellationToken)) + { + var channels = new TLVector<TeleSharp.TL.TLAbsInputChannel>(); // 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<TeleSharp.TL.Messages.TLChats>(req, token).ConfigureAwait(false); + + return fchat; + } + + /// <summary> + /// Gets the participants of the channel having the supplied type. + /// The method will auto-paginate results and return all the participants + /// </summary> + /// <param name="channel">The TLChannel whose participants are requested</param> + /// <param name="stIdx">The index to start fetching from. -1 will automatically fetch all the results</param> + /// <param name="pageSize">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.</param> + /// <param name="partType">The type of the participants to get. Choose Recents not to filter</param> + /// <param name="token"></param> + /// <returns></returns> + public async Task<TLChannelParticipants> 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); + } + + /// <summary> + /// Gets the participants of the channel having the supplied type. + /// The method will auto-paginate results and return all the participants + /// </summary> + /// <param name="channelId">The id of the channel whose participants are requested</param> + /// <param name="accessHash">The access hash of the channel whose participants are requested</param> + /// <param name="stIdx">The index to start fetching from. -1 will automatically fetch all the results</param> + /// <param name="pageSize">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.</param> + /// <param name="partType">The type of the participants to get. Choose Recents not to filter</param> + /// <param name="token"></param> + /// <returns></returns> + public async Task<TLChannelParticipants> 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<TLAbsChannelParticipant>(); + ret.Users = new TLVector<TLAbsUser>(); + + do + { + var req = new TLRequestGetParticipants() + { + Channel = new TLInputChannel() + { + ChannelId = channelId, + AccessHash = accessHash + }, + Filter = filter, + Offset = found, + Limit = pageSize + }; + var fchat = await SendRequestAsync<TLChannelParticipants>(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; + } + + /// <summary> + /// Invites the passed users to the specified channel + /// </summary> + /// <param name="channel">The descriptor of the channel to invite the users to</param> + /// <param name="users"></param> + /// <returns></returns> + public async Task<TLUpdates> InviteToChannel(TLChannel channel, int[] users, CancellationToken token = default(CancellationToken)) + { + return await InviteToChannel(channel.Id, (long)channel.AccessHash, users, token); + } + + /// <summary> + /// Invites the passed users to the specified channel + /// </summary> + /// <param name="channelId">The id of the channel to invite the users to</param> + /// <param name="accessHash">The access hash of the channel to invite the users to</param> + /// <param name="users"></param> + /// <returns></returns> + public async Task<TLUpdates> InviteToChannel(int channelId, long accessHash, int[] users, CancellationToken token = default(CancellationToken)) + { + TLVector<TLAbsInputUser> absUsers = new TLVector<TLAbsInputUser>(); + 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<TLUpdates>(req, token).ConfigureAwait(false); + + return updates; + } + + /// <summary> + /// Joins a chat invite and returns the updates + /// This method can't be used by a bot. + /// For a list of possible errors <see cref="https://core.telegram.org/method/messages.importChatInvite"/> + /// </summary> + /// <param name="hash">hash from t.me/joinchat/hash</param> + /// <returns>The updates</returns> + public async Task<TLUpdates> ImportChatInvite(string hash, CancellationToken token = default(CancellationToken)) + { + TLRequestImportChatInvite req = new TLRequestImportChatInvite() + { + Hash = hash + }; + var updates = await SendRequestAsync<TLUpdates>(req, token).ConfigureAwait(false); + return updates; + } + + /// <summary> + /// Resolves a given Telegram username to the corresponding object. A username can be a person's username or a group or a channel name. + /// </summary> + /// <param name="username">The telegram @username to resolve, without the @. It can be a person's username or a group or a channel name.</param> + /// <param name="token"></param> + /// <returns></returns> + public async Task<TLResolvedPeer> ResolveUsername(string username, CancellationToken token = default(CancellationToken)) + { + if (string.IsNullOrWhiteSpace(username)) return null; + + TLRequestResolveUsername req = new TLRequestResolveUsername() + { + Username = username + }; + var ret = await SendRequestAsync<TLResolvedPeer>(req, token).ConfigureAwait(false); + return ret; + } + + /// <summary> + /// Checks whether or not the authenticated user has joined the chat already + /// </summary> + /// <param name="hash">Invite hash in t.me/joinchat/hash</param> + /// <param name="token"></param> + /// <returns></returns> + public async Task<TLAbsChatInvite> CheckChatInvite(string hash, CancellationToken token = default(CancellationToken)) + { + if (string.IsNullOrWhiteSpace(hash)) return null; + + TLRequestCheckChatInvite req = new TLRequestCheckChatInvite() + { + Hash = hash + }; + var ret = await SendAuthenticatedRequestAsync<TLAbsChatInvite>(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 @@ <Compile Include="Auth\Step1_PQRequest.cs" /> <Compile Include="Auth\Step2_DHExchange.cs" /> <Compile Include="Auth\Step3_CompleteDHExchange.cs" /> - <Compile Include="DataCenterIPVersion.cs" /> + <Compile Include="DataCenter.cs" /> <Compile Include="Exceptions\CloudPasswordNeededException.cs" /> <Compile Include="Exceptions\InvalidPhoneCodeException.cs" /> <Compile Include="Exceptions\MissingApiConfigurationException.cs" /> @@ -71,11 +71,12 @@ <Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Network\Requests\AckRequest.cs" /> <Compile Include="Network\Requests\PingRequest.cs" /> + <Compile Include="Types\DataCenterIPVersions.cs" /> + <Compile Include="Types\ParticipantFilterTypes.cs" /> <Compile Include="Utils\UploadHelper.cs" /> <Compile Include="Session.cs" /> <Compile Include="TelegramClient.cs" /> <Compile Include="Utils\Helpers.cs" /> - <Compile Include="DataCenter.cs" /> </ItemGroup> <ItemGroup> <None Include="packages.config" /> 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 { /// <summary> /// 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 /// </summary> - public enum DataCenterIPVersion + public enum DataCenterIPVersions { /// <summary> /// Picks the first available address passed by Telegram @@ -27,4 +27,6 @@ public enum DataCenterIPVersion /// </summary> 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 + } + + +}