Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0bbaac5689 |
@@ -26,8 +26,8 @@ public class YTrackAPI : YCommonAPI
|
|||||||
return $"https://{host}/get-{codec}/{sign}/{ts}{path}";
|
return $"https://{host}/get-{codec}/{sign}/{ts}{path}";
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<YTrack?> GetAsync(string trackId)
|
public async Task<YTrack?> GetAsync(string trackId)
|
||||||
=> GetAsync(trackId);
|
=> (await GetAsync([trackId]))?.FirstOrDefault();
|
||||||
|
|
||||||
public Task<List<YTrack>?> GetAsync(IEnumerable<string> trackIds)
|
public Task<List<YTrack>?> GetAsync(IEnumerable<string> trackIds)
|
||||||
=> new YGetTracksBuilder(Api).ExecuteAsync(trackIds);
|
=> new YGetTracksBuilder(Api).ExecuteAsync(trackIds);
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ public class YUserAPI : YCommonAPI
|
|||||||
if (!csrfMatch.Success || !processMatch.Success)
|
if (!csrfMatch.Success || !processMatch.Success)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
Api.Storage.AuthToken = new YAuthToken
|
Api.Storage.HeaderToken = new YAuthToken
|
||||||
{
|
{
|
||||||
CsfrToken = csrfMatch.Groups[1].Value,
|
CsfrToken = csrfMatch.Groups[1].Value,
|
||||||
ProcessUuid = processMatch.Groups[1].Value
|
ProcessUuid = processMatch.Groups[1].Value
|
||||||
@@ -45,9 +45,7 @@ public class YUserAPI : YCommonAPI
|
|||||||
Api.Storage.AccessToken = accessToken;
|
Api.Storage.AccessToken = accessToken;
|
||||||
Api.Storage.Token = accessToken.AccessToken;
|
Api.Storage.Token = accessToken.AccessToken;
|
||||||
|
|
||||||
var shortInfo = await new YGetShortAccountInfoBuilder(Api).ExecuteAsync(null!);
|
await AuthorizeAsync(accessToken.AccessToken);
|
||||||
if (shortInfo?.Status != YAuthStatus.Ok || string.IsNullOrWhiteSpace(shortInfo.Uid))
|
|
||||||
throw new Exception("Не удалось подтвердить авторизацию");
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -96,13 +94,31 @@ public class YUserAPI : YCommonAPI
|
|||||||
return $"https://passport.yandex.ru/auth/magic/code/?track_id={qr.TrackId}";
|
return $"https://passport.yandex.ru/auth/magic/code/?track_id={qr.TrackId}";
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<YAuthQRStatus?> AuthorizeByQRAsync()
|
public async Task<YAuthQRStatus?> CheckQRStatusAsync()
|
||||||
{
|
{
|
||||||
if (Api.Storage.AuthToken == null)
|
if (Api.Storage.AuthToken == null)
|
||||||
throw new Exception("Сессия не инициализирована");
|
throw new Exception("Сессия не инициализирована");
|
||||||
|
|
||||||
|
var status = await new YPostQrStatus(Api).ExecuteAsync(null!);
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(status?.TrackId))
|
||||||
|
{
|
||||||
|
Api.Storage.AuthToken.SessionTrackId = status.TrackId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<YAuthQRSession?> AuthorizeByQRAsync()
|
||||||
|
{
|
||||||
|
if (Api.Storage.AuthToken == null)
|
||||||
|
throw new Exception("Сессия не инициализирована");
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(Api.Storage.AuthToken.SessionTrackId))
|
||||||
|
throw new Exception("Токен сессии не инициализирован");
|
||||||
|
|
||||||
var status = await new YGetAuthLoginQRBuilder(Api).ExecuteAsync(null!);
|
var status = await new YGetAuthLoginQRBuilder(Api).ExecuteAsync(null!);
|
||||||
if (status?.Status == YAuthStatus.Ok && await LoginByCookiesAsync())
|
if (status != null && status.DefaultUid != 0 && await LoginByCookiesAsync())
|
||||||
return status;
|
return status;
|
||||||
throw new AuthenticationException("Ошибка авторизации по QR");
|
throw new AuthenticationException("Ошибка авторизации по QR");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System.Net;
|
||||||
using YandexMusic.API.Models.Account;
|
using YandexMusic.API.Models.Account;
|
||||||
|
|
||||||
namespace YandexMusic.API.Common;
|
namespace YandexMusic.API.Common;
|
||||||
@@ -7,6 +8,15 @@ namespace YandexMusic.API.Common;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class AuthStorage
|
public class AuthStorage
|
||||||
{
|
{
|
||||||
|
private CookieContainer _cookieContainer;
|
||||||
|
|
||||||
|
public AuthStorage(CookieContainer cookieContainer)
|
||||||
|
{
|
||||||
|
_cookieContainer = cookieContainer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CookieContainer CookieContainer => _cookieContainer;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Флаг, указывающий, авторизован ли пользователь.
|
/// Флаг, указывающий, авторизован ли пользователь.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -32,6 +42,11 @@ public class AuthStorage
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public YAccessToken AccessToken { get; internal set; } = new();
|
public YAccessToken AccessToken { get; internal set; } = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Внутренние данные авторизации (CSRF, track_id и т.д.).
|
||||||
|
/// </summary>
|
||||||
|
public YAuthToken HeaderToken { get; set; } = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Внутренние данные авторизации (CSRF, track_id и т.д.).
|
/// Внутренние данные авторизации (CSRF, track_id и т.д.).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
21
YandexMusic.API/Models/Account/YAuthQRSession.cs
Normal file
21
YandexMusic.API/Models/Account/YAuthQRSession.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace YandexMusic.API.Models.Account;
|
||||||
|
|
||||||
|
public class YAuthQRSession
|
||||||
|
{
|
||||||
|
[JsonPropertyName("default_uid")]
|
||||||
|
public int DefaultUid { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("retpath")]
|
||||||
|
public string RetPath { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("track_id")]
|
||||||
|
public string TrackId { get; set; }
|
||||||
|
|
||||||
|
public string Id { get; set; }
|
||||||
|
|
||||||
|
public string State { get; set; }
|
||||||
|
|
||||||
|
public YAuthCaptcha Captcha { get; set; }
|
||||||
|
}
|
||||||
9
YandexMusic.API/Models/Account/YAuthQrState.cs
Normal file
9
YandexMusic.API/Models/Account/YAuthQrState.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
using System.Runtime.Serialization;
|
||||||
|
|
||||||
|
namespace YandexMusic.API.Models.Account;
|
||||||
|
|
||||||
|
public enum YAuthQrState
|
||||||
|
{
|
||||||
|
[EnumMember(Value = "otp_auth_finished")]
|
||||||
|
OtpAuthFinished,
|
||||||
|
}
|
||||||
@@ -2,19 +2,11 @@
|
|||||||
|
|
||||||
namespace YandexMusic.API.Models.Account;
|
namespace YandexMusic.API.Models.Account;
|
||||||
|
|
||||||
public class YAuthQRStatus : YAuthBase
|
public class YAuthQRStatus
|
||||||
{
|
{
|
||||||
[JsonPropertyName("default_uid")]
|
[JsonPropertyName("state")]
|
||||||
public int DefaultUid { get; set; }
|
public string? State { get; set; } = null;
|
||||||
|
|
||||||
public string RetPath { get; set; }
|
[JsonPropertyName("trackId")]
|
||||||
|
public string TrackId { get; set; } = string.Empty;
|
||||||
[JsonPropertyName("track_id")]
|
|
||||||
public string TrackId { get; set; }
|
|
||||||
|
|
||||||
public string Id { get; set; }
|
|
||||||
|
|
||||||
public string State { get; set; }
|
|
||||||
|
|
||||||
public YAuthCaptcha Captcha { get; set; }
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,13 @@
|
|||||||
using System.Text.Json.Serialization;
|
namespace YandexMusic.API.Models.Account;
|
||||||
|
|
||||||
namespace YandexMusic.API.Models.Account;
|
|
||||||
|
|
||||||
public class YAuthToken
|
public class YAuthToken
|
||||||
{
|
{
|
||||||
[JsonPropertyName("csfr_token")]
|
|
||||||
public string CsfrToken { get; set; }
|
public string CsfrToken { get; set; }
|
||||||
|
|
||||||
[JsonPropertyName("track_id")]
|
|
||||||
public string TrackId { get; set; }
|
public string TrackId { get; set; }
|
||||||
|
|
||||||
[JsonPropertyName("process_uuid")]
|
public string SessionTrackId { get; set; }
|
||||||
|
|
||||||
public string ProcessUuid { get; set; }
|
public string ProcessUuid { get; set; }
|
||||||
|
|
||||||
public Dictionary<string, string> Cookie { get; set; } = new();
|
public Dictionary<string, string> Cookie { get; set; } = new();
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Net;
|
using System.Net;
|
||||||
|
using System.Net.Http.Headers;
|
||||||
using YandexMusic.API.Models.Account;
|
using YandexMusic.API.Models.Account;
|
||||||
using YandexMusic.API.Requests.Common;
|
using YandexMusic.API.Requests.Common;
|
||||||
|
|
||||||
@@ -8,7 +9,47 @@ internal class YGetAuthCookiesBuilder : YAuthRequestBuilder<YAccessToken?, objec
|
|||||||
{
|
{
|
||||||
public YGetAuthCookiesBuilder(YandexMusicApi api) : base(api) { }
|
public YGetAuthCookiesBuilder(YandexMusicApi api) : base(api) { }
|
||||||
protected override string Method => WebRequestMethods.Http.Post;
|
protected override string Method => WebRequestMethods.Http.Post;
|
||||||
|
protected override string BaseUrl => YConstants.Endpoints.MobilePassportUrl;
|
||||||
protected override string PathTemplate => "1/bundle/oauth/token_by_sessionid";
|
protected override string PathTemplate => "1/bundle/oauth/token_by_sessionid";
|
||||||
protected override HttpContent? GetContent(object _)
|
protected override HttpContent? GetContent(object _)
|
||||||
=> new FormUrlEncodedContent(new Dictionary<string, string> { { "client_id", YConstants.XClientId }, { "client_secret", YConstants.XClientSecret } });
|
=> new FormUrlEncodedContent(new Dictionary<string, string> { { "client_id", YConstants.XClientId }, { "client_secret", YConstants.XClientSecret } });
|
||||||
|
protected override void SetCustomHeaders(HttpRequestHeaders headers)
|
||||||
|
{
|
||||||
|
base.SetCustomHeaders(headers);
|
||||||
|
headers.Add("ya-client-host", "passport.yandex.ru");
|
||||||
|
|
||||||
|
var cookieString = GetCookieString();
|
||||||
|
if (!string.IsNullOrEmpty(cookieString))
|
||||||
|
headers.Add("Ya-Client-Cookie", cookieString);
|
||||||
|
}
|
||||||
|
private string GetCookieString()
|
||||||
|
{
|
||||||
|
var container = Storage.CookieContainer;
|
||||||
|
if (container == null) return string.Empty;
|
||||||
|
|
||||||
|
var uris = new[]
|
||||||
|
{
|
||||||
|
new Uri("https://yandex.ru"),
|
||||||
|
new Uri("https://passport.yandex.ru"),
|
||||||
|
new Uri("https://mobileproxy.passport.yandex.net")
|
||||||
|
};
|
||||||
|
|
||||||
|
var cookies = new List<string>();
|
||||||
|
foreach (var uri in uris)
|
||||||
|
{
|
||||||
|
var cookieCollection = container.GetCookies(uri);
|
||||||
|
foreach (Cookie cookie in cookieCollection)
|
||||||
|
{
|
||||||
|
cookies.Add($"{cookie.Name}={cookie.Value}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var distinct = cookies
|
||||||
|
.Select(c => c.Split('=')[0])
|
||||||
|
.Distinct()
|
||||||
|
.Select(name => cookies.First(c => c.StartsWith(name + "=")))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
return string.Join("; ", distinct);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
using System.Net;
|
using System.Net;
|
||||||
|
using System.Net.Http.Headers;
|
||||||
using YandexMusic.API.Models.Account;
|
using YandexMusic.API.Models.Account;
|
||||||
|
|
||||||
namespace YandexMusic.API.Requests.Account;
|
namespace YandexMusic.API.Requests.Account;
|
||||||
|
|
||||||
internal class YGetAuthLoginQRBuilder : YAuthRequestBuilder<YAuthQRStatus, string>
|
internal class YGetAuthLoginQRBuilder : YAuthRequestBuilder<YAuthQRSession, string>
|
||||||
{
|
{
|
||||||
public YGetAuthLoginQRBuilder(YandexMusicApi yandex) : base(yandex)
|
public YGetAuthLoginQRBuilder(YandexMusicApi yandex) : base(yandex)
|
||||||
{
|
{
|
||||||
@@ -11,13 +12,17 @@ internal class YGetAuthLoginQRBuilder : YAuthRequestBuilder<YAuthQRStatus, strin
|
|||||||
|
|
||||||
protected override string Method => WebRequestMethods.Http.Post;
|
protected override string Method => WebRequestMethods.Http.Post;
|
||||||
|
|
||||||
protected override string PathTemplate => "auth/new/magic/status/";
|
protected override string PathTemplate => "pwl-yandex/api/passport/sessions/get_session";
|
||||||
|
|
||||||
protected override HttpContent GetContent(string tuple)
|
protected override HttpContent GetContent(string tuple)
|
||||||
{
|
{
|
||||||
return new FormUrlEncodedContent(new Dictionary<string, string> {
|
return new FormUrlEncodedContent(new Dictionary<string, string> {
|
||||||
{ "csrf_token", Api.Storage.AuthToken.CsfrToken },
|
{ "track_id", Api.Storage.AuthToken.SessionTrackId }
|
||||||
{ "track_id", Api.Storage.AuthToken.TrackId }
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
protected override void SetCustomHeaders(HttpRequestHeaders headers)
|
||||||
|
{
|
||||||
|
headers.Add("X-Csrf-Token", Api.Storage.HeaderToken.CsfrToken);
|
||||||
|
headers.Add("Process-Uuid", Api.Storage.HeaderToken.ProcessUuid);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -13,7 +13,7 @@ internal class YGetAuthQRBuilder : YAuthRequestBuilder<YAuthQR?, object>
|
|||||||
=> new FormUrlEncodedContent(new Dictionary<string, string> { { "retpath", "" } });
|
=> new FormUrlEncodedContent(new Dictionary<string, string> { { "retpath", "" } });
|
||||||
protected override void SetCustomHeaders(HttpRequestHeaders headers)
|
protected override void SetCustomHeaders(HttpRequestHeaders headers)
|
||||||
{
|
{
|
||||||
headers.Add("X-Csrf-Token", Api.Storage.AuthToken.CsfrToken);
|
headers.Add("X-Csrf-Token", Api.Storage.HeaderToken.CsfrToken);
|
||||||
headers.Add("Process-Uuid", Api.Storage.AuthToken.ProcessUuid);
|
headers.Add("Process-Uuid", Api.Storage.HeaderToken.ProcessUuid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -13,7 +13,7 @@ internal class YPostAuthStats : YAuthRequestBuilder<YAuthEmpty?, object>
|
|||||||
=> new FormUrlEncodedContent(new Dictionary<string, string> { { "messageType", "CLIENT_READY" } });
|
=> new FormUrlEncodedContent(new Dictionary<string, string> { { "messageType", "CLIENT_READY" } });
|
||||||
protected override void SetCustomHeaders(HttpRequestHeaders headers)
|
protected override void SetCustomHeaders(HttpRequestHeaders headers)
|
||||||
{
|
{
|
||||||
headers.Add("X-Csrf-Token", Api.Storage.AuthToken.CsfrToken);
|
headers.Add("X-Csrf-Token", Api.Storage.HeaderToken.CsfrToken);
|
||||||
headers.Add("Process-Uuid", Api.Storage.AuthToken.ProcessUuid);
|
headers.Add("Process-Uuid", Api.Storage.HeaderToken.ProcessUuid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
23
YandexMusic.API/Requests/Account/YPostQrStatus.cs
Normal file
23
YandexMusic.API/Requests/Account/YPostQrStatus.cs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
using System.Net;
|
||||||
|
using System.Net.Http.Headers;
|
||||||
|
using YandexMusic.API.Models.Account;
|
||||||
|
|
||||||
|
namespace YandexMusic.API.Requests.Account;
|
||||||
|
|
||||||
|
internal class YPostQrStatus : YAuthRequestBuilder<YAuthQRStatus?, object>
|
||||||
|
{
|
||||||
|
public YPostQrStatus(YandexMusicApi api) : base(api) { }
|
||||||
|
protected override string Method => WebRequestMethods.Http.Post;
|
||||||
|
protected override string PathTemplate => "pwl-yandex/api/passport/auth/magic/code/status";
|
||||||
|
protected override HttpContent? GetContent(object _)
|
||||||
|
=> new FormUrlEncodedContent(new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
["csrf_token"] = Api.Storage.AuthToken.CsfrToken,
|
||||||
|
["track_id"] = Api.Storage.AuthToken.TrackId,
|
||||||
|
});
|
||||||
|
protected override void SetCustomHeaders(HttpRequestHeaders headers)
|
||||||
|
{
|
||||||
|
headers.Add("X-Csrf-Token", Api.Storage.HeaderToken.CsfrToken);
|
||||||
|
headers.Add("Process-Uuid", Api.Storage.HeaderToken.ProcessUuid);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,5 +12,6 @@ internal class YConstants
|
|||||||
{
|
{
|
||||||
public const string MusicUrl = "https://api.music.yandex.net";
|
public const string MusicUrl = "https://api.music.yandex.net";
|
||||||
public const string PassportUrl = "https://passport.yandex.ru/";
|
public const string PassportUrl = "https://passport.yandex.ru/";
|
||||||
|
public const string MobilePassportUrl = "https://mobileproxy.passport.yandex.net";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using YandexMusic.API;
|
using System.Net;
|
||||||
|
using YandexMusic.API;
|
||||||
using YandexMusic.API.Common;
|
using YandexMusic.API.Common;
|
||||||
using YandexMusic.API.Common.Ynison;
|
using YandexMusic.API.Common.Ynison;
|
||||||
using YandexMusic.API.Models.Account;
|
using YandexMusic.API.Models.Account;
|
||||||
@@ -42,20 +43,39 @@ public class YandexMusicClient : IDisposable
|
|||||||
public HttpClient HttpClient => _httpClient;
|
public HttpClient HttpClient => _httpClient;
|
||||||
|
|
||||||
/// <summary>Создаёт новый экземпляр клиента с собственным HttpClient.</summary>
|
/// <summary>Создаёт новый экземпляр клиента с собственным HttpClient.</summary>
|
||||||
public YandexMusicClient() : this(YandexMusicHttpClientFactory.CreateDefault())
|
public YandexMusicClient(
|
||||||
|
CookieContainer? cookieContainer = null,
|
||||||
|
IWebProxy? proxy = null,
|
||||||
|
TimeSpan? timeout = null,
|
||||||
|
string? userAgent = null
|
||||||
|
)
|
||||||
{
|
{
|
||||||
_ownsHttpClient = true;
|
if (cookieContainer == null) cookieContainer = new CookieContainer();
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
var handler = new HttpClientHandler
|
||||||
/// Создаёт новый экземпляр клиента с указанным HttpClient.
|
{
|
||||||
/// </summary>
|
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate,
|
||||||
/// <param name="httpClient">Экземпляр HttpClient (должен быть настроен с нужными куками, таймаутами).</param>
|
UseCookies = true,
|
||||||
/// <param name="ownsHttpClient">Если true, клиент будет отвечать за освобождение HttpClient при Dispose.</param>
|
CookieContainer = cookieContainer,
|
||||||
public YandexMusicClient(HttpClient httpClient)
|
AllowAutoRedirect = true,
|
||||||
{
|
MaxAutomaticRedirections = 10,
|
||||||
_httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
|
Proxy = proxy,
|
||||||
_storage = new AuthStorage();
|
UseProxy = proxy != null
|
||||||
|
};
|
||||||
|
|
||||||
|
var client = new HttpClient(handler, disposeHandler: true)
|
||||||
|
{
|
||||||
|
Timeout = timeout ?? TimeSpan.FromSeconds(30)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Стандартный User-Agent, похожий на браузерный
|
||||||
|
client.DefaultRequestHeaders.Add("User-Agent",
|
||||||
|
userAgent ?? "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36");
|
||||||
|
client.DefaultRequestHeaders.Add("Accept", "*/*");
|
||||||
|
client.DefaultRequestHeaders.Add("Accept-Language", "ru-RU,ru;q=0.9,en;q=0.8");
|
||||||
|
|
||||||
|
_httpClient = client;
|
||||||
|
_storage = new AuthStorage(cookieContainer);
|
||||||
_api = new YandexMusicApi(_httpClient, _storage);
|
_api = new YandexMusicApi(_httpClient, _storage);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,8 +96,12 @@ public class YandexMusicClient : IDisposable
|
|||||||
public Task<string?> GetAuthQRLink()
|
public Task<string?> GetAuthQRLink()
|
||||||
=> _api.User.GetAuthQRLinkAsync();
|
=> _api.User.GetAuthQRLinkAsync();
|
||||||
|
|
||||||
|
/// <summary>Проверка состояния сканирования QR-кода.</summary>
|
||||||
|
public Task<YAuthQRStatus?> CheckQRStatusAsync()
|
||||||
|
=> _api.User.CheckQRStatusAsync();
|
||||||
|
|
||||||
/// <summary>Авторизация по QR-коду (после сканирования).</summary>
|
/// <summary>Авторизация по QR-коду (после сканирования).</summary>
|
||||||
public Task<YAuthQRStatus?> AuthorizeByQR()
|
public Task<YAuthQRSession?> AuthorizeByQR()
|
||||||
=> _api.User.AuthorizeByQRAsync();
|
=> _api.User.AuthorizeByQRAsync();
|
||||||
|
|
||||||
/// <summary>Получение капчи.</summary>
|
/// <summary>Получение капчи.</summary>
|
||||||
|
|||||||
Reference in New Issue
Block a user