Полный рефакторинг api. Вынесено отдельно api passport
This commit is contained in:
111
YandexMusic.API/API/YAuthAPI.cs
Normal file
111
YandexMusic.API/API/YAuthAPI.cs
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
using System.Security.Authentication;
|
||||||
|
using YandexMusic.API.Models.Account;
|
||||||
|
using YandexMusic.API.Requests.Account;
|
||||||
|
|
||||||
|
namespace YandexMusic.API;
|
||||||
|
|
||||||
|
/// <summary>API для работы с пользователем и авторизации.</summary>
|
||||||
|
public class YAuthAPI : YCommonAPI
|
||||||
|
{
|
||||||
|
public YAuthAPI(YandexMusicApi api) : base(api) { }
|
||||||
|
|
||||||
|
/// <summary>Авторизация по готовому музыкальному токену (OAuth).</summary>
|
||||||
|
public async Task AuthorizeAsync(string musicToken)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(musicToken))
|
||||||
|
throw new ArgumentException("Токен не может быть пустым", nameof(musicToken));
|
||||||
|
|
||||||
|
Api.Storage.Token = musicToken;
|
||||||
|
var authInfo = await new YGetAuthInfoBuilder(Api).ExecuteAsync(null!);
|
||||||
|
if (authInfo?.Account?.Uid == null)
|
||||||
|
throw new Exception("Пользователь не авторизован");
|
||||||
|
|
||||||
|
Api.Storage.SetAuthorized(authInfo.Account, musicToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Авторизация по паспортному токену (полученному, например, после QR-входа).</summary>
|
||||||
|
public async Task AuthorizeByPassportTokenAsync(string passportToken)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(passportToken))
|
||||||
|
throw new ArgumentException("Паспортный токен не может быть пустым", nameof(passportToken));
|
||||||
|
|
||||||
|
var musicToken = await Api.Passport.GetMusicTokenByPassportTokenAsync(passportToken);
|
||||||
|
if (musicToken?.AccessToken == null)
|
||||||
|
throw new Exception("Не удалось обменять паспортный токен на музыкальный");
|
||||||
|
|
||||||
|
await AuthorizeAsync(musicToken.AccessToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Получение информации о текущем аккаунте.</summary>
|
||||||
|
public Task<YAccountResult?> GetUserAuthAsync()
|
||||||
|
=> new YGetAuthInfoBuilder(Api).ExecuteAsync(null!);
|
||||||
|
|
||||||
|
/// <summary>Создание сессии авторизации (получение доступных методов входа).</summary>
|
||||||
|
public async Task<YAuthTypes?> CreateAuthSessionAsync(string userName)
|
||||||
|
{
|
||||||
|
if (!await Api.Passport.GetCsrfTokenAsync()) // вместо InitSessionAsync
|
||||||
|
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>
|
||||||
|
public Task<YAuthCaptcha?> GetCaptchaAsync()
|
||||||
|
{
|
||||||
|
if (Api.Storage.AuthToken == null)
|
||||||
|
throw new AuthenticationException("Выполните CreateAuthSessionAsync перед использованием");
|
||||||
|
return new YGetAuthCaptchaBuilder(Api).ExecuteAsync(null!);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Авторизация по капче.</summary>
|
||||||
|
public Task<YAuthBase?> AuthorizeByCaptchaAsync(string captchaValue)
|
||||||
|
{
|
||||||
|
if (Api.Storage.AuthToken == null)
|
||||||
|
throw new AuthenticationException("Выполните CreateAuthSessionAsync перед использованием");
|
||||||
|
return new YGetAuthLoginCaptchaBuilder(Api).ExecuteAsync(captchaValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Отправить письмо для авторизации.</summary>
|
||||||
|
public Task<YAuthLetter?> GetAuthLetterAsync()
|
||||||
|
=> new YGetAuthLetterBuilder(Api).ExecuteAsync(null!);
|
||||||
|
|
||||||
|
/// <summary>Подтверждение входа по письму и получение музыкального токена.</summary>
|
||||||
|
public async Task<bool> AuthorizeByLetterAsync()
|
||||||
|
{
|
||||||
|
var status = await new YGetAuthLoginLetterBuilder(Api).ExecuteAsync(null!);
|
||||||
|
if (status?.Status != YAuthStatus.Ok || !status.MagicLinkConfirmed)
|
||||||
|
throw new Exception("Письмо не подтверждено");
|
||||||
|
|
||||||
|
var musicToken = await Api.Passport.GetMusicTokenByCookiesAsync();
|
||||||
|
if (musicToken?.AccessToken == null)
|
||||||
|
throw new Exception("Не удалось получить музыкальный токен после подтверждения письма");
|
||||||
|
|
||||||
|
await AuthorizeAsync(musicToken.AccessToken);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Авторизация по паролю приложения Яндекс.</summary>
|
||||||
|
public async Task<YAuthBase?> AuthorizeByAppPasswordAsync(string password)
|
||||||
|
{
|
||||||
|
if (Api.Storage.AuthToken == null)
|
||||||
|
throw new AuthenticationException("Выполните CreateAuthSessionAsync перед использованием");
|
||||||
|
|
||||||
|
var result = await new YGetAuthAppPasswordBuilder(Api).ExecuteAsync(password);
|
||||||
|
if (result?.Status != YAuthStatus.Ok)
|
||||||
|
throw new AuthenticationException("Ошибка авторизации по паролю");
|
||||||
|
|
||||||
|
var musicToken = await Api.Passport.GetMusicTokenByCookiesAsync();
|
||||||
|
if (musicToken?.AccessToken == null)
|
||||||
|
throw new Exception("Не удалось получить музыкальный токен после ввода пароля");
|
||||||
|
|
||||||
|
await AuthorizeAsync(musicToken.AccessToken);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Получение информации о пользователе через логин.</summary>
|
||||||
|
public Task<YLoginInfo?> GetLoginInfoAsync()
|
||||||
|
=> new YGetLoginInfoBuilder(Api).ExecuteAsync(null!);
|
||||||
|
}
|
||||||
230
YandexMusic.API/API/YPassportAPI.cs
Normal file
230
YandexMusic.API/API/YPassportAPI.cs
Normal file
@@ -0,0 +1,230 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,185 +0,0 @@
|
|||||||
using System.Security.Authentication;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using YandexMusic.API.Models.Account;
|
|
||||||
using YandexMusic.API.Requests.Account;
|
|
||||||
|
|
||||||
namespace YandexMusic.API;
|
|
||||||
|
|
||||||
/// <summary>API для работы с пользователем и авторизации.</summary>
|
|
||||||
public class YUserAPI : YCommonAPI
|
|
||||||
{
|
|
||||||
public YUserAPI(YandexMusicApi api) : base(api) { }
|
|
||||||
|
|
||||||
private 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
|
|
||||||
};
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
await AuthorizeByPassportAsync(accessToken.AccessToken);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task AuthorizeAsync(string token)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(token))
|
|
||||||
throw new Exception("Токен не может быть пустым");
|
|
||||||
|
|
||||||
Api.Storage.Token = token;
|
|
||||||
var authInfo = await new YGetAuthInfoBuilder(Api).ExecuteAsync(null!);
|
|
||||||
if (authInfo?.Account?.Uid == null)
|
|
||||||
throw new Exception("Пользователь не авторизован");
|
|
||||||
|
|
||||||
Api.Storage.SetAuthorized(authInfo.Account, token);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task AuthorizeByPassportAsync(string token)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(token))
|
|
||||||
throw new Exception("Токен не может быть пустым");
|
|
||||||
|
|
||||||
Api.Storage.Token = token;
|
|
||||||
await GetAccessTokenAsync();
|
|
||||||
await AuthorizeAsync(Api.Storage.Token);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<YAccountResult?> GetUserAuthAsync()
|
|
||||||
=> new YGetAuthInfoBuilder(Api).ExecuteAsync(null!);
|
|
||||||
|
|
||||||
public async Task<YAuthTypes?> CreateAuthSessionAsync(string userName)
|
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<string?> GetAuthQRLinkAsync()
|
|
||||||
{
|
|
||||||
if (!await GetCsrfTokenAsync())
|
|
||||||
throw new Exception("Не удалось инициализировать сессию");
|
|
||||||
|
|
||||||
var qr = await new YGetAuthQRBuilder(Api).ExecuteAsync(null!);
|
|
||||||
if (qr?.Status != YAuthStatus.Ok || string.IsNullOrEmpty(qr.TrackId))
|
|
||||||
return null;
|
|
||||||
|
|
||||||
Api.Storage.AuthToken = new YAuthToken
|
|
||||||
{
|
|
||||||
TrackId = qr.TrackId,
|
|
||||||
CsfrToken = qr.CsrfToken
|
|
||||||
};
|
|
||||||
return $"https://passport.yandex.ru/auth/magic/code/?track_id={qr.TrackId}";
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<YAuthQRStatus?> CheckQRStatusAsync()
|
|
||||||
{
|
|
||||||
if (Api.Storage.AuthToken == null)
|
|
||||||
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!);
|
|
||||||
if (status != null && status.DefaultUid != 0 && await LoginByCookiesAsync())
|
|
||||||
return status;
|
|
||||||
throw new AuthenticationException("Ошибка авторизации по QR");
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<YAuthCaptcha?> GetCaptchaAsync()
|
|
||||||
{
|
|
||||||
if (Api.Storage.AuthToken == null)
|
|
||||||
throw new AuthenticationException("Выполните CreateAuthSessionAsync перед использованием");
|
|
||||||
return new YGetAuthCaptchaBuilder(Api).ExecuteAsync(null!);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<YAuthBase?> AuthorizeByCaptchaAsync(string captchaValue)
|
|
||||||
{
|
|
||||||
if (Api.Storage.AuthToken == null)
|
|
||||||
throw new AuthenticationException("Выполните CreateAuthSessionAsync перед использованием");
|
|
||||||
return new YGetAuthLoginCaptchaBuilder(Api).ExecuteAsync(captchaValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<YAuthLetter?> GetAuthLetterAsync()
|
|
||||||
=> new YGetAuthLetterBuilder(Api).ExecuteAsync(null!);
|
|
||||||
|
|
||||||
public async Task<bool> AuthorizeByLetterAsync()
|
|
||||||
{
|
|
||||||
var status = await new YGetAuthLoginLetterBuilder(Api).ExecuteAsync(null!);
|
|
||||||
if (status?.Status != YAuthStatus.Ok || !status.MagicLinkConfirmed)
|
|
||||||
throw new Exception("Письмо не подтверждено");
|
|
||||||
return await LoginByCookiesAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<YAuthBase?> AuthorizeByAppPasswordAsync(string password)
|
|
||||||
{
|
|
||||||
if (Api.Storage.AuthToken == null)
|
|
||||||
throw new AuthenticationException("Выполните CreateAuthSessionAsync перед использованием");
|
|
||||||
|
|
||||||
var result = await new YGetAuthAppPasswordBuilder(Api).ExecuteAsync(password);
|
|
||||||
if (result?.Status == YAuthStatus.Ok && await LoginByCookiesAsync())
|
|
||||||
return result;
|
|
||||||
throw new AuthenticationException("Ошибка авторизации по паролю");
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<YAccessToken?> GetAccessTokenAsync()
|
|
||||||
{
|
|
||||||
if (Api.Storage.AuthToken == null)
|
|
||||||
throw new Exception("Сессия не инициализована");
|
|
||||||
|
|
||||||
var token = await new YGetMusicTokenBuilder(Api).ExecuteAsync(null!);
|
|
||||||
if (token?.AccessToken != null)
|
|
||||||
Api.Storage.Token = token.AccessToken;
|
|
||||||
return token;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<YLoginInfo?> GetLoginInfoAsync()
|
|
||||||
=> new YGetLoginInfoBuilder(Api).ExecuteAsync(null!);
|
|
||||||
}
|
|
||||||
@@ -45,12 +45,17 @@ public class AuthStorage
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Внутренние данные авторизации (CSRF, track_id и т.д.).
|
/// Внутренние данные авторизации (CSRF, track_id и т.д.).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public YAuthToken HeaderToken { get; set; } = new();
|
public YAuthToken? HeaderToken { get; set; } = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Внутренние данные авторизации (CSRF, track_id и т.д.).
|
/// Внутренние данные авторизации (CSRF, track_id и т.д.).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public YAuthToken AuthToken { get; set; } = new();
|
public YAuthToken? AuthToken { get; set; } = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Страна, используемая для авторизации (по умолчанию "ru"). Может влиять на язык интерфейса и доступные методы авторизации.
|
||||||
|
/// </summary>
|
||||||
|
public object Country { get; set; } = "ru";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Устанавливает флаг авторизации и сохраняет информацию об аккаунте.
|
/// Устанавливает флаг авторизации и сохраняет информацию об аккаунте.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
using YandexMusic.API.Models.Album;
|
using YandexMusic.API.Models.Album;
|
||||||
|
|
||||||
namespace YandexMusic.API.Extensions.API;
|
namespace YandexMusic.API;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Методы-расширения для альбома.
|
/// Методы-расширения для альбома.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
using YandexMusic.API.Models.Artist;
|
using YandexMusic.API.Models.Artist;
|
||||||
using YandexMusic.API.Models.Track;
|
using YandexMusic.API.Models.Track;
|
||||||
|
|
||||||
namespace YandexMusic.API.Extensions.API;
|
namespace YandexMusic.API;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Методы-расширения для исполнителя.
|
/// Методы-расширения для исполнителя.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
using YandexMusic.API.Models.Playlist;
|
using YandexMusic.API.Models.Playlist;
|
||||||
using YandexMusic.API.Models.Track;
|
using YandexMusic.API.Models.Track;
|
||||||
|
|
||||||
namespace YandexMusic.API.Extensions.API;
|
namespace YandexMusic.API;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Методы-расширения для плейлиста.
|
/// Методы-расширения для плейлиста.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
using YandexMusic.API.Models.Radio;
|
using YandexMusic.API.Models.Radio;
|
||||||
using YandexMusic.API.Models.Track;
|
using YandexMusic.API.Models.Track;
|
||||||
|
|
||||||
namespace YandexMusic.API.Extensions.API;
|
namespace YandexMusic.API;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Методы-расширения для радиостанции.
|
/// Методы-расширения для радиостанции.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
using YandexMusic.API.Models.Track;
|
using YandexMusic.API.Models.Track;
|
||||||
|
|
||||||
namespace YandexMusic.API.Extensions.API;
|
namespace YandexMusic.API;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Методы-расширения для трека.
|
/// Методы-расширения для трека.
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
using YandexMusic.API.Models.Account;
|
||||||
|
|
||||||
namespace YandexMusic.API.Models.Account;
|
namespace YandexMusic.API.Models.Passport;
|
||||||
|
|
||||||
public class YAuthQR : YAuthBase
|
public class YPassportTrack : YAuthBase
|
||||||
{
|
{
|
||||||
[JsonPropertyName("track_id")]
|
[JsonPropertyName("track_id")]
|
||||||
public string TrackId { get; set; }
|
public string TrackId { get; set; }
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
using YandexMusic.API.Models.Account;
|
||||||
|
|
||||||
namespace YandexMusic.API.Models.Account;
|
namespace YandexMusic.API.Models.Passport;
|
||||||
|
|
||||||
public class YAuthQRSession
|
public class YAuthQrSession
|
||||||
{
|
{
|
||||||
[JsonPropertyName("default_uid")]
|
[JsonPropertyName("default_uid")]
|
||||||
public int DefaultUid { get; set; }
|
public int DefaultUid { get; set; }
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace YandexMusic.API.Models.Account;
|
namespace YandexMusic.API.Models.Passport;
|
||||||
|
|
||||||
public class YAuthQRStatus
|
public class YAuthQRStatus
|
||||||
{
|
{
|
||||||
39
YandexMusic.API/Models/Passport/YCheckAvailabilityResult.cs
Normal file
39
YandexMusic.API/Models/Passport/YCheckAvailabilityResult.cs
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace YandexMusic.API.Models.Passport;
|
||||||
|
|
||||||
|
public class YCheckAvailabilityResult
|
||||||
|
{
|
||||||
|
[JsonPropertyName("antifraudScore")]
|
||||||
|
public string AntifraudScore { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonPropertyName("hasAvailableAccounts")]
|
||||||
|
public bool HasAvailableAccounts { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("flnFlowRequired")]
|
||||||
|
public bool FlnFlowRequired { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("can_use_push")]
|
||||||
|
public bool CanUsePush { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("can_use_webauthn")]
|
||||||
|
public bool CanUseWebauthn { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("has_master")]
|
||||||
|
public bool HasMaster { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("is_session_mastered")]
|
||||||
|
public bool IsSessionMastered { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("does_master_have_free_slots")]
|
||||||
|
public bool DoesMasterHaveFreeSlots { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("allowed_registration_flows")]
|
||||||
|
public List<object> AllowedRegistrationFlows { get; set; } = new();
|
||||||
|
|
||||||
|
[JsonPropertyName("SuggestBy")]
|
||||||
|
public string SuggestBy { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonPropertyName("master_info")]
|
||||||
|
public YMasterInfo? MasterInfo { get; set; }
|
||||||
|
}
|
||||||
12
YandexMusic.API/Models/Passport/YMasterInfo.cs
Normal file
12
YandexMusic.API/Models/Passport/YMasterInfo.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace YandexMusic.API.Models.Passport;
|
||||||
|
|
||||||
|
public class YMasterInfo
|
||||||
|
{
|
||||||
|
[JsonPropertyName("firstname")]
|
||||||
|
public string FirstName { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonPropertyName("lastname")]
|
||||||
|
public string LastName { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
42
YandexMusic.API/Models/Passport/YMultistepStart.cs
Normal file
42
YandexMusic.API/Models/Passport/YMultistepStart.cs
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace YandexMusic.API.Models.Passport;
|
||||||
|
|
||||||
|
public class YMultistepStart
|
||||||
|
{
|
||||||
|
[JsonPropertyName("track_id")]
|
||||||
|
public string TrackId { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonPropertyName("can_authorize")]
|
||||||
|
public bool CanAuthorize { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("can_register")]
|
||||||
|
public bool CanRegister { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("is_rfc_2fa_enabled")]
|
||||||
|
public bool IsRfc2faEnabled { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("allowed_account_types")]
|
||||||
|
public List<string> AllowedAccountTypes { get; set; } = new();
|
||||||
|
|
||||||
|
[JsonPropertyName("location_id")]
|
||||||
|
public string LocationId { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonPropertyName("primary_alias_type")]
|
||||||
|
public int PrimaryAliasType { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("auth_methods")]
|
||||||
|
public List<string> AuthMethods { get; set; } = new();
|
||||||
|
|
||||||
|
[JsonPropertyName("preferred_auth_method")]
|
||||||
|
public string PreferredAuthMethod { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonPropertyName("csrf_token")]
|
||||||
|
public string CsrfToken { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonPropertyName("error")]
|
||||||
|
public string? Error { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("errors")]
|
||||||
|
public List<string>? Errors { get; set; }
|
||||||
|
}
|
||||||
48
YandexMusic.API/Models/Passport/YPassportAccount.cs
Normal file
48
YandexMusic.API/Models/Passport/YPassportAccount.cs
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace YandexMusic.API.Models.Passport;
|
||||||
|
|
||||||
|
public class YPassportAccount
|
||||||
|
{
|
||||||
|
[JsonPropertyName("avatar_url")]
|
||||||
|
public string AvatarUrl { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonPropertyName("display_login")]
|
||||||
|
public string DisplayLogin { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonPropertyName("display_name")]
|
||||||
|
public YPassportName? DisplayName { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("has_master")]
|
||||||
|
public bool HasMaster { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("has_plus")]
|
||||||
|
public bool HasPlus { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("has_secure_phone")]
|
||||||
|
public bool HasSecurePhone { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("is_2fa_enabled")]
|
||||||
|
public bool Is2faEnabled { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("is_rfc_2fa_enabled")]
|
||||||
|
public bool IsRfc2faEnabled { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("is_sms_2fa_enabled")]
|
||||||
|
public bool IsSms2faEnabled { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("is_workspace_user")]
|
||||||
|
public bool IsWorkspaceUser { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("is_yandexoid")]
|
||||||
|
public bool IsYandexoid { get; set; }
|
||||||
|
|
||||||
|
public bool Login { get; set; }
|
||||||
|
|
||||||
|
public YPassportPerson? Person { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("secure_phone_id")]
|
||||||
|
public int SecurePhoneId { get; set; }
|
||||||
|
|
||||||
|
public int Uid { get; set; }
|
||||||
|
}
|
||||||
11
YandexMusic.API/Models/Passport/YPassportName.cs
Normal file
11
YandexMusic.API/Models/Passport/YPassportName.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace YandexMusic.API.Models.Passport;
|
||||||
|
|
||||||
|
public class YPassportName
|
||||||
|
{
|
||||||
|
[JsonPropertyName("default_avatar")]
|
||||||
|
public string DefaultAvatar { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
11
YandexMusic.API/Models/Passport/YPassportPerson.cs
Normal file
11
YandexMusic.API/Models/Passport/YPassportPerson.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
namespace YandexMusic.API.Models.Passport;
|
||||||
|
|
||||||
|
public class YPassportPerson
|
||||||
|
{
|
||||||
|
public string Birthday { get; set; } = string.Empty;
|
||||||
|
public string Country { get; set; } = string.Empty;
|
||||||
|
public string FirstName { get; set; } = string.Empty;
|
||||||
|
public int Gender { get; set; }
|
||||||
|
public string Language { get; set; } = string.Empty;
|
||||||
|
public string LastName { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
12
YandexMusic.API/Models/Passport/YPassportSession.cs
Normal file
12
YandexMusic.API/Models/Passport/YPassportSession.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace YandexMusic.API.Models.Passport;
|
||||||
|
|
||||||
|
public class YPassportSession
|
||||||
|
{
|
||||||
|
[JsonPropertyName("track_id")]
|
||||||
|
public string TrackId { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonPropertyName("default_uid")]
|
||||||
|
public string DefaultUid { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
12
YandexMusic.API/Models/Passport/YPassportSessionStatus.cs
Normal file
12
YandexMusic.API/Models/Passport/YPassportSessionStatus.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace YandexMusic.API.Models.Passport;
|
||||||
|
|
||||||
|
public class YPassportSessionStatus
|
||||||
|
{
|
||||||
|
[JsonPropertyName("session_is_correct")]
|
||||||
|
public bool SessionIsCorrect { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("session_has_users")]
|
||||||
|
public bool SessionHasUsers { get; set; }
|
||||||
|
}
|
||||||
21
YandexMusic.API/Models/Passport/YPassportUser.cs
Normal file
21
YandexMusic.API/Models/Passport/YPassportUser.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace YandexMusic.API.Models.Passport;
|
||||||
|
|
||||||
|
public class YPassportUser
|
||||||
|
{
|
||||||
|
[JsonPropertyName("track_id")]
|
||||||
|
public string TrackId { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonPropertyName("state")]
|
||||||
|
public string State { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonPropertyName("account")]
|
||||||
|
public YPassportAccount? Account { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("error")]
|
||||||
|
public string? Error { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("errors")]
|
||||||
|
public List<string>? Errors { get; set; }
|
||||||
|
}
|
||||||
7
YandexMusic.API/Models/Passport/YPushApp.cs
Normal file
7
YandexMusic.API/Models/Passport/YPushApp.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
namespace YandexMusic.API.Models.Passport;
|
||||||
|
|
||||||
|
public class YPushApp
|
||||||
|
{
|
||||||
|
public string App { get; set; } = string.Empty;
|
||||||
|
public string Platform { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
18
YandexMusic.API/Models/Passport/YSendPushResult.cs
Normal file
18
YandexMusic.API/Models/Passport/YSendPushResult.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace YandexMusic.API.Models.Passport;
|
||||||
|
|
||||||
|
public class YSendPushResult
|
||||||
|
{
|
||||||
|
[JsonPropertyName("pushes_devices_list")]
|
||||||
|
public List<object> PushesDevicesList { get; set; } = new();
|
||||||
|
|
||||||
|
[JsonPropertyName("deny_resend_until")]
|
||||||
|
public int DenyResendUntil { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("is_push_silent")]
|
||||||
|
public bool IsPushSilent { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("apps_for_bright_push")]
|
||||||
|
public List<YPushApp> AppsForBrightPush { get; set; } = new();
|
||||||
|
}
|
||||||
54
YandexMusic.API/Models/Passport/YSuggestAccount.cs
Normal file
54
YandexMusic.API/Models/Passport/YSuggestAccount.cs
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace YandexMusic.API.Models.Passport;
|
||||||
|
|
||||||
|
public class YSuggestAccount
|
||||||
|
{
|
||||||
|
[JsonPropertyName("allowed_auth_flows")]
|
||||||
|
public List<string> AllowedAuthFlows { get; set; } = new();
|
||||||
|
|
||||||
|
[JsonPropertyName("avatar_url")]
|
||||||
|
public string AvatarUrl { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonPropertyName("default_avatar")]
|
||||||
|
public string DefaultAvatar { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonPropertyName("display_name")]
|
||||||
|
public YPassportName? DisplayName { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("has_bank_card")]
|
||||||
|
public bool HasBankCard { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("has_family")]
|
||||||
|
public bool HasFamily { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("has_master")]
|
||||||
|
public bool HasMaster { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("has_plus")]
|
||||||
|
public bool HasPlus { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("is_communal")]
|
||||||
|
public bool IsCommunal { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("location_id")]
|
||||||
|
public string LocationId { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonPropertyName("login")]
|
||||||
|
public string Login { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonPropertyName("primary_alias_type")]
|
||||||
|
public int PrimaryAliasType { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("priority")]
|
||||||
|
public int Priority { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("uid")]
|
||||||
|
public long Uid { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("shields")]
|
||||||
|
public List<string> Shields { get; set; } = new();
|
||||||
|
|
||||||
|
[JsonPropertyName("require_additional_sms_to_login")]
|
||||||
|
public bool RequireAdditionalSmsToLogin { get; set; }
|
||||||
|
}
|
||||||
15
YandexMusic.API/Models/Passport/YSuggestByPhoneResult.cs
Normal file
15
YandexMusic.API/Models/Passport/YSuggestByPhoneResult.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace YandexMusic.API.Models.Passport;
|
||||||
|
|
||||||
|
public class YSuggestByPhoneResult
|
||||||
|
{
|
||||||
|
[JsonPropertyName("accounts")]
|
||||||
|
public List<YSuggestAccount> Accounts { get; set; } = new();
|
||||||
|
|
||||||
|
[JsonPropertyName("allowed_registration_flows")]
|
||||||
|
public List<string> AllowedRegistrationFlows { get; set; } = new();
|
||||||
|
|
||||||
|
[JsonPropertyName("uid_from_bb")]
|
||||||
|
public string UidFromBb { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using YandexMusic.API.Models.Account;
|
||||||
|
|
||||||
|
namespace YandexMusic.API.Models.Passport;
|
||||||
|
|
||||||
|
public class YValidatePhoneNumberResult
|
||||||
|
{
|
||||||
|
[JsonPropertyName("phone_number")]
|
||||||
|
public YPhoneNumber? PhoneNumber { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("valid_for_flash_call")]
|
||||||
|
public bool ValidForFlashCall { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("location_id")]
|
||||||
|
public string LocationId { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonPropertyName("valid_for_viber")]
|
||||||
|
public bool ValidForViber { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("valid_for_whatsapp")]
|
||||||
|
public bool ValidForWhatsapp { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("valid_for_telegram")]
|
||||||
|
public bool ValidForTelegram { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("valid_for_sms")]
|
||||||
|
public bool ValidForSms { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("track_id")]
|
||||||
|
public string TrackId { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonPropertyName("error")]
|
||||||
|
public string? Error { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("errors")]
|
||||||
|
public List<string>? Errors { get; set; }
|
||||||
|
}
|
||||||
18
YandexMusic.API/Models/Passport/YValidateSquatter.cs
Normal file
18
YandexMusic.API/Models/Passport/YValidateSquatter.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace YandexMusic.API.Models.Passport;
|
||||||
|
|
||||||
|
public class YValidateSquatter
|
||||||
|
{
|
||||||
|
[JsonPropertyName("require_flow_with_fio")]
|
||||||
|
public bool RequireFlowWithFio { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("require_flow_with_auth_hint")]
|
||||||
|
public bool RequireFlowWithAuthHint { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("auth_hint_question_id")]
|
||||||
|
public string AuthHintQuestionId { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonPropertyName("suggestBy")]
|
||||||
|
public string SuggestBy { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@ using YandexMusic.API.Models.Account;
|
|||||||
|
|
||||||
namespace YandexMusic.API.Requests.Account;
|
namespace YandexMusic.API.Requests.Account;
|
||||||
|
|
||||||
internal class YGetAuthAppPasswordBuilder : YAuthRequestBuilder<YAuthBase?, string>
|
internal class YGetAuthAppPasswordBuilder : YPassportRequestBuilder<YAuthBase?, string>
|
||||||
{
|
{
|
||||||
public YGetAuthAppPasswordBuilder(YandexMusicApi api) : base(api) { }
|
public YGetAuthAppPasswordBuilder(YandexMusicApi api) : base(api) { }
|
||||||
protected override string Method => WebRequestMethods.Http.Post;
|
protected override string Method => WebRequestMethods.Http.Post;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ using YandexMusic.API.Models.Account;
|
|||||||
|
|
||||||
namespace YandexMusic.API.Requests.Account;
|
namespace YandexMusic.API.Requests.Account;
|
||||||
|
|
||||||
internal class YGetAuthCaptchaBuilder : YAuthRequestBuilder<YAuthCaptcha?, object>
|
internal class YGetAuthCaptchaBuilder : YPassportRequestBuilder<YAuthCaptcha?, object>
|
||||||
{
|
{
|
||||||
public YGetAuthCaptchaBuilder(YandexMusicApi api) : base(api) { }
|
public YGetAuthCaptchaBuilder(YandexMusicApi api) : base(api) { }
|
||||||
protected override string Method => WebRequestMethods.Http.Post;
|
protected override string Method => WebRequestMethods.Http.Post;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ using YandexMusic.API.Requests.Common;
|
|||||||
|
|
||||||
namespace YandexMusic.API.Requests.Account;
|
namespace YandexMusic.API.Requests.Account;
|
||||||
|
|
||||||
internal class YGetAuthCookiesBuilder : YAuthRequestBuilder<YAccessToken?, object>
|
internal class YGetAuthCookiesBuilder : YPassportRequestBuilder<YAccessToken?, object>
|
||||||
{
|
{
|
||||||
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;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ using YandexMusic.API.Models.Account;
|
|||||||
|
|
||||||
namespace YandexMusic.API.Requests.Account;
|
namespace YandexMusic.API.Requests.Account;
|
||||||
|
|
||||||
internal class YGetAuthLetterBuilder : YAuthRequestBuilder<YAuthLetter?, object>
|
internal class YGetAuthLetterBuilder : YPassportRequestBuilder<YAuthLetter?, object>
|
||||||
{
|
{
|
||||||
public YGetAuthLetterBuilder(YandexMusicApi api) : base(api) { }
|
public YGetAuthLetterBuilder(YandexMusicApi api) : base(api) { }
|
||||||
protected override string Method => WebRequestMethods.Http.Post;
|
protected override string Method => WebRequestMethods.Http.Post;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ using YandexMusic.API.Models.Account;
|
|||||||
|
|
||||||
namespace YandexMusic.API.Requests.Account;
|
namespace YandexMusic.API.Requests.Account;
|
||||||
|
|
||||||
internal class YGetAuthLoginCaptchaBuilder : YAuthRequestBuilder<YAuthBase?, string>
|
internal class YGetAuthLoginCaptchaBuilder : YPassportRequestBuilder<YAuthBase?, string>
|
||||||
{
|
{
|
||||||
public YGetAuthLoginCaptchaBuilder(YandexMusicApi api) : base(api) { }
|
public YGetAuthLoginCaptchaBuilder(YandexMusicApi api) : base(api) { }
|
||||||
protected override string Method => WebRequestMethods.Http.Post;
|
protected override string Method => WebRequestMethods.Http.Post;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ using YandexMusic.API.Models.Account;
|
|||||||
|
|
||||||
namespace YandexMusic.API.Requests.Account;
|
namespace YandexMusic.API.Requests.Account;
|
||||||
|
|
||||||
internal class YGetAuthLoginLetterBuilder : YAuthRequestBuilder<YAuthLetterStatus?, object>
|
internal class YGetAuthLoginLetterBuilder : YPassportRequestBuilder<YAuthLetterStatus?, object>
|
||||||
{
|
{
|
||||||
public YGetAuthLoginLetterBuilder(YandexMusicApi api) : base(api) { }
|
public YGetAuthLoginLetterBuilder(YandexMusicApi api) : base(api) { }
|
||||||
protected override string Method => WebRequestMethods.Http.Post;
|
protected override string Method => WebRequestMethods.Http.Post;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ using YandexMusic.API.Models.Account;
|
|||||||
|
|
||||||
namespace YandexMusic.API.Requests.Account;
|
namespace YandexMusic.API.Requests.Account;
|
||||||
|
|
||||||
internal class YGetAuthLoginUserBuilder : YAuthRequestBuilder<YAuthTypes?, (string token, string login)>
|
internal class YGetAuthLoginUserBuilder : YPassportRequestBuilder<YAuthTypes?, (string token, string login)>
|
||||||
{
|
{
|
||||||
public YGetAuthLoginUserBuilder(YandexMusicApi api) : base(api) { }
|
public YGetAuthLoginUserBuilder(YandexMusicApi api) : base(api) { }
|
||||||
protected override string Method => WebRequestMethods.Http.Post;
|
protected override string Method => WebRequestMethods.Http.Post;
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
using System.Net;
|
|
||||||
using System.Net.Http.Headers;
|
|
||||||
using YandexMusic.API.Models.Account;
|
|
||||||
|
|
||||||
namespace YandexMusic.API.Requests.Account;
|
|
||||||
|
|
||||||
internal class YGetAuthQRBuilder : YAuthRequestBuilder<YAuthQR?, object>
|
|
||||||
{
|
|
||||||
public YGetAuthQRBuilder(YandexMusicApi api) : base(api) { }
|
|
||||||
protected override string Method => WebRequestMethods.Http.Post;
|
|
||||||
protected override string PathTemplate => "pwl-yandex/api/passport/auth/password/submit";
|
|
||||||
protected override HttpContent? GetContent(object _)
|
|
||||||
=> new FormUrlEncodedContent(new Dictionary<string, string> { { "retpath", "" } });
|
|
||||||
protected override void SetCustomHeaders(HttpRequestHeaders headers)
|
|
||||||
{
|
|
||||||
headers.Add("X-Csrf-Token", Api.Storage.HeaderToken.CsfrToken);
|
|
||||||
headers.Add("Process-Uuid", Api.Storage.HeaderToken.ProcessUuid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,7 +3,7 @@ using YandexMusic.API.Models.Account;
|
|||||||
|
|
||||||
namespace YandexMusic.API.Requests.Account;
|
namespace YandexMusic.API.Requests.Account;
|
||||||
|
|
||||||
internal class YGetLoginInfoBuilder : YAuthRequestBuilder<YLoginInfo?, object>
|
internal class YGetLoginInfoBuilder : YPassportRequestBuilder<YLoginInfo?, object>
|
||||||
{
|
{
|
||||||
public YGetLoginInfoBuilder(YandexMusicApi api) : base(api) { }
|
public YGetLoginInfoBuilder(YandexMusicApi api) : base(api) { }
|
||||||
protected override string Method => WebRequestMethods.Http.Get;
|
protected override string Method => WebRequestMethods.Http.Get;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ using YandexMusic.API.Models.Account;
|
|||||||
|
|
||||||
namespace YandexMusic.API.Requests.Account;
|
namespace YandexMusic.API.Requests.Account;
|
||||||
|
|
||||||
internal class YGetShortAccountInfoBuilder : YAuthRequestBuilder<YShortAccountInfo?, object>
|
internal class YGetShortAccountInfoBuilder : YPassportRequestBuilder<YShortAccountInfo?, object>
|
||||||
{
|
{
|
||||||
public YGetShortAccountInfoBuilder(YandexMusicApi api) : base(api) { }
|
public YGetShortAccountInfoBuilder(YandexMusicApi api) : base(api) { }
|
||||||
protected override string Method => WebRequestMethods.Http.Get;
|
protected override string Method => WebRequestMethods.Http.Get;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ using YandexMusic.API.Models.Account;
|
|||||||
|
|
||||||
namespace YandexMusic.API.Requests.Account;
|
namespace YandexMusic.API.Requests.Account;
|
||||||
|
|
||||||
internal class YPostAuthStats : YAuthRequestBuilder<YAuthEmpty?, object>
|
internal class YPostAuthStats : YPassportRequestBuilder<YAuthEmpty?, object>
|
||||||
{
|
{
|
||||||
public YPostAuthStats(YandexMusicApi api) : base(api) { }
|
public YPostAuthStats(YandexMusicApi api) : base(api) { }
|
||||||
protected override string Method => WebRequestMethods.Http.Post;
|
protected override string Method => WebRequestMethods.Http.Post;
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
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,8 @@ internal class YGetArtistTrackBuilder : YMusicRequestBuilder<YTracksPage?, (stri
|
|||||||
protected override Dictionary<string, string> GetSubstitutions((string id, int page, int pageSize) tuple)
|
protected override Dictionary<string, string> GetSubstitutions((string id, int page, int pageSize) tuple)
|
||||||
=> new() { { "artistId", tuple.id } };
|
=> new() { { "artistId", tuple.id } };
|
||||||
protected override NameValueCollection GetQueryParams((string id, int page, int pageSize) tuple)
|
protected override NameValueCollection GetQueryParams((string id, int page, int pageSize) tuple)
|
||||||
=> new() { { "page", tuple.page.ToString() }, { "pageSize", tuple.pageSize.ToString() } };
|
=> new() {
|
||||||
|
{ "page", tuple.page.ToString() },
|
||||||
|
{ "pageSize", tuple.pageSize.ToString() },
|
||||||
|
};
|
||||||
}
|
}
|
||||||
@@ -4,15 +4,20 @@ using YandexMusic.API.Requests.Common;
|
|||||||
namespace YandexMusic.API.Requests;
|
namespace YandexMusic.API.Requests;
|
||||||
|
|
||||||
/// <summary>Базовый класс для запросов к Passport (passport.yandex.ru).</summary>
|
/// <summary>Базовый класс для запросов к Passport (passport.yandex.ru).</summary>
|
||||||
internal abstract class YAuthRequestBuilder<TResponse, TParams> : YJsonRequestBuilder<TResponse, TParams>
|
internal abstract class YPassportRequestBuilder<TResponse, TParams> : YJsonRequestBuilder<TResponse, TParams>
|
||||||
{
|
{
|
||||||
protected override string BaseUrl => YConstants.Endpoints.PassportUrl;
|
protected override string BaseUrl => YConstants.Endpoints.PassportUrl;
|
||||||
|
|
||||||
protected YAuthRequestBuilder(YandexMusicApi api) : base(api) { }
|
protected YPassportRequestBuilder(YandexMusicApi api) : base(api) { }
|
||||||
|
|
||||||
protected override void SetCustomHeaders(HttpRequestHeaders headers)
|
protected override void SetCustomHeaders(HttpRequestHeaders headers)
|
||||||
{
|
{
|
||||||
base.SetCustomHeaders(headers);
|
base.SetCustomHeaders(headers);
|
||||||
headers.Add("X-Requested-With", "XMLHttpRequest");
|
headers.Add("X-Requested-With", "XMLHttpRequest");
|
||||||
|
if (Api.Storage.HeaderToken != null)
|
||||||
|
{
|
||||||
|
headers.Add("X-Csrf-Token", Api.Storage.HeaderToken.CsfrToken);
|
||||||
|
headers.Add("Process-Uuid", Api.Storage.HeaderToken.ProcessUuid);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
using System.Net;
|
||||||
|
using System.Net.Http.Json;
|
||||||
|
using YandexMusic.API.Models.Passport;
|
||||||
|
|
||||||
|
namespace YandexMusic.API.Requests.Passport;
|
||||||
|
|
||||||
|
internal class YCheckPhoneAvailabilityBuilder : YPassportRequestBuilder<YCheckAvailabilityResult?, string>
|
||||||
|
{
|
||||||
|
public YCheckPhoneAvailabilityBuilder(YandexMusicApi api) : base(api) { }
|
||||||
|
|
||||||
|
protected override string Method => WebRequestMethods.Http.Post;
|
||||||
|
protected override string PathTemplate => "pwl-yandex/api/passport/suggest/check_availability";
|
||||||
|
|
||||||
|
protected override HttpContent? GetContent(string phone)
|
||||||
|
{
|
||||||
|
var data = new
|
||||||
|
{
|
||||||
|
track_id = Api.Storage.AuthToken.TrackId,
|
||||||
|
phone_number = phone,
|
||||||
|
can_use_anmon = true,
|
||||||
|
check_for_push = true,
|
||||||
|
push_suggest_log_all_subscriptions = false
|
||||||
|
};
|
||||||
|
|
||||||
|
return JsonContent.Create(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
24
YandexMusic.API/Requests/Passport/YCheckPushCodeBuilder.cs
Normal file
24
YandexMusic.API/Requests/Passport/YCheckPushCodeBuilder.cs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
using System.Net;
|
||||||
|
using System.Net.Http.Json;
|
||||||
|
using YandexMusic.API.Models.Account;
|
||||||
|
|
||||||
|
namespace YandexMusic.API.Requests.Passport;
|
||||||
|
|
||||||
|
internal class YCheckPushCodeBuilder : YPassportRequestBuilder<YAuthEmpty?, string>
|
||||||
|
{
|
||||||
|
public YCheckPushCodeBuilder(YandexMusicApi api) : base(api) { }
|
||||||
|
|
||||||
|
protected override string Method => WebRequestMethods.Http.Post;
|
||||||
|
protected override string PathTemplate => "pwl-yandex/api/passport/auth/check-push-code";
|
||||||
|
|
||||||
|
protected override HttpContent? GetContent(string code)
|
||||||
|
{
|
||||||
|
var data = new
|
||||||
|
{
|
||||||
|
track_id = Api.Storage.AuthToken.TrackId,
|
||||||
|
code
|
||||||
|
};
|
||||||
|
|
||||||
|
return JsonContent.Create(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
19
YandexMusic.API/Requests/Passport/YCheckSessionBuilder.cs
Normal file
19
YandexMusic.API/Requests/Passport/YCheckSessionBuilder.cs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
using System.Net;
|
||||||
|
using System.Net.Http.Json;
|
||||||
|
using YandexMusic.API.Models.Passport;
|
||||||
|
|
||||||
|
namespace YandexMusic.API.Requests.Passport;
|
||||||
|
|
||||||
|
internal class YCheckSessionBuilder : YPassportRequestBuilder<YPassportSessionStatus?, object>
|
||||||
|
{
|
||||||
|
public YCheckSessionBuilder(YandexMusicApi api) : base(api) { }
|
||||||
|
|
||||||
|
protected override string Method => WebRequestMethods.Http.Post;
|
||||||
|
protected override string PathTemplate => "pwl-yandex/api/passport/sessions/check_session";
|
||||||
|
|
||||||
|
protected override HttpContent? GetContent(object _)
|
||||||
|
{
|
||||||
|
var data = new { track_id = Api.Storage.AuthToken.TrackId };
|
||||||
|
return JsonContent.Create(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
13
YandexMusic.API/Requests/Passport/YCreateTrackBuilder.cs
Normal file
13
YandexMusic.API/Requests/Passport/YCreateTrackBuilder.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
using System.Net;
|
||||||
|
using YandexMusic.API.Models.Passport;
|
||||||
|
|
||||||
|
namespace YandexMusic.API.Requests.Passport;
|
||||||
|
|
||||||
|
internal class YCreateTrackBuilder : YPassportRequestBuilder<YPassportTrack?, object>
|
||||||
|
{
|
||||||
|
public YCreateTrackBuilder(YandexMusicApi api) : base(api) { }
|
||||||
|
protected override string Method => WebRequestMethods.Http.Post;
|
||||||
|
protected override string PathTemplate => "pwl-yandex/api/passport/auth/password/submit";
|
||||||
|
protected override HttpContent? GetContent(object _)
|
||||||
|
=> new FormUrlEncodedContent(new Dictionary<string, string> { { "retpath", "" } });
|
||||||
|
}
|
||||||
@@ -1,10 +1,9 @@
|
|||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Http.Headers;
|
using YandexMusic.API.Models.Passport;
|
||||||
using YandexMusic.API.Models.Account;
|
|
||||||
|
|
||||||
namespace YandexMusic.API.Requests.Account;
|
namespace YandexMusic.API.Requests.Passport;
|
||||||
|
|
||||||
internal class YGetAuthLoginQRBuilder : YAuthRequestBuilder<YAuthQRSession, string>
|
internal class YGetAuthLoginQRBuilder : YPassportRequestBuilder<YAuthQrSession, string>
|
||||||
{
|
{
|
||||||
public YGetAuthLoginQRBuilder(YandexMusicApi yandex) : base(yandex)
|
public YGetAuthLoginQRBuilder(YandexMusicApi yandex) : base(yandex)
|
||||||
{
|
{
|
||||||
@@ -20,9 +19,4 @@ internal class YGetAuthLoginQRBuilder : YAuthRequestBuilder<YAuthQRSession, stri
|
|||||||
{ "track_id", Api.Storage.AuthToken.SessionTrackId }
|
{ "track_id", Api.Storage.AuthToken.SessionTrackId }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
protected override void SetCustomHeaders(HttpRequestHeaders headers)
|
|
||||||
{
|
|
||||||
headers.Add("X-Csrf-Token", Api.Storage.HeaderToken.CsfrToken);
|
|
||||||
headers.Add("Process-Uuid", Api.Storage.HeaderToken.ProcessUuid);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -3,21 +3,21 @@ 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;
|
||||||
|
|
||||||
namespace YandexMusic.API.Requests.Account;
|
namespace YandexMusic.API.Requests.Passport;
|
||||||
|
|
||||||
internal class YGetMusicTokenBuilder : YAuthRequestBuilder<YAccessToken?, object>
|
internal class YGetMusicTokenBuilder : YPassportRequestBuilder<YAccessToken?, string>
|
||||||
{
|
{
|
||||||
public YGetMusicTokenBuilder(YandexMusicApi api) : base(api) { }
|
public YGetMusicTokenBuilder(YandexMusicApi api) : base(api) { }
|
||||||
protected override string BaseUrl => YConstants.Endpoints.MobilePassportUrl;
|
protected override string BaseUrl => YConstants.Endpoints.MobilePassportUrl;
|
||||||
protected override string Method => WebRequestMethods.Http.Post;
|
protected override string Method => WebRequestMethods.Http.Post;
|
||||||
protected override string PathTemplate => "/1/token";
|
protected override string PathTemplate => "/1/token";
|
||||||
protected override HttpContent? GetContent(object _)
|
protected override HttpContent? GetContent(string passportToken)
|
||||||
=> new FormUrlEncodedContent(new Dictionary<string, string>
|
=> new FormUrlEncodedContent(new Dictionary<string, string>
|
||||||
{
|
{
|
||||||
{ "client_id", YConstants.ClientId },
|
{ "client_id", YConstants.ClientId },
|
||||||
{ "client_secret", YConstants.ClientSecret },
|
{ "client_secret", YConstants.ClientSecret },
|
||||||
{ "grant_type", "x-token" },
|
{ "grant_type", "x-token" },
|
||||||
{ "access_token", Api.Storage.AccessToken.AccessToken }
|
{ "access_token", passportToken }
|
||||||
});
|
});
|
||||||
protected override void SetCustomHeaders(HttpRequestHeaders headers)
|
protected override void SetCustomHeaders(HttpRequestHeaders headers)
|
||||||
{
|
{
|
||||||
17
YandexMusic.API/Requests/Passport/YGetQrStatus.cs
Normal file
17
YandexMusic.API/Requests/Passport/YGetQrStatus.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
using System.Net;
|
||||||
|
using YandexMusic.API.Models.Passport;
|
||||||
|
|
||||||
|
namespace YandexMusic.API.Requests.Passport;
|
||||||
|
|
||||||
|
internal class YGetQrStatus : YPassportRequestBuilder<YAuthQRStatus?, object>
|
||||||
|
{
|
||||||
|
public YGetQrStatus(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,
|
||||||
|
});
|
||||||
|
}
|
||||||
20
YandexMusic.API/Requests/Passport/YGetSessionBuilder.cs
Normal file
20
YandexMusic.API/Requests/Passport/YGetSessionBuilder.cs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
using System.Net;
|
||||||
|
using System.Net.Http.Json;
|
||||||
|
using YandexMusic.API.Models.Passport;
|
||||||
|
|
||||||
|
namespace YandexMusic.API.Requests.Passport;
|
||||||
|
|
||||||
|
internal class YGetSessionBuilder : YPassportRequestBuilder<YPassportSession?, object>
|
||||||
|
{
|
||||||
|
public YGetSessionBuilder(YandexMusicApi api) : base(api) { }
|
||||||
|
|
||||||
|
protected override string Method => WebRequestMethods.Http.Post;
|
||||||
|
protected override string PathTemplate => "pwl-yandex/api/passport/sessions/get_session";
|
||||||
|
|
||||||
|
protected override HttpContent? GetContent(object _)
|
||||||
|
{
|
||||||
|
var data = new { track_id = Api.Storage.AuthToken.TrackId };
|
||||||
|
return JsonContent.Create(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
using System.Net;
|
||||||
|
using System.Net.Http.Json;
|
||||||
|
using YandexMusic.API.Models.Passport;
|
||||||
|
|
||||||
|
namespace YandexMusic.API.Requests.Passport;
|
||||||
|
|
||||||
|
internal class YMultiStepPasswordBuilder : YPassportRequestBuilder<YPassportUser?, string>
|
||||||
|
{
|
||||||
|
public YMultiStepPasswordBuilder(YandexMusicApi api) : base(api) { }
|
||||||
|
|
||||||
|
protected override string Method => WebRequestMethods.Http.Post;
|
||||||
|
protected override string PathTemplate => "pwl-yandex/api/passport/auth/multistep_password";
|
||||||
|
|
||||||
|
protected override HttpContent? GetContent(string password)
|
||||||
|
{
|
||||||
|
var data = new
|
||||||
|
{
|
||||||
|
track_id = Api.Storage.AuthToken.TrackId,
|
||||||
|
password
|
||||||
|
};
|
||||||
|
return JsonContent.Create(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
35
YandexMusic.API/Requests/Passport/YMultistepStartBuilder.cs
Normal file
35
YandexMusic.API/Requests/Passport/YMultistepStartBuilder.cs
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
using System.Net;
|
||||||
|
using System.Net.Http.Json;
|
||||||
|
using YandexMusic.API.Models.Passport;
|
||||||
|
|
||||||
|
namespace YandexMusic.API.Requests.Passport;
|
||||||
|
|
||||||
|
internal class YMultistepStartBuilder : YPassportRequestBuilder<YMultistepStart?, string>
|
||||||
|
{
|
||||||
|
public YMultistepStartBuilder(YandexMusicApi api) : base(api) { }
|
||||||
|
|
||||||
|
protected override string Method => WebRequestMethods.Http.Post;
|
||||||
|
protected override string PathTemplate => "pwl-yandex/api/passport/auth/multistep_start";
|
||||||
|
|
||||||
|
protected override HttpContent? GetContent(string login)
|
||||||
|
{
|
||||||
|
var data = new
|
||||||
|
{
|
||||||
|
login,
|
||||||
|
track_id = Api.Storage.AuthToken.TrackId,
|
||||||
|
display_language = "ru",
|
||||||
|
retpath = string.Empty,
|
||||||
|
can_send_push_code = true,
|
||||||
|
check_for_xtokens_for_pictures = false,
|
||||||
|
force_check_for_protocols = true,
|
||||||
|
app_id = "ru.yandex.music",
|
||||||
|
am_version_name = "7.50.2(750024597)",
|
||||||
|
app_platform = "android",
|
||||||
|
app_version_name = "2026.02.3 #135rur",
|
||||||
|
device_id = Api.Storage.DeviceId,
|
||||||
|
device_connection_type = "9"
|
||||||
|
};
|
||||||
|
|
||||||
|
return JsonContent.Create(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
23
YandexMusic.API/Requests/Passport/YRfcOtpBuilder.cs
Normal file
23
YandexMusic.API/Requests/Passport/YRfcOtpBuilder.cs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
using System.Net;
|
||||||
|
using System.Net.Http.Json;
|
||||||
|
using YandexMusic.API.Models.Passport;
|
||||||
|
|
||||||
|
namespace YandexMusic.API.Requests.Passport;
|
||||||
|
|
||||||
|
internal class YRfcOtpBuilder : YPassportRequestBuilder<YPassportUser?, string>
|
||||||
|
{
|
||||||
|
public YRfcOtpBuilder(YandexMusicApi api) : base(api) { }
|
||||||
|
|
||||||
|
protected override string Method => WebRequestMethods.Http.Post;
|
||||||
|
protected override string PathTemplate => "pwl-yandex/api/passport/auth/rfc-otp";
|
||||||
|
|
||||||
|
protected override HttpContent? GetContent(string otp)
|
||||||
|
{
|
||||||
|
var data = new
|
||||||
|
{
|
||||||
|
track_id = Api.Storage.AuthToken.TrackId,
|
||||||
|
otp
|
||||||
|
};
|
||||||
|
return JsonContent.Create(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
26
YandexMusic.API/Requests/Passport/YSendPushBuilder.cs
Normal file
26
YandexMusic.API/Requests/Passport/YSendPushBuilder.cs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
using System.Net;
|
||||||
|
using System.Net.Http.Json;
|
||||||
|
using YandexMusic.API.Models.Passport;
|
||||||
|
|
||||||
|
namespace YandexMusic.API.Requests.Passport;
|
||||||
|
|
||||||
|
internal class YSendPushBuilder : YPassportRequestBuilder<YSendPushResult?, string>
|
||||||
|
{
|
||||||
|
public YSendPushBuilder(YandexMusicApi api) : base(api) { }
|
||||||
|
|
||||||
|
protected override string Method => WebRequestMethods.Http.Post;
|
||||||
|
protected override string PathTemplate => "pwl-yandex/api/passport/auth/suggest-send-push";
|
||||||
|
|
||||||
|
protected override HttpContent? GetContent(string phone)
|
||||||
|
{
|
||||||
|
var data = new
|
||||||
|
{
|
||||||
|
track_id = Api.Storage.AuthToken.TrackId,
|
||||||
|
phone_number = phone,
|
||||||
|
can_use_anmon = true,
|
||||||
|
force_show_code_in_notification = "1",
|
||||||
|
country = Api.Storage.Country
|
||||||
|
};
|
||||||
|
return JsonContent.Create(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
23
YandexMusic.API/Requests/Passport/YSuggestByPhoneBuilder.cs
Normal file
23
YandexMusic.API/Requests/Passport/YSuggestByPhoneBuilder.cs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
using System.Net;
|
||||||
|
using System.Net.Http.Json;
|
||||||
|
using YandexMusic.API.Models.Passport;
|
||||||
|
|
||||||
|
namespace YandexMusic.API.Requests.Passport;
|
||||||
|
|
||||||
|
internal class YSuggestByPhoneBuilder : YPassportRequestBuilder<YSuggestByPhoneResult?, object>
|
||||||
|
{
|
||||||
|
public YSuggestByPhoneBuilder(YandexMusicApi api) : base(api) { }
|
||||||
|
|
||||||
|
protected override string Method => WebRequestMethods.Http.Post;
|
||||||
|
protected override string PathTemplate => "pwl-yandex/api/passport/suggest/by_phone";
|
||||||
|
|
||||||
|
protected override HttpContent? GetContent(object _)
|
||||||
|
{
|
||||||
|
var data = new
|
||||||
|
{
|
||||||
|
track_id = Api.Storage.AuthToken.TrackId,
|
||||||
|
can_use_anmon = true
|
||||||
|
};
|
||||||
|
return JsonContent.Create(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
using System.Net;
|
||||||
|
using System.Net.Http.Json;
|
||||||
|
using YandexMusic.API.Models.Passport;
|
||||||
|
|
||||||
|
namespace YandexMusic.API.Requests.Passport;
|
||||||
|
|
||||||
|
internal class YValidatePhoneNumberBuilder : YPassportRequestBuilder<YValidatePhoneNumberResult?, string>
|
||||||
|
{
|
||||||
|
public YValidatePhoneNumberBuilder(YandexMusicApi api) : base(api) { }
|
||||||
|
|
||||||
|
protected override string Method => WebRequestMethods.Http.Post;
|
||||||
|
protected override string PathTemplate => "pwl-yandex/api/passport/validate/phone_number";
|
||||||
|
|
||||||
|
protected override HttpContent? GetContent(string phone)
|
||||||
|
{
|
||||||
|
var data = new
|
||||||
|
{
|
||||||
|
track_id = Api.Storage.AuthToken.TrackId,
|
||||||
|
phone_number = phone,
|
||||||
|
country = Api.Storage.Country
|
||||||
|
};
|
||||||
|
return JsonContent.Create(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
using System.Net;
|
||||||
|
using System.Net.Http.Json;
|
||||||
|
using YandexMusic.API.Models.Passport;
|
||||||
|
|
||||||
|
namespace YandexMusic.API.Requests.Passport;
|
||||||
|
|
||||||
|
internal class YValidateSquatterBuilder : YPassportRequestBuilder<YValidateSquatter?, string>
|
||||||
|
{
|
||||||
|
public YValidateSquatterBuilder(YandexMusicApi api) : base(api) { }
|
||||||
|
|
||||||
|
protected override string Method => WebRequestMethods.Http.Post;
|
||||||
|
protected override string PathTemplate => "pwl-yandex/api/passport/validate/squatter";
|
||||||
|
|
||||||
|
protected override HttpContent? GetContent(string phone)
|
||||||
|
{
|
||||||
|
var data = new
|
||||||
|
{
|
||||||
|
track_id = Api.Storage.AuthToken.TrackId,
|
||||||
|
phone_number = phone,
|
||||||
|
scenario = "auth",
|
||||||
|
can_use_anmon = true
|
||||||
|
};
|
||||||
|
return JsonContent.Create(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,11 +9,11 @@ namespace YandexMusic.API.Requests.Track;
|
|||||||
internal class YStorageDownloadFileBuilder : YJsonRequestBuilder<YStorageDownloadFile?, string>
|
internal class YStorageDownloadFileBuilder : YJsonRequestBuilder<YStorageDownloadFile?, string>
|
||||||
{
|
{
|
||||||
public YStorageDownloadFileBuilder(YandexMusicApi api) : base(api) { }
|
public YStorageDownloadFileBuilder(YandexMusicApi api) : base(api) { }
|
||||||
protected override string BaseUrl => ""; // не используется, т.к. URL берётся из параметра
|
protected override string BaseUrl => "{src}"; // не используется, т.к. URL берётся из параметра
|
||||||
|
|
||||||
protected override string Method => WebRequestMethods.Http.Get;
|
protected override string Method => WebRequestMethods.Http.Get;
|
||||||
|
|
||||||
protected override string PathTemplate => "{src}";
|
protected override string PathTemplate => "";
|
||||||
|
|
||||||
protected override Dictionary<string, string> GetSubstitutions(string src)
|
protected override Dictionary<string, string> GetSubstitutions(string src)
|
||||||
=> new() { { "src", src.Split('?')[0] } };
|
=> new() { { "src", src.Split('?')[0] } };
|
||||||
@@ -26,7 +26,7 @@ internal class YStorageDownloadFileBuilder : YJsonRequestBuilder<YStorageDownloa
|
|||||||
foreach (var param in parts[1].Split('&'))
|
foreach (var param in parts[1].Split('&'))
|
||||||
{
|
{
|
||||||
var kv = param.Split('=');
|
var kv = param.Split('=');
|
||||||
if (kv.Length == 2) query.Add(kv[0], kv[1]);
|
if (kv.Length >= 2) query.Add(kv[0], kv[1]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return query;
|
return query;
|
||||||
|
|||||||
@@ -8,11 +8,11 @@ namespace YandexMusic.API.Requests.Ugc;
|
|||||||
internal class YUgcUploadBuilder : YJsonRequestBuilder<YResponse<string>?, (string postTargetLink, byte[] fileBytes)>
|
internal class YUgcUploadBuilder : YJsonRequestBuilder<YResponse<string>?, (string postTargetLink, byte[] fileBytes)>
|
||||||
{
|
{
|
||||||
public YUgcUploadBuilder(YandexMusicApi api) : base(api) { }
|
public YUgcUploadBuilder(YandexMusicApi api) : base(api) { }
|
||||||
protected override string BaseUrl => "";
|
protected override string BaseUrl => "{postTargetLink}";
|
||||||
|
|
||||||
protected override string Method => WebRequestMethods.Http.Post;
|
protected override string Method => WebRequestMethods.Http.Post;
|
||||||
|
|
||||||
protected override string PathTemplate => "{postTargetLink}";
|
protected override string PathTemplate => "";
|
||||||
|
|
||||||
protected override Dictionary<string, string> GetSubstitutions((string postTargetLink, byte[] fileBytes) tuple)
|
protected override Dictionary<string, string> GetSubstitutions((string postTargetLink, byte[] fileBytes) tuple)
|
||||||
=> new() { { "postTargetLink", tuple.postTargetLink } };
|
=> new() { { "postTargetLink", tuple.postTargetLink } };
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ namespace YandexMusic.API;
|
|||||||
public class YandexMusicApi
|
public class YandexMusicApi
|
||||||
{
|
{
|
||||||
/// <summary>HttpClient, используемый для всех запросов.</summary>
|
/// <summary>HttpClient, используемый для всех запросов.</summary>
|
||||||
public HttpClient HttpClient { get; }
|
internal HttpClient HttpClient { get; }
|
||||||
/// <summary>Хранилище данных авторизации.</summary>
|
/// <summary>Хранилище данных авторизации.</summary>
|
||||||
public AuthStorage Storage { get; }
|
internal AuthStorage Storage { get; }
|
||||||
|
|
||||||
/// <summary>API для работы с альбомами.</summary>
|
/// <summary>API для работы с альбомами.</summary>
|
||||||
public YAlbumAPI Album { get; internal set; } = null!;
|
public YAlbumAPI Album { get; internal set; } = null!;
|
||||||
@@ -33,11 +33,13 @@ public class YandexMusicApi
|
|||||||
/// <summary>API для работы с очередями.</summary>
|
/// <summary>API для работы с очередями.</summary>
|
||||||
public YQueueAPI Queue { get; internal set; } = null!;
|
public YQueueAPI Queue { get; internal set; } = null!;
|
||||||
/// <summary>API для работы с пользователем и авторизацией.</summary>
|
/// <summary>API для работы с пользователем и авторизацией.</summary>
|
||||||
public YUserAPI User { get; internal set; } = null!;
|
public YAuthAPI Auth { get; internal set; } = null!;
|
||||||
/// <summary>API для загрузки пользовательского контента.</summary>
|
/// <summary>API для загрузки пользовательского контента.</summary>
|
||||||
public YUgcAPI UserGeneratedContent { get; internal set; } = null!;
|
public YUgcAPI UserGeneratedContent { get; internal set; } = null!;
|
||||||
/// <summary>API для работы с протоколом Ynison (WebSocket).</summary>
|
/// <summary>API для работы с протоколом Ynison (WebSocket).</summary>
|
||||||
public YYnisonAPI Ynison { get; internal set; } = null!;
|
public YYnisonAPI Ynison { get; internal set; } = null!;
|
||||||
|
/// <summary>API для работы с яндекс пасспорт.</summary>
|
||||||
|
public YPassportAPI Passport { get; internal set; } = null!;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Инициализирует новый экземпляр API.
|
/// Инициализирует новый экземпляр API.
|
||||||
@@ -49,19 +51,20 @@ public class YandexMusicApi
|
|||||||
HttpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
|
HttpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
|
||||||
Storage = storage ?? throw new ArgumentNullException(nameof(storage));
|
Storage = storage ?? throw new ArgumentNullException(nameof(storage));
|
||||||
|
|
||||||
Album = new YAlbumAPI(this);
|
Album = new(this);
|
||||||
Artist = new YArtistAPI(this);
|
Artist = new(this);
|
||||||
Label = new YLabelAPI(this);
|
Label = new(this);
|
||||||
Landing = new YLandingAPI(this);
|
Landing = new(this);
|
||||||
Library = new YLibraryAPI(this);
|
Library = new(this);
|
||||||
Playlist = new YPlaylistAPI(this);
|
Playlist = new(this);
|
||||||
Pins = new YPinsAPI(this);
|
Pins = new(this);
|
||||||
Radio = new YRadioAPI(this);
|
Radio = new(this);
|
||||||
Search = new YSearchAPI(this);
|
Search = new(this);
|
||||||
Track = new YTrackAPI(this);
|
Track = new(this);
|
||||||
Queue = new YQueueAPI(this);
|
Queue = new(this);
|
||||||
User = new YUserAPI(this);
|
Auth = new(this);
|
||||||
UserGeneratedContent = new YUgcAPI(this);
|
UserGeneratedContent = new(this);
|
||||||
Ynison = new YYnisonAPI(this);
|
Ynison = new(this);
|
||||||
|
Passport = new(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<Solution>
|
<Solution>
|
||||||
<Project Path="YaMusicCli/YaMusicCli.csproj" Id="5cce354e-7517-4a94-9584-197daa3ad6a4" />
|
<Project Path="YaMusicCli/YaMusicCli.csproj" Id="5cce354e-7517-4a94-9584-197daa3ad6a4" />
|
||||||
<Project Path="YandexMusic.API/YandexMusic.API.csproj" />
|
<Project Path="YandexMusic.API/YandexMusic.API.csproj" />
|
||||||
<Project Path="YandexMusic/YandexMusic.csproj" Id="044fcef4-86d2-4cc9-9f7e-a577c19ae5c3" />
|
<Project Path="YandexMusic/YandexMusic.csproj" />
|
||||||
</Solution>
|
</Solution>
|
||||||
|
|||||||
@@ -3,18 +3,6 @@ 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;
|
||||||
using YandexMusic.API.Models.Album;
|
|
||||||
using YandexMusic.API.Models.Artist;
|
|
||||||
using YandexMusic.API.Models.Common;
|
|
||||||
using YandexMusic.API.Models.Feed;
|
|
||||||
using YandexMusic.API.Models.Landing;
|
|
||||||
using YandexMusic.API.Models.Landing.Entity.Entities.Context;
|
|
||||||
using YandexMusic.API.Models.Library;
|
|
||||||
using YandexMusic.API.Models.Playlist;
|
|
||||||
using YandexMusic.API.Models.Queue;
|
|
||||||
using YandexMusic.API.Models.Radio;
|
|
||||||
using YandexMusic.API.Models.Search;
|
|
||||||
using YandexMusic.API.Models.Track;
|
|
||||||
|
|
||||||
namespace YandexMusic;
|
namespace YandexMusic;
|
||||||
|
|
||||||
@@ -41,6 +29,9 @@ public class YandexMusicClient : IDisposable
|
|||||||
/// <summary>HttpClient, используемый клиентом (можно получить для настройки кук).</summary>
|
/// <summary>HttpClient, используемый клиентом (можно получить для настройки кук).</summary>
|
||||||
public HttpClient HttpClient => _httpClient;
|
public HttpClient HttpClient => _httpClient;
|
||||||
|
|
||||||
|
/// <summary>API Яндекс Музыки.</summary>
|
||||||
|
public YandexMusicApi Api => _api;
|
||||||
|
|
||||||
/// <summary>Создаёт новый экземпляр клиента с собственным HttpClient.</summary>
|
/// <summary>Создаёт новый экземпляр клиента с собственным HttpClient.</summary>
|
||||||
public YandexMusicClient(
|
public YandexMusicClient(
|
||||||
CookieContainer? cookieContainer = null,
|
CookieContainer? cookieContainer = null,
|
||||||
@@ -78,348 +69,9 @@ public class YandexMusicClient : IDisposable
|
|||||||
_api = new YandexMusicApi(_httpClient, _storage);
|
_api = new YandexMusicApi(_httpClient, _storage);
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Авторизация
|
|
||||||
|
|
||||||
/// <summary>Авторизация по готовому OAuth-токену.</summary>
|
|
||||||
public async Task<bool> Authorize(string token)
|
|
||||||
{
|
|
||||||
await _api.User.AuthorizeAsync(token);
|
|
||||||
return _storage.IsAuthorized;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Создание сеанса и получение доступных методов авторизации.</summary>
|
|
||||||
public Task<YAuthTypes?> CreateAuthSession(string userName)
|
|
||||||
=> _api.User.CreateAuthSessionAsync(userName);
|
|
||||||
|
|
||||||
/// <summary>Получение ссылки на QR-код для авторизации.</summary>
|
|
||||||
public Task<string?> GetAuthQRLink()
|
|
||||||
=> _api.User.GetAuthQRLinkAsync();
|
|
||||||
|
|
||||||
/// <summary>Проверка состояния сканирования QR-кода.</summary>
|
|
||||||
public Task<YAuthQRStatus?> CheckQRStatusAsync()
|
|
||||||
=> _api.User.CheckQRStatusAsync();
|
|
||||||
|
|
||||||
/// <summary>Авторизация по QR-коду (после сканирования).</summary>
|
|
||||||
public Task<YAuthQRSession?> AuthorizeByQR()
|
|
||||||
=> _api.User.AuthorizeByQRAsync();
|
|
||||||
|
|
||||||
/// <summary>Получение капчи.</summary>
|
|
||||||
public Task<YAuthCaptcha?> GetCaptcha()
|
|
||||||
=> _api.User.GetCaptchaAsync();
|
|
||||||
|
|
||||||
/// <summary>Авторизация с вводом капчи.</summary>
|
|
||||||
public Task<YAuthBase?> AuthorizeByCaptcha(string captcha)
|
|
||||||
=> _api.User.AuthorizeByCaptchaAsync(captcha);
|
|
||||||
|
|
||||||
/// <summary>Запрос письма для авторизации.</summary>
|
|
||||||
public Task<YAuthLetter?> GetAuthLetter()
|
|
||||||
=> _api.User.GetAuthLetterAsync();
|
|
||||||
|
|
||||||
/// <summary>Подтверждение авторизации по письму.</summary>
|
|
||||||
public Task<bool> AuthorizeByLetter()
|
|
||||||
=> _api.User.AuthorizeByLetterAsync();
|
|
||||||
|
|
||||||
/// <summary>Авторизация по паролю приложения.</summary>
|
|
||||||
public Task<YAuthBase?> AuthorizeByAppPassword(string password)
|
|
||||||
=> _api.User.AuthorizeByAppPasswordAsync(password);
|
|
||||||
|
|
||||||
/// <summary>Получение AccessToken после успешной авторизации.</summary>
|
|
||||||
public Task<YAccessToken?> GetAccessToken()
|
|
||||||
=> _api.User.GetAccessTokenAsync();
|
|
||||||
|
|
||||||
/// <summary>Получение информации о пользователе через логин Яндекса.</summary>
|
|
||||||
public Task<YLoginInfo?> GetLoginInfo()
|
|
||||||
=> _api.User.GetLoginInfoAsync();
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Треки
|
|
||||||
|
|
||||||
/// <summary>Получает трек по идентификатору.</summary>
|
|
||||||
public async Task<YTrack?> GetTrackAsync(string id)
|
|
||||||
=> (await _api.Track.GetAsync(id));
|
|
||||||
|
|
||||||
/// <summary>Получает список треков по идентификаторам.</summary>
|
|
||||||
public async Task<List<YTrack>> GetTracksAsync(IEnumerable<string> ids)
|
|
||||||
=> (await _api.Track.GetAsync(ids)) ?? new List<YTrack>();
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Альбомы
|
|
||||||
|
|
||||||
/// <summary>Получает альбом по идентификатору.</summary>
|
|
||||||
public Task<YAlbum?> GetAlbumAsync(string id)
|
|
||||||
=> _api.Album.GetAsync(id);
|
|
||||||
|
|
||||||
/// <summary>Получает список альбомов по идентификаторам.</summary>
|
|
||||||
public async Task<List<YAlbum>> GetAlbumsAsync(IEnumerable<string> ids)
|
|
||||||
=> (await _api.Album.GetAsync(ids)) ?? new List<YAlbum>();
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Главная страница
|
|
||||||
|
|
||||||
/// <summary>Получает персональные блоки лендинга.</summary>
|
|
||||||
public Task<YLanding?> GetLandingAsync(params YLandingBlockType[] blocks)
|
|
||||||
=> _api.Landing.GetAsync(blocks);
|
|
||||||
|
|
||||||
/// <summary>Получает ленту событий.</summary>
|
|
||||||
public Task<YFeed?> GetFeedAsync()
|
|
||||||
=> _api.Landing.GetFeedAsync();
|
|
||||||
|
|
||||||
/// <summary>Получает детский лендинг.</summary>
|
|
||||||
public Task<YChildrenLanding?> GetChildrenLandingAsync()
|
|
||||||
=> _api.Landing.GetChildrenLandingAsync();
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Исполнители
|
|
||||||
|
|
||||||
/// <summary>Получает информацию об исполнителе.</summary>
|
|
||||||
public Task<YArtistBriefInfo?> GetArtistAsync(string id)
|
|
||||||
=> _api.Artist.GetAsync(id);
|
|
||||||
|
|
||||||
/// <summary>Получает список исполнителей.</summary>
|
|
||||||
public async Task<List<YArtist>> GetArtistsAsync(IEnumerable<string> ids)
|
|
||||||
=> (await _api.Artist.GetAsync(ids)) ?? new List<YArtist>();
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Плейлисты
|
|
||||||
|
|
||||||
/// <summary>Получает плейлист по пользователю и идентификатору.</summary>
|
|
||||||
public Task<YPlaylist?> GetPlaylistAsync(string user, string id)
|
|
||||||
=> _api.Playlist.GetAsync(user, id);
|
|
||||||
|
|
||||||
/// <summary>Получает плейлист по UUID.</summary>
|
|
||||||
public Task<YPlaylist?> GetPlaylistAsync(string uuid)
|
|
||||||
=> _api.Playlist.GetAsync(uuid);
|
|
||||||
|
|
||||||
/// <summary>Получает список плейлистов по списку пар (пользователь, идентификатор).</summary>
|
|
||||||
public async Task<List<YPlaylist>> GetPlaylistsAsync(IEnumerable<(string user, string id)> ids)
|
|
||||||
=> (await _api.Playlist.GetAsync(ids)) ?? new List<YPlaylist>();
|
|
||||||
|
|
||||||
/// <summary>Получает персональные плейлисты (с главной страницы).</summary>
|
|
||||||
public Task<List<YPlaylist>> GetPersonalPlaylistsAsync()
|
|
||||||
=> _api.Playlist.GetPersonalPlaylistsAsync();
|
|
||||||
|
|
||||||
/// <summary>Получает избранные плейлисты.</summary>
|
|
||||||
public async Task<List<YPlaylist>> GetFavoritesAsync()
|
|
||||||
=> (await _api.Playlist.FavoritesAsync()) ?? new List<YPlaylist>();
|
|
||||||
|
|
||||||
/// <summary>Получает плейлист «Дежавю».</summary>
|
|
||||||
public Task<YPlaylist?> GetDejaVuAsync()
|
|
||||||
=> _api.Playlist.DejaVuAsync();
|
|
||||||
|
|
||||||
/// <summary>Получает плейлист «Тайник».</summary>
|
|
||||||
public Task<YPlaylist?> GetMissedAsync()
|
|
||||||
=> _api.Playlist.MissedAsync();
|
|
||||||
|
|
||||||
/// <summary>Получает плейлист дня.</summary>
|
|
||||||
public Task<YPlaylist?> GetOfTheDayAsync()
|
|
||||||
=> _api.Playlist.OfTheDayAsync();
|
|
||||||
|
|
||||||
/// <summary>Получает плейлист «Кинопоиск».</summary>
|
|
||||||
public Task<YPlaylist?> GetKinopoiskAsync()
|
|
||||||
=> _api.Playlist.KinopoiskAsync();
|
|
||||||
|
|
||||||
/// <summary>Получает плейлист «Премьера».</summary>
|
|
||||||
public Task<YPlaylist?> GetPremiereAsync()
|
|
||||||
=> _api.Playlist.PremiereAsync();
|
|
||||||
|
|
||||||
/// <summary>Создаёт новый плейлист с заданным именем.</summary>
|
|
||||||
public Task<YPlaylist?> CreatePlaylistAsync(string name)
|
|
||||||
=> _api.Playlist.CreateAsync(name);
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Поиск
|
|
||||||
|
|
||||||
/// <summary>Выполняет поиск.</summary>
|
|
||||||
public Task<YSearch?> SearchAsync(string searchText, YSearchType searchType, int page = 0, int pageSize = 20)
|
|
||||||
=> _api.Search.SearchAsync(searchText, searchType, page, pageSize);
|
|
||||||
|
|
||||||
/// <summary>Получает подсказки для поискового запроса.</summary>
|
|
||||||
public Task<YSearchSuggest?> GetSearchSuggestionsAsync(string searchText)
|
|
||||||
=> _api.Search.GetSearchSuggestionsAsync(searchText);
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Библиотека
|
|
||||||
|
|
||||||
/// <summary>Получает лайкнутые треки.</summary>
|
|
||||||
public async Task<List<YTrack>> GetLikedTracksAsync()
|
|
||||||
{
|
|
||||||
var likes = await _api.Library.GetLikedTracksAsync();
|
|
||||||
var ids = likes?.Library?.Tracks.Select(t => t.Id).ToArray() ?? Array.Empty<string>();
|
|
||||||
if (ids.Length == 0) return new List<YTrack>();
|
|
||||||
var tracks = await _api.Track.GetAsync(ids);
|
|
||||||
return tracks ?? new List<YTrack>();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Получает дизлайкнутые треки.</summary>
|
|
||||||
public async Task<List<YTrack>> GetDislikedTracksAsync()
|
|
||||||
{
|
|
||||||
var dislikes = await _api.Library.GetDislikedTracksAsync();
|
|
||||||
var ids = dislikes?.Library?.Tracks.Select(t => t.Id).ToArray() ?? Array.Empty<string>();
|
|
||||||
if (ids.Length == 0) return new List<YTrack>();
|
|
||||||
var tracks = await _api.Track.GetAsync(ids);
|
|
||||||
return tracks ?? new List<YTrack>();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Получает лайкнутые альбомы.</summary>
|
|
||||||
public async Task<List<YAlbum>> GetLikedAlbumsAsync()
|
|
||||||
{
|
|
||||||
var albums = await _api.Library.GetLikedAlbumsAsync();
|
|
||||||
var ids = albums?.Select(a => a.Id).ToArray() ?? Array.Empty<string>();
|
|
||||||
if (ids.Length == 0) return new List<YAlbum>();
|
|
||||||
var result = await _api.Album.GetAsync(ids);
|
|
||||||
return result ?? new List<YAlbum>();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Получает лайкнутых исполнителей.</summary>
|
|
||||||
public async Task<List<YArtist>> GetLikedArtistsAsync()
|
|
||||||
{
|
|
||||||
var artists = await _api.Library.GetLikedArtistsAsync();
|
|
||||||
var ids = artists?.Select(a => a.Id).ToArray() ?? Array.Empty<string>();
|
|
||||||
if (ids.Length == 0) return new List<YArtist>();
|
|
||||||
var result = await _api.Artist.GetAsync(ids);
|
|
||||||
return result ?? new List<YArtist>();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Получает дизлайкнутых исполнителей.</summary>
|
|
||||||
public async Task<List<YArtist>> GetDislikedArtistsAsync()
|
|
||||||
{
|
|
||||||
var artists = await _api.Library.GetDislikedArtistsAsync();
|
|
||||||
var ids = artists?.Select(a => a.Id).ToArray() ?? Array.Empty<string>();
|
|
||||||
if (ids.Length == 0) return new List<YArtist>();
|
|
||||||
var result = await _api.Artist.GetAsync(ids);
|
|
||||||
return result ?? new List<YArtist>();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Получает лайкнутые плейлисты.</summary>
|
|
||||||
public async Task<List<YPlaylist>> GetLikedPlaylistsAsync()
|
|
||||||
{
|
|
||||||
var playlists = await _api.Library.GetLikedPlaylistsAsync();
|
|
||||||
var ids = playlists?
|
|
||||||
.Select(p => (p.Playlist.Uid, p.Playlist.Kind))
|
|
||||||
.ToArray() ?? Array.Empty<(string, string)>();
|
|
||||||
if (ids.Length == 0) return new List<YPlaylist>();
|
|
||||||
var result = await _api.Playlist.GetAsync(ids);
|
|
||||||
return result ?? new List<YPlaylist>();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Получает список недавно прослушанных треков.</summary>
|
|
||||||
public async Task<List<YRecentlyListened>> GetRecentlyListenedAsync(
|
|
||||||
IEnumerable<YPlayContextType> contextTypes,
|
|
||||||
int trackCount = 50,
|
|
||||||
int contextCount = 10)
|
|
||||||
{
|
|
||||||
var response = await _api.Library.GetRecentlyListenedAsync(contextTypes, trackCount, contextCount);
|
|
||||||
return response?.Contexts ?? new List<YRecentlyListened>();
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Радио
|
|
||||||
|
|
||||||
/// <summary>Получает список рекомендованных радиостанций.</summary>
|
|
||||||
public async Task<List<YStation>> GetRadioDashboardAsync()
|
|
||||||
{
|
|
||||||
var dashboard = await _api.Radio.GetStationsDashboardAsync();
|
|
||||||
return dashboard?.Stations ?? new List<YStation>();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Получает список всех радиостанций.</summary>
|
|
||||||
public async Task<List<YStation>> GetRadioStationsAsync()
|
|
||||||
=> (await _api.Radio.GetStationsAsync()) ?? new List<YStation>();
|
|
||||||
|
|
||||||
/// <summary>Получает информацию о радиостанции по идентификатору.</summary>
|
|
||||||
public async Task<YStation?> GetRadioStationAsync(YStationId id)
|
|
||||||
=> (await _api.Radio.GetStationAsync(id))?.FirstOrDefault();
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Очереди
|
|
||||||
|
|
||||||
/// <summary>Получает все очереди с разных устройств.</summary>
|
|
||||||
public Task<YQueueItemsContainer?> GetQueuesAsync(string? device = null)
|
|
||||||
=> _api.Queue.ListAsync(device);
|
|
||||||
|
|
||||||
/// <summary>Получает очередь по идентификатору.</summary>
|
|
||||||
public Task<YQueue?> GetQueueAsync(string queueId)
|
|
||||||
=> _api.Queue.GetAsync(queueId);
|
|
||||||
|
|
||||||
/// <summary>Создаёт новую очередь.</summary>
|
|
||||||
public Task<YNewQueue?> CreateQueueAsync(YQueue queue, string? device = null)
|
|
||||||
=> _api.Queue.CreateAsync(queue, device);
|
|
||||||
|
|
||||||
/// <summary>Обновляет позицию в очереди.</summary>
|
|
||||||
public Task<YUpdatedQueue?> UpdateQueuePositionAsync(string queueId, int currentIndex, bool isInteractive, string? device = null)
|
|
||||||
=> _api.Queue.UpdatePositionAsync(queueId, currentIndex, isInteractive, device);
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Загрузка треков (UGC)
|
|
||||||
|
|
||||||
/// <summary>Загружает трек из файла в плейлист.</summary>
|
|
||||||
public Task<string?> UploadTrackToPlaylistAsync(YPlaylist playlist, string fileName, string filePath)
|
|
||||||
=> _api.UserGeneratedContent.UploadTrackToPlaylistAsync(playlist, fileName, filePath);
|
|
||||||
|
|
||||||
/// <summary>Загружает трек из потока в плейлист.</summary>
|
|
||||||
public Task<string?> UploadTrackToPlaylistAsync(YPlaylist playlist, string fileName, Stream stream)
|
|
||||||
=> _api.UserGeneratedContent.UploadTrackToPlaylistAsync(playlist, fileName, stream);
|
|
||||||
|
|
||||||
/// <summary>Загружает трек из массива байтов в плейлист.</summary>
|
|
||||||
public Task<string?> UploadTrackToPlaylistAsync(YPlaylist playlist, string fileName, byte[] file)
|
|
||||||
=> _api.UserGeneratedContent.UploadTrackToPlaylistAsync(playlist, fileName, file);
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Лейблы
|
|
||||||
|
|
||||||
/// <summary>Получает альбомы лейбла с пагинацией.</summary>
|
|
||||||
public async Task<List<YAlbum>> GetAlbumsByLabelAsync(YLabel label, int page = 0)
|
|
||||||
=> (await _api.Label.GetAlbumsByLabelAsync(label, page))?.Albums ?? new List<YAlbum>();
|
|
||||||
|
|
||||||
/// <summary>Получает артистов лейбла с пагинацией.</summary>
|
|
||||||
public async Task<List<YArtist>> GetArtistsByLabelAsync(YLabel label, int page = 0)
|
|
||||||
=> (await _api.Label.GetArtistsByLabelAsync(label, page))?.Artists ?? new List<YArtist>();
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Ynison (WebSocket плеер)
|
|
||||||
|
|
||||||
/// <summary>Подключается к Ynison и запускает синхронизацию состояния плеера.</summary>
|
|
||||||
public async Task ConnectYnisonAsync()
|
|
||||||
{
|
|
||||||
if (_player == null)
|
|
||||||
_player = _api.Ynison.GetPlayer();
|
|
||||||
await _player.ConnectAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Отключается от Ynison.</summary>
|
|
||||||
public async Task DisconnectYnisonAsync()
|
|
||||||
{
|
|
||||||
if (_player != null)
|
|
||||||
await _player.DisconnectAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region IDisposable
|
|
||||||
|
|
||||||
private bool _disposed;
|
|
||||||
|
|
||||||
/// <summary>Освобождает ресурсы.</summary>
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
if (_disposed) return;
|
_httpClient?.Dispose();
|
||||||
_player?.Dispose();
|
_player?.Dispose();
|
||||||
_httpClient.Dispose();
|
|
||||||
_disposed = true;
|
|
||||||
GC.SuppressFinalize(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user