From 409cf256199ffaf917d740dfc504e5936c2f405e Mon Sep 17 00:00:00 2001 From: Wizou Date: Sun, 5 Dec 2021 07:21:30 +0100 Subject: [PATCH] Fix 444 trying to download from a media_only DC with unnegociated AuthKey --- README.md | 2 +- src/Client.cs | 28 ++++++++++++++++++---------- src/Compat.cs | 31 +++++++++++++++++++------------ src/TL.cs | 2 ++ 4 files changed, 40 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 7ecb0e6..a140c1d 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ static string Config(string what) using var client = new WTelegram.Client(Config); ``` There are other configuration items that are queried to your method but returning `null` let WTelegramClient choose a default adequate value. -Those shown above are the only ones that have no default values and should be provided by your method. +Those shown above are the only ones that have no default values and should be provided by your method. Returning `null` for verification_code or password will show a prompt for console apps, or an error otherwise. Another simple approach is to pass `Environment.GetEnvironmentVariable` as the config callback and define the configuration items as environment variables. diff --git a/src/Client.cs b/src/Client.cs index e3ca2ea..e84cae9 100644 --- a/src/Client.cs +++ b/src/Client.cs @@ -1,6 +1,7 @@ using System; using System.Buffers.Binary; using System.Collections.Generic; +using System.ComponentModel; using System.Globalization; using System.IO; using System.Linq; @@ -343,6 +344,8 @@ namespace WTelegram if (dcSession.Client != null || dcSession.DataCenter.flags == flags) return dcSession; // if we have already a session with this DC and we are connected or it is a perfect match, use it // try to find the most appropriate DcOption for this DC + if ((dcSession?.AuthKeyID ?? 0) == 0) // we will need to negociate an AuthKey => can't use media_only DC + flags &= ~DcOption.Flags.media_only; var dcOptions = _session.DcOptions.Where(dc => dc.id == dcId).OrderBy(dc => dc.flags ^ flags); var dcOption = dcOptions.FirstOrDefault() ?? throw new ApplicationException($"Could not find adequate dc_option for DC {dcId}"); dcSession ??= new Session.DCSession { Id = Helpers.RandomLong() }; // create new session only if not already existing @@ -960,11 +963,7 @@ namespace WTelegram _session.UserId = _dcSession.UserId = 0; } var authorization = await this.Auth_ImportBotAuthorization(0, _apiId, _apiHash, botToken); - if (authorization is not Auth_Authorization { user: User user }) - throw new ApplicationException("Failed to get Authorization: " + authorization.GetType().Name); - _session.UserId = _dcSession.UserId = user.id; - _session.Save(); - return user; + return LoginAlreadyDone(authorization); } /// Login as a user (if not already logged-in). @@ -1010,24 +1009,21 @@ namespace WTelegram sentCode = await this.Auth_SendCode(phone_number, _apiId, _apiHash, settings ??= new()); } Helpers.Log(3, $"A verification code has been sent via {sentCode.type.GetType().Name[17..]}"); - Auth_AuthorizationBase authorization; - for (int retry = 1; ; retry++) + Auth_AuthorizationBase authorization = null; + for (int retry = 1; authorization == null; retry++) try { var verification_code = Config("verification_code"); authorization = await this.Auth_SignIn(phone_number, sentCode.phone_code_hash, verification_code); - break; } catch (RpcException e) when (e.Code == 401 && e.Message == "SESSION_PASSWORD_NEEDED") { var accountPassword = await this.Account_GetPassword(); var checkPasswordSRP = Check2FA(accountPassword, () => Config("password")); authorization = await this.Auth_CheckPassword(checkPasswordSRP); - break; } catch (RpcException e) when (e.Code == 400 && e.Message == "PHONE_CODE_INVALID" && retry != 3) { - continue; } if (authorization is Auth_AuthorizationSignUpRequired signUpRequired) { @@ -1040,6 +1036,18 @@ namespace WTelegram if (wait > TimeSpan.Zero) await Task.Delay(wait); // we get a FLOOD_WAIT_3 if we SignUp too fast authorization = await this.Auth_SignUp(phone_number, sentCode.phone_code_hash, first_name, last_name); } + return LoginAlreadyDone(authorization); + } + + /// [Not recommended] You can use this if you have already obtained a login authorization manually + /// if this was not a successful Auth_Authorization, an exception is thrown + /// the User that was authorized + /// This approach is not recommended because you likely didn't properly handle all aspects of the login process + ///
(transient failures, unnecessary login, 2FA, sign-up required, slowness to respond, verification code resending, encryption key safety, etc..) + ///
Methods LoginUserIfNeeded and LoginBotIfNeeded handle these automatically for you
+ [EditorBrowsable(EditorBrowsableState.Never)] + public User LoginAlreadyDone(Auth_AuthorizationBase authorization) + { if (authorization is not Auth_Authorization { user: User user }) throw new ApplicationException("Failed to get Authorization: " + authorization.GetType().Name); _session.UserId = _dcSession.UserId = user.id; diff --git a/src/Compat.cs b/src/Compat.cs index 818bb4e..6aa4d0a 100644 --- a/src/Compat.cs +++ b/src/Compat.cs @@ -6,14 +6,20 @@ using System.Net; using System.Numerics; using System.Security.Cryptography; +#if NETCOREAPP2_1_OR_GREATER namespace WTelegram { static class Compat { -#if NETCOREAPP2_1_OR_GREATER - internal static IPEndPoint IPEndPoint_Parse(string addr) => IPEndPoint.Parse(addr); internal static BigInteger BigEndianInteger(byte[] value) => new(value, true, true); + internal static IPEndPoint IPEndPoint_Parse(string addr) => IPEndPoint.Parse(addr); + } +} #else +namespace WTelegram +{ + static class Compat + { internal static BigInteger BigEndianInteger(byte[] value) { var data = new byte[value.Length + 1]; @@ -68,20 +74,21 @@ namespace WTelegram rsa.ImportParameters(new RSAParameters { Modulus = m.ToArray(), Exponent = e.ToArray() }); } } +} - static class Convert +static class Convert +{ + internal static string ToHexString(byte[] data) => BitConverter.ToString(data).Replace("-", ""); + internal static byte[] FromHexString(string hex) { - internal static byte[] FromHexString(string hex) - { - int NumberChars = hex.Length; - byte[] bytes = new byte[NumberChars / 2]; - for (int i = 0; i < NumberChars; i += 2) - bytes[i / 2] = System.Convert.ToByte(hex.Substring(i, 2), 16); - return bytes; - } -#endif + int NumberChars = hex.Length; + byte[] bytes = new byte[NumberChars / 2]; + for (int i = 0; i < NumberChars; i += 2) + bytes[i / 2] = System.Convert.ToByte(hex.Substring(i, 2), 16); + return bytes; } } +#endif #if NETSTANDARD2_0 namespace System.Runtime.CompilerServices diff --git a/src/TL.cs b/src/TL.cs index 6e5f70e..2d49f30 100644 --- a/src/TL.cs +++ b/src/TL.cs @@ -336,6 +336,7 @@ namespace TL 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 override string ToString() => Convert.ToHexString(raw); public static implicit operator byte[](Int128 int128) => int128.raw; } @@ -349,6 +350,7 @@ namespace TL 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 override string ToString() => Convert.ToHexString(raw); public static implicit operator byte[](Int256 int256) => int256.raw; }