Files
YandexMusic/YandexMusic.API/API/YPassportAPI.cs

230 lines
9.6 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;
}
}