From 3c19be32c761988486ee1535d93975d2841d1662 Mon Sep 17 00:00:00 2001 From: Wizou <11647984+wiz0u@users.noreply.github.com> Date: Sat, 27 Apr 2024 12:34:32 +0200 Subject: [PATCH] New method LoginWithQRCode (fix #247) --- .github/dev.yml | 2 +- .github/release.yml | 2 +- src/Client.cs | 116 +++++++++++++++++++++++++++++++++++--------- 3 files changed, 95 insertions(+), 25 deletions(-) diff --git a/.github/dev.yml b/.github/dev.yml index 611e374..b418590 100644 --- a/.github/dev.yml +++ b/.github/dev.yml @@ -1,7 +1,7 @@ pr: none trigger: [ master ] -name: 4.0.1-dev.$(Rev:r) +name: 4.1.1-dev.$(Rev:r) pool: vmImage: ubuntu-latest diff --git a/.github/release.yml b/.github/release.yml index 4c8040d..f079d8e 100644 --- a/.github/release.yml +++ b/.github/release.yml @@ -1,7 +1,7 @@ pr: none trigger: none -name: 4.0.$(Rev:r) +name: 4.1.$(Rev:r) pool: vmImage: ubuntu-latest diff --git a/src/Client.cs b/src/Client.cs index c620140..6572d5c 100644 --- a/src/Client.cs +++ b/src/Client.cs @@ -1203,19 +1203,7 @@ namespace WTelegram } catch (RpcException e) when (e.Code == 401 && e.Message == "SESSION_PASSWORD_NEEDED") { - for (int pwdRetry = 1; authorization == null; pwdRetry++) - try - { - var accountPassword = await this.Account_GetPassword(); - RaiseUpdates(accountPassword); - var checkPasswordSRP = await Check2FA(accountPassword, () => ConfigAsync("password")); - authorization = await this.Auth_CheckPassword(checkPasswordSRP); - } - catch (RpcException pe) when (pe.Code == 400 && pe.Message == "PASSWORD_HASH_INVALID") - { - Helpers.Log(4, "Wrong password!"); - if (pwdRetry >= MaxCodePwdAttempts) throw; - } + authorization = await LoginPasswordNeeded(); } } @@ -1248,6 +1236,83 @@ namespace WTelegram return User; } + /// Login via QR code + /// Callback to display the login url as QR code to the user (QRCoder library can help you) + /// (optional) To prevent logging as these user ids + /// If session is already connected to a user, this method will first log out.
You can also check property before calling this method. + /// If you need to abort the method before login is completed + /// Detail about the logged-in user + public async Task LoginWithQRCode(Action qrDisplay, long[] except_ids = null, bool logoutFirst = true, CancellationToken ct = default) + { + await ConnectAsync(); + if (logoutFirst && _session.UserId != 0) // a user is already logged-in + { + await this.Auth_LogOut(); + _session.UserId = _dcSession.UserId = 0; + User = null; + } + var tcs = new TaskCompletionSource(); + OnUpdates += CatchQRUpdate; + try + { + while (!ct.IsCancellationRequested) + { + var ltb = await this.Auth_ExportLoginToken(_session.ApiId, _apiHash ??= Config("api_hash"), except_ids); + retry: + switch (ltb) + { + case Auth_LoginToken lt: + var url = "tg://login?token=" + System.Convert.ToBase64String(lt.token).Replace('/', '_').Replace('+', '-'); + Helpers.Log(3, $"Waiting for this QR code login to be accepted: " + url); + qrDisplay(url); + if (lt.expires - DateTime.UtcNow is { Ticks: >= 0 } delay) + await Task.WhenAny(Task.Delay(delay, ct), tcs.Task); + break; + case Auth_LoginTokenMigrateTo ltmt: + await MigrateToDC(ltmt.dc_id); + ltb = await this.Auth_ImportLoginToken(ltmt.token); + goto retry; + case Auth_LoginTokenSuccess lts: + return LoginAlreadyDone(lts.authorization); + } + } + ct.ThrowIfCancellationRequested(); + return null; + } + catch (RpcException e) when (e.Code == 401 && e.Message == "SESSION_PASSWORD_NEEDED") + { + return LoginAlreadyDone(await LoginPasswordNeeded()); + } + finally + { + OnUpdates -= CatchQRUpdate; + } + + Task CatchQRUpdate(UpdatesBase updates) + { + if (updates.UpdateList.OfType().Any()) + tcs.TrySetResult(true); + return Task.CompletedTask; + } + } + + private async Task LoginPasswordNeeded() + { + for (int pwdRetry = 1; ; pwdRetry++) + try + { + var accountPassword = await this.Account_GetPassword(); + RaiseUpdates(accountPassword); + var checkPasswordSRP = await Check2FA(accountPassword, () => ConfigAsync("password")); + return await this.Auth_CheckPassword(checkPasswordSRP); + } + catch (RpcException pe) when (pe.Code == 400 && pe.Message == "PASSWORD_HASH_INVALID") + { + Helpers.Log(4, "Wrong password!"); + if (pwdRetry >= MaxCodePwdAttempts) throw; + } + } + /// [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 @@ -1418,16 +1483,7 @@ namespace WTelegram { if (message != "FILE_MIGRATE_X") { - // this is a hack to migrate _dcSession in-place (staying in same Client): - Session.DCSession dcSession; - lock (_session) - dcSession = GetOrCreateDCSession(x, _dcSession.DataCenter.flags); - Reset(false, false); - _session.MainDC = x; - _dcSession.Client = null; - _dcSession = dcSession; - _dcSession.Client = this; - await ConnectAsync(); + await MigrateToDC(x); goto retry; } } @@ -1459,6 +1515,20 @@ namespace WTelegram } } + private async Task MigrateToDC(int dcId) + { + // this is a hack to migrate _dcSession in-place (staying in same Client): + Session.DCSession dcSession; + lock (_session) + dcSession = GetOrCreateDCSession(dcId, _dcSession.DataCenter.flags); + Reset(false, false); + _session.MainDC = dcId; + _dcSession.Client = null; + _dcSession = dcSession; + _dcSession.Client = this; + await ConnectAsync(); + } + public async Task InvokeAffected(IMethod query, long peerId) where T : Messages_AffectedMessages { var result = await Invoke(query);