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);