Полностью переписанное api
All checks were successful
Release / pack-and-publish (release) Successful in 36s
All checks were successful
Release / pack-and-publish (release) Successful in 36s
This commit is contained in:
@@ -1,300 +1,159 @@
|
||||
using System.Security.Authentication;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
using YandexMusic.API.Common;
|
||||
using YandexMusic.API.Models.Account;
|
||||
using YandexMusic.API.Models.Common;
|
||||
using YandexMusic.API.Requests.Account;
|
||||
|
||||
namespace YandexMusic.API;
|
||||
|
||||
/// <summary>
|
||||
/// API для пользователя
|
||||
/// </summary>
|
||||
public partial class YUserAPI : YCommonAPI
|
||||
/// <summary>API для работы с пользователем и авторизации.</summary>
|
||||
public class YUserAPI : YCommonAPI
|
||||
{
|
||||
#region Вспомогательные функции
|
||||
public YUserAPI(YandexMusicApi api) : base(api) { }
|
||||
|
||||
private async Task<bool> GetCsrfTokenAsync(AuthStorage storage)
|
||||
private async Task<bool> GetCsrfTokenAsync()
|
||||
{
|
||||
using HttpResponseMessage authMethodsResponse = await new YGetAuthMethodsBuilder(api, storage)
|
||||
.Build(null)
|
||||
.GetResponseAsync();
|
||||
using var response = await new YGetAuthMethodsBuilder(Api).ExecuteRawAsync(null!);
|
||||
if (response == null || !response.IsSuccessStatusCode)
|
||||
throw new HttpRequestException("Не удалось получить CSRF-токен");
|
||||
|
||||
if (!authMethodsResponse.IsSuccessStatusCode)
|
||||
throw new HttpRequestException("Невозможно получить CFRF-токен.");
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
var csrfMatch = Regex.Match(content, @"window\.__CSRF__\s*=\s*""([^""]+)""");
|
||||
var processMatch = Regex.Match(content, @"'process_uuid'\s*:\s*'([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})'");
|
||||
|
||||
string responseString = await authMethodsResponse.Content
|
||||
.ReadAsStringAsync();
|
||||
Match match = Regex.Match(responseString, "\"csrf_token\" value=\"([^\"]+)\"");
|
||||
|
||||
if (!match.Success || match.Groups.Count < 2)
|
||||
if (!csrfMatch.Success || !processMatch.Success)
|
||||
return false;
|
||||
|
||||
storage.AuthToken = new YAuthToken
|
||||
Api.Storage.AuthToken = new YAuthToken
|
||||
{
|
||||
CsfrToken = match.Groups[1].Value
|
||||
CsfrToken = csrfMatch.Groups[1].Value,
|
||||
ProcessUuid = processMatch.Groups[1].Value
|
||||
};
|
||||
|
||||
await new YPostAuthStats(Api).ExecuteAsync(null!);
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Task<bool> LoginByCookiesAsync()
|
||||
{
|
||||
if (Api.Storage.AuthToken == null)
|
||||
throw new AuthenticationException("Сессия входа не инициализирована");
|
||||
|
||||
var accessToken = await new YGetAuthCookiesBuilder(Api).ExecuteAsync(null!);
|
||||
if (accessToken == null || string.IsNullOrEmpty(accessToken.AccessToken))
|
||||
return false;
|
||||
|
||||
Api.Storage.AccessToken = accessToken;
|
||||
Api.Storage.Token = accessToken.AccessToken;
|
||||
|
||||
var shortInfo = await new YGetShortAccountInfoBuilder(Api).ExecuteAsync(null!);
|
||||
if (shortInfo?.Status != YAuthStatus.Ok || string.IsNullOrWhiteSpace(shortInfo.Uid))
|
||||
throw new Exception("Не удалось подтвердить авторизацию");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Task<bool> LoginByCookiesAsync(AuthStorage storage)
|
||||
{
|
||||
if (storage.AuthToken == null)
|
||||
throw new AuthenticationException("Невозможно инициализировать сессию входа.");
|
||||
|
||||
YAccessToken accessToken = await new YGetAuthCookiesBuilder(api, storage)
|
||||
.Build(null)
|
||||
.GetResponseAsync();
|
||||
|
||||
storage.IsAuthorized = !string.IsNullOrEmpty(accessToken.AccessToken);
|
||||
|
||||
storage.AccessToken = accessToken;
|
||||
storage.Token = accessToken.AccessToken;
|
||||
|
||||
YShortAccountInfo validateTokenResponse = await new YGetShortAccountInifoBuilder(api, storage)
|
||||
.Build(null)
|
||||
.GetResponseAsync();
|
||||
|
||||
if (validateTokenResponse.Status != YAuthStatus.Ok)
|
||||
throw new Exception("Вход в аккаунт не выполнен.");
|
||||
|
||||
storage.IsAuthorized = !string.IsNullOrWhiteSpace(validateTokenResponse.Uid);
|
||||
|
||||
return storage.IsAuthorized;
|
||||
}
|
||||
|
||||
#endregion Вспомогательные функции
|
||||
|
||||
public YUserAPI(YandexMusicApi yandex) : base(yandex)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Авторизация
|
||||
/// </summary>
|
||||
/// <param name="storage">Хранилище</param>
|
||||
/// <param name="token">Токен авторизации</param>
|
||||
/// <returns></returns>
|
||||
public async Task AuthorizeAsync(AuthStorage storage, string token)
|
||||
public async Task AuthorizeAsync(string token)
|
||||
{
|
||||
if (string.IsNullOrEmpty(token))
|
||||
throw new Exception("Задан пустой токен авторизации.");
|
||||
throw new Exception("Токен не может быть пустым");
|
||||
|
||||
storage.Token = token;
|
||||
Api.Storage.Token = token;
|
||||
var authInfo = await new YGetAuthInfoBuilder(Api).ExecuteAsync(null!);
|
||||
if (authInfo?.Account?.Uid == null)
|
||||
throw new Exception("Пользователь не авторизован");
|
||||
|
||||
// Пытаемся получить информацию о пользователе
|
||||
YResponse<YAccountResult> authInfo = await GetUserAuthAsync(storage);
|
||||
|
||||
// Если не авторизован, то авторизуем
|
||||
if (string.IsNullOrEmpty(authInfo.Result.Account.Uid))
|
||||
throw new Exception("Пользователь незалогинен.");
|
||||
|
||||
// Флаг авторизации
|
||||
storage.IsAuthorized = true;
|
||||
storage.User = authInfo.Result.Account;
|
||||
Api.Storage.SetAuthorized(authInfo.Account, token);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получение информации об авторизации
|
||||
/// </summary>
|
||||
/// <param name="storage">Хранилище</param>
|
||||
/// <returns></returns>
|
||||
public Task<YResponse<YAccountResult>> GetUserAuthAsync(AuthStorage storage)
|
||||
public Task<YAccountResult?> GetUserAuthAsync()
|
||||
=> new YGetAuthInfoBuilder(Api).ExecuteAsync(null!);
|
||||
|
||||
public async Task<YAuthTypes?> CreateAuthSessionAsync(string userName)
|
||||
{
|
||||
return new YGetAuthInfoBuilder(api, storage)
|
||||
.Build(null)
|
||||
.GetResponseAsync();
|
||||
if (!await GetCsrfTokenAsync())
|
||||
throw new Exception("Не удалось инициализировать сессию");
|
||||
|
||||
var result = await new YGetAuthLoginUserBuilder(Api).ExecuteAsync((Api.Storage.AuthToken.CsfrToken, userName));
|
||||
if (result?.TrackId != null)
|
||||
Api.Storage.AuthToken.TrackId = result.TrackId;
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Создание сеанса и получение доступных методов авторизации
|
||||
/// </summary>
|
||||
/// <param name="storage">Хранилище</param>
|
||||
/// <param name="userName">Имя пользователя</param>
|
||||
/// <returns></returns>
|
||||
public async Task<YAuthTypes> CreateAuthSessionAsync(AuthStorage storage, string userName)
|
||||
public async Task<string?> GetAuthQRLinkAsync()
|
||||
{
|
||||
if (!await GetCsrfTokenAsync(storage))
|
||||
throw new Exception("Невозможно инициализировать сессию входа.");
|
||||
if (!await GetCsrfTokenAsync())
|
||||
throw new Exception("Не удалось инициализировать сессию");
|
||||
|
||||
YAuthTypes types = await new YGetAuthLoginUserBuilder(api, storage)
|
||||
.Build((storage.AuthToken.CsfrToken, userName))
|
||||
.GetResponseAsync();
|
||||
var qr = await new YGetAuthQRBuilder(Api).ExecuteAsync(null!);
|
||||
if (qr?.Status != YAuthStatus.Ok || string.IsNullOrEmpty(qr.TrackId))
|
||||
return null;
|
||||
|
||||
storage.AuthToken.TrackId = types.TrackId;
|
||||
|
||||
return types;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получение ссылки на QR-код
|
||||
/// </summary>
|
||||
/// <param name="storage">Хранилище</param>
|
||||
/// <returns></returns>
|
||||
public async Task<string> GetAuthQRLinkAsync(AuthStorage storage)
|
||||
{
|
||||
if (!await GetCsrfTokenAsync(storage))
|
||||
throw new Exception("Невозможно инициализировать сессию входа.");
|
||||
|
||||
YAuthQR result = await new YGetAuthQRBuilder(api, storage)
|
||||
.Build(null)
|
||||
.GetResponseAsync();
|
||||
|
||||
if (result.Status != YAuthStatus.Ok)
|
||||
return string.Empty;
|
||||
|
||||
storage.AuthToken = new YAuthToken
|
||||
Api.Storage.AuthToken = new YAuthToken
|
||||
{
|
||||
TrackId = result.TrackId,
|
||||
CsfrToken = result.CsrfToken
|
||||
TrackId = qr.TrackId,
|
||||
CsfrToken = qr.CsrfToken
|
||||
};
|
||||
|
||||
return $"https://passport.yandex.ru/auth/magic/code/?track_id={result.TrackId}";
|
||||
return $"https://passport.yandex.ru/auth/magic/code/?track_id={qr.TrackId}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Авторизация по QR-коду
|
||||
/// </summary>
|
||||
/// <param name="storage">Хранилище</param>
|
||||
/// <returns></returns>
|
||||
public async Task<YAuthQRStatus> AuthorizeByQRAsync(AuthStorage storage)
|
||||
public async Task<YAuthQRStatus?> AuthorizeByQRAsync()
|
||||
{
|
||||
if (storage.AuthToken == null)
|
||||
throw new Exception("Не выполнен запрос на авторизацию по QR.");
|
||||
if (Api.Storage.AuthToken == null)
|
||||
throw new Exception("Сессия не инициализирована");
|
||||
|
||||
try
|
||||
{
|
||||
YAuthQRStatus qrStatus = await new YGetAuthLoginQRBuilder(api, storage)
|
||||
.Build(null)
|
||||
.GetResponseAsync();
|
||||
if (qrStatus.Status != YAuthStatus.Ok)
|
||||
return qrStatus;
|
||||
|
||||
bool ok = await LoginByCookiesAsync(storage);
|
||||
if (!ok)
|
||||
throw new AuthenticationException("Ошибка авторизации по QR.");
|
||||
|
||||
return qrStatus;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new AuthenticationException("Ошибка авторизации по QR.", ex);
|
||||
}
|
||||
var status = await new YGetAuthLoginQRBuilder(Api).ExecuteAsync(null!);
|
||||
if (status?.Status == YAuthStatus.Ok && await LoginByCookiesAsync())
|
||||
return status;
|
||||
throw new AuthenticationException("Ошибка авторизации по QR");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получение <see cref="YAuthCaptcha"/>
|
||||
/// </summary>
|
||||
/// <param name="storage">Хранилище</param>
|
||||
/// <returns></returns>
|
||||
public Task<YAuthCaptcha> GetCaptchaAsync(AuthStorage storage)
|
||||
public Task<YAuthCaptcha?> GetCaptchaAsync()
|
||||
{
|
||||
if (storage.AuthToken == null || string.IsNullOrWhiteSpace(storage.AuthToken.CsfrToken))
|
||||
throw new AuthenticationException($"Не найдена сессия входа. Выполните {nameof(CreateAuthSessionAsync)} перед использованием.");
|
||||
|
||||
return new YGetAuthCaptchaBuilder(api, storage)
|
||||
.Build(null)
|
||||
.GetResponseAsync();
|
||||
if (Api.Storage.AuthToken == null)
|
||||
throw new AuthenticationException("Выполните CreateAuthSessionAsync перед использованием");
|
||||
return new YGetAuthCaptchaBuilder(Api).ExecuteAsync(null!);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Авторизация по captcha
|
||||
/// </summary>
|
||||
/// <param name="storage">Хранилище</param>
|
||||
/// <param name="captchaValue">Значение captcha</param>
|
||||
/// <returns></returns>
|
||||
public Task<YAuthBase> AuthorizeByCaptchaAsync(AuthStorage storage, string captchaValue)
|
||||
public Task<YAuthBase?> AuthorizeByCaptchaAsync(string captchaValue)
|
||||
{
|
||||
if (storage.AuthToken == null || string.IsNullOrWhiteSpace(storage.AuthToken.CsfrToken))
|
||||
throw new AuthenticationException($"Не найдена сессия входа. Выполните {nameof(CreateAuthSessionAsync)} перед использованием.");
|
||||
|
||||
return new YGetAuthLoginCaptchaBuilder(api, storage)
|
||||
.Build(captchaValue)
|
||||
.GetResponseAsync();
|
||||
if (Api.Storage.AuthToken == null)
|
||||
throw new AuthenticationException("Выполните CreateAuthSessionAsync перед использованием");
|
||||
return new YGetAuthLoginCaptchaBuilder(Api).ExecuteAsync(captchaValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получение письма авторизации на почту пользователя
|
||||
/// </summary>
|
||||
/// <param name="storage">Хранилище</param>
|
||||
/// <returns></returns>
|
||||
public Task<YAuthLetter> GetAuthLetterAsync(AuthStorage storage)
|
||||
public Task<YAuthLetter?> GetAuthLetterAsync()
|
||||
=> new YGetAuthLetterBuilder(Api).ExecuteAsync(null!);
|
||||
|
||||
public async Task<bool> AuthorizeByLetterAsync()
|
||||
{
|
||||
return new YGetAuthLetterBuilder(api, storage)
|
||||
.Build(null)
|
||||
.GetResponseAsync();
|
||||
var status = await new YGetAuthLoginLetterBuilder(Api).ExecuteAsync(null!);
|
||||
if (status?.Status != YAuthStatus.Ok || !status.MagicLinkConfirmed)
|
||||
throw new Exception("Письмо не подтверждено");
|
||||
return await LoginByCookiesAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Авторизация после подтверждения входа через письмо
|
||||
/// </summary>
|
||||
/// <param name="storage">Хранилище</param>
|
||||
/// <returns></returns>
|
||||
public async Task<bool> AuthorizeByLetterAsync(AuthStorage storage)
|
||||
public async Task<YAuthBase?> AuthorizeByAppPasswordAsync(string password)
|
||||
{
|
||||
YAuthLetterStatus status = await new YGetAuthLoginLetterBuilder(api, storage)
|
||||
.Build(null)
|
||||
.GetResponseAsync();
|
||||
if (Api.Storage.AuthToken == null)
|
||||
throw new AuthenticationException("Выполните CreateAuthSessionAsync перед использованием");
|
||||
|
||||
if (status.Status == YAuthStatus.Ok && !status.MagicLinkConfirmed)
|
||||
throw new Exception("Не подтвержден вход посредством e-mail.");
|
||||
|
||||
return await LoginByCookiesAsync(storage);
|
||||
var result = await new YGetAuthAppPasswordBuilder(Api).ExecuteAsync(password);
|
||||
if (result?.Status == YAuthStatus.Ok && await LoginByCookiesAsync())
|
||||
return result;
|
||||
throw new AuthenticationException("Ошибка авторизации по паролю");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Авторизация с помощью пароля из приложения Яндекс
|
||||
/// </summary>
|
||||
/// <param name="storage">Хранилище</param>
|
||||
/// <param name="password">Пароль</param>
|
||||
/// <returns></returns>
|
||||
public async Task<YAuthBase> AuthorizeByAppPasswordAsync(AuthStorage storage, string password)
|
||||
public async Task<YAccessToken?> GetAccessTokenAsync()
|
||||
{
|
||||
if (storage.AuthToken == null || string.IsNullOrWhiteSpace(storage.AuthToken.CsfrToken))
|
||||
throw new AuthenticationException($"Не найдена сессия входа. Выполните {nameof(CreateAuthSessionAsync)} перед использованием.");
|
||||
if (Api.Storage.AuthToken == null)
|
||||
throw new Exception("Сессия не инициализована");
|
||||
|
||||
YAuthBase response = await new YGetAuthAppPasswordBuilder(api, storage)
|
||||
.Build(password)
|
||||
.GetResponseAsync();
|
||||
|
||||
if (response.Status == YAuthStatus.Ok)
|
||||
{
|
||||
bool ok = await LoginByCookiesAsync(storage);
|
||||
if (!ok)
|
||||
throw new AuthenticationException("Ошибка авторизации.");
|
||||
}
|
||||
|
||||
return response;
|
||||
var token = await new YGetMusicTokenBuilder(Api).ExecuteAsync(null!);
|
||||
if (token?.AccessToken != null)
|
||||
Api.Storage.Token = token.AccessToken;
|
||||
return token;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получение <see cref="YAccessToken"/> после авторизации с помощью QR, e-mail, пароля из приложения
|
||||
/// </summary>
|
||||
public async Task<YAccessToken> GetAccessTokenAsync(AuthStorage storage)
|
||||
{
|
||||
if (storage.AuthToken == null)
|
||||
throw new Exception("Не найдена сессия входа.");
|
||||
|
||||
YAccessToken accessToken = await new YGetMusicTokenBuilder(api, storage)
|
||||
.Build(null)
|
||||
.GetResponseAsync();
|
||||
|
||||
storage.Token = accessToken.AccessToken;
|
||||
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получение информации о пользователе через логин Яндекса
|
||||
/// </summary>
|
||||
public Task<YLoginInfo> GetLoginInfoAsync(AuthStorage storage)
|
||||
{
|
||||
return new YGetLoginInfoBuilder(api, storage)
|
||||
.Build(null)
|
||||
.GetResponseAsync();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
public Task<YLoginInfo?> GetLoginInfoAsync()
|
||||
=> new YGetLoginInfoBuilder(Api).ExecuteAsync(null!);
|
||||
}
|
||||
Reference in New Issue
Block a user