230 lines
9.6 KiB
C#
230 lines
9.6 KiB
C#
using System.Security.Authentication;
|
||
using System.Text.RegularExpressions;
|
||
using YandexMusic.API.Models.Account;
|
||
using YandexMusic.API.Models.Passport;
|
||
using YandexMusic.API.Requests.Account;
|
||
using YandexMusic.API.Requests.Passport;
|
||
|
||
namespace YandexMusic.API;
|
||
|
||
/// <summary>API для работы с яндекс паспортом</summary>
|
||
public class YPassportAPI : YCommonAPI
|
||
{
|
||
public YPassportAPI(YandexMusicApi api) : base(api) { }
|
||
|
||
public async Task<YAccessToken?> GetMusicTokenByCookiesAsync()
|
||
{
|
||
if (string.IsNullOrEmpty(Api.Storage.AuthToken.TrackId))
|
||
{
|
||
if (!await GetCsrfTokenAsync())
|
||
throw new Exception("Не удалось инициализировать сессию");
|
||
await CreateTrackAsync(); // ваш приватный метод создания track_id
|
||
}
|
||
return await new YGetAuthCookiesBuilder(Api).ExecuteAsync(null!);
|
||
}
|
||
|
||
public async Task<YAccessToken?> GetMusicTokenByPassportTokenAsync(string passportToken)
|
||
=> await GetAccessTokenAsync(passportToken);
|
||
|
||
public async Task<string?> GetAuthQRLinkAsync()
|
||
{
|
||
if (!await GetCsrfTokenAsync())
|
||
throw new Exception("Не удалось инициализировать сессию");
|
||
|
||
await CreateTrackAsync();
|
||
|
||
return $"https://passport.yandex.ru/auth/magic/code/?track_id={Api.Storage.AuthToken.TrackId}";
|
||
}
|
||
|
||
public async Task<YAuthQRStatus?> CheckQRStatusAsync()
|
||
{
|
||
if (Api.Storage.AuthToken == null)
|
||
throw new Exception("Сессия не инициализирована");
|
||
|
||
var status = await new YGetQrStatus(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!);
|
||
if (status != null && status.DefaultUid != 0 && await LoginByCookiesAsync())
|
||
return status;
|
||
throw new AuthenticationException("Ошибка авторизации по QR");
|
||
}
|
||
|
||
/// <summary>Многоступенчатая авторизация: начало (передача логина).</summary>
|
||
public async Task<YMultistepStart?> MultistepStartAsync(string login)
|
||
{
|
||
if (Api.Storage.AuthToken == null)
|
||
throw new Exception("Сессия не инициализирована. Вызовите GetAuthQRLinkAsync или CreateTrackAsync");
|
||
return await new YMultistepStartBuilder(Api).ExecuteAsync(login);
|
||
}
|
||
|
||
/// <summary>Многоступенчатая авторизация: ввод пароля.</summary>
|
||
public async Task<YPassportUser?> MultistepPasswordAsync(string password)
|
||
{
|
||
if (Api.Storage.AuthToken == null)
|
||
throw new Exception("Сессия не инициализирована");
|
||
return await new YMultiStepPasswordBuilder(Api).ExecuteAsync(password);
|
||
}
|
||
|
||
/// <summary>Авторизация с помощью RFC OTP.</summary>
|
||
public async Task<YPassportUser?> RfcOtpPasswordAsync(string rfcOtp)
|
||
{
|
||
if (Api.Storage.AuthToken == null)
|
||
throw new Exception("Сессия не инициализирована");
|
||
return await new YRfcOtpBuilder(Api).ExecuteAsync(rfcOtp);
|
||
}
|
||
|
||
/// <summary>Создание сессии пользователя.</summary>
|
||
public async Task<YPassportSession?> CreateUserSessionAsync()
|
||
{
|
||
if (Api.Storage.AuthToken == null)
|
||
throw new Exception("Сессия не инициализирована");
|
||
return await new YGetSessionBuilder(Api).ExecuteAsync(null!);
|
||
}
|
||
|
||
/// <summary>Проверка состояния сессии.</summary>
|
||
public async Task<YPassportSessionStatus?> GetSessionStateAsync()
|
||
{
|
||
if (Api.Storage.AuthToken == null)
|
||
throw new Exception("Сессия не инициализирована");
|
||
return await new YCheckSessionBuilder(Api).ExecuteAsync(null!);
|
||
}
|
||
|
||
/// <summary>Проверка номера телефона (валидация).</summary>
|
||
public async Task<YValidatePhoneNumberResult?> ValidatePhoneNumberAsync(string phone)
|
||
{
|
||
if (Api.Storage.AuthToken == null)
|
||
throw new Exception("Сессия не инициализирована");
|
||
return await new YValidatePhoneNumberBuilder(Api).ExecuteAsync(phone);
|
||
}
|
||
|
||
/// <summary>Проверка доступности номера.</summary>
|
||
public async Task<YCheckAvailabilityResult?> CheckPhoneAvailabilityAsync(string phone)
|
||
{
|
||
if (Api.Storage.AuthToken == null)
|
||
throw new Exception("Сессия не инициализирована");
|
||
return await new YCheckPhoneAvailabilityBuilder(Api).ExecuteAsync(phone);
|
||
}
|
||
|
||
/// <summary>Запрос на отправку push-уведомления.</summary>
|
||
public async Task<YSendPushResult?> SuggestSendPushAsync(string phone)
|
||
{
|
||
if (Api.Storage.AuthToken == null)
|
||
throw new Exception("Сессия не инициализирована");
|
||
return await new YSendPushBuilder(Api).ExecuteAsync(phone);
|
||
}
|
||
|
||
/// <summary>Проверка push-кода.</summary>
|
||
public async Task CheckPushCodeAsync(string code)
|
||
{
|
||
if (Api.Storage.AuthToken == null)
|
||
throw new Exception("Сессия не инициализирована");
|
||
await new YCheckPushCodeBuilder(Api).ExecuteAsync(code);
|
||
}
|
||
|
||
/// <summary>Проверка на "сквоттера" (захват номера).</summary>
|
||
public async Task<YValidateSquatter?> ValidateSquatterAsync(string phone)
|
||
{
|
||
if (Api.Storage.AuthToken == null)
|
||
throw new Exception("Сессия не инициализирована");
|
||
return await new YValidateSquatterBuilder(Api).ExecuteAsync(phone);
|
||
}
|
||
|
||
/// <summary>Получение списка аккаунтов по номеру телефона.</summary>
|
||
public async Task<YSuggestByPhoneResult?> SuggestByPhoneAsync()
|
||
{
|
||
if (Api.Storage.AuthToken == null)
|
||
throw new Exception("Сессия не инициализирована");
|
||
return await new YSuggestByPhoneBuilder(Api).ExecuteAsync(null!);
|
||
}
|
||
|
||
/// <summary>Вход по паролю (упрощённый, если не нужна многоступенчатость).</summary>
|
||
public async Task<YPassportUser?> LoginByPasswordAsync(string password)
|
||
{
|
||
// Сначала запускаем мультистеп (если track_id уже есть)
|
||
if (string.IsNullOrEmpty(Api.Storage.AuthToken.TrackId))
|
||
throw new Exception("TrackId не найден. Вызовите MultistepStartAsync сначала.");
|
||
return await MultistepPasswordAsync(password);
|
||
}
|
||
|
||
|
||
|
||
|
||
private async Task CreateTrackAsync()
|
||
{
|
||
if (!await GetCsrfTokenAsync())
|
||
throw new Exception("Невозможно инициализировать сессию входа.");
|
||
|
||
var track = await new YCreateTrackBuilder(Api).ExecuteAsync(null!);
|
||
|
||
if (string.IsNullOrWhiteSpace(track?.TrackId) || string.IsNullOrWhiteSpace(track?.CsrfToken))
|
||
throw new Exception("Не удалось создать трек паспорта.");
|
||
|
||
Api.Storage.AuthToken.TrackId = track.TrackId;
|
||
Api.Storage.AuthToken.CsfrToken = track.CsrfToken;
|
||
}
|
||
|
||
internal async Task<bool> GetCsrfTokenAsync()
|
||
{
|
||
using var response = await new YGetAuthMethodsBuilder(Api).ExecuteRawAsync(null!);
|
||
if (response == null || !response.IsSuccessStatusCode)
|
||
throw new HttpRequestException("Не удалось получить CSRF-токен");
|
||
|
||
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})'");
|
||
|
||
if (!csrfMatch.Success || !processMatch.Success)
|
||
return false;
|
||
|
||
Api.Storage.HeaderToken = new YAuthToken
|
||
{
|
||
CsfrToken = csrfMatch.Groups[1].Value,
|
||
ProcessUuid = processMatch.Groups[1].Value
|
||
};
|
||
|
||
return true;
|
||
}
|
||
|
||
internal async Task<YAccessToken?> GetAccessTokenAsync(string passportToken)
|
||
{
|
||
if (string.IsNullOrEmpty(passportToken))
|
||
throw new Exception("Сессия не инициализована");
|
||
|
||
var token = await new YGetMusicTokenBuilder(Api).ExecuteAsync(passportToken);
|
||
if (token?.AccessToken != null)
|
||
Api.Storage.Token = token.AccessToken;
|
||
|
||
return token;
|
||
}
|
||
|
||
|
||
internal 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;
|
||
|
||
await GetAccessTokenAsync(accessToken.AccessToken);
|
||
|
||
return true;
|
||
}
|
||
} |