8 Commits

Author SHA1 Message Date
526353d679 Переделано воспроизведение аудио
All checks were successful
Release / pack-and-publish (release) Successful in 36s
2026-04-21 11:14:36 +03:00
eb1eba0162 Полный рефакторинг api. Вынесено отдельно api passport 2026-04-20 23:30:01 +03:00
FrigaT
34261d02a9 Авторизация через паспорт
All checks were successful
Release / pack-and-publish (release) Successful in 31s
2026-04-20 15:58:27 +03:00
FrigaT
5f761d4fe8 Добавлена авторизация через паспорт 2026-04-20 15:56:38 +03:00
FrigaT
b6f78da9c8 dispose
All checks were successful
Release / pack-and-publish (release) Successful in 37s
2026-04-20 14:47:43 +03:00
FrigaT
0bbaac5689 Переделан способ авторизации по qr
All checks were successful
Release / pack-and-publish (release) Successful in 1m5s
2026-04-20 14:31:47 +03:00
FrigaT
a7caf829d3 Открыл AuthToken
All checks were successful
Release / pack-and-publish (release) Successful in 34s
2026-04-19 20:19:41 +03:00
FrigaT
add7f08215 Добавлен вывод AuthStorage. Спрятаны внутренние api запросы
All checks were successful
Release / pack-and-publish (release) Successful in 32s
2026-04-19 17:41:30 +03:00
117 changed files with 1372 additions and 728 deletions

View 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!);
}

View 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;
}
}

View File

@@ -26,8 +26,8 @@ public class YTrackAPI : YCommonAPI
return $"https://{host}/get-{codec}/{sign}/{ts}{path}";
}
public Task<YTrack?> GetAsync(string trackId)
=> GetAsync(trackId);
public async Task<YTrack?> GetAsync(string trackId)
=> (await GetAsync([trackId]))?.FirstOrDefault();
public Task<List<YTrack>?> GetAsync(IEnumerable<string> trackIds)
=> new YGetTracksBuilder(Api).ExecuteAsync(trackIds);

View File

@@ -1,159 +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.AuthToken = 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;
var shortInfo = await new YGetShortAccountInfoBuilder(Api).ExecuteAsync(null!);
if (shortInfo?.Status != YAuthStatus.Ok || string.IsNullOrWhiteSpace(shortInfo.Uid))
throw new Exception("Не удалось подтвердить авторизацию");
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 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?> AuthorizeByQRAsync()
{
if (Api.Storage.AuthToken == null)
throw new Exception("Сессия не инициализирована");
var status = await new YGetAuthLoginQRBuilder(Api).ExecuteAsync(null!);
if (status?.Status == YAuthStatus.Ok && 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!);
}

View File

@@ -1,3 +1,4 @@
using System.Net;
using YandexMusic.API.Models.Account;
namespace YandexMusic.API.Common;
@@ -7,6 +8,15 @@ namespace YandexMusic.API.Common;
/// </summary>
public class AuthStorage
{
private CookieContainer _cookieContainer;
public AuthStorage(CookieContainer cookieContainer)
{
_cookieContainer = cookieContainer;
}
public CookieContainer CookieContainer => _cookieContainer;
/// <summary>
/// Флаг, указывающий, авторизован ли пользователь.
/// </summary>
@@ -35,7 +45,17 @@ public class AuthStorage
/// <summary>
/// Внутренние данные авторизации (CSRF, track_id и т.д.).
/// </summary>
internal YAuthToken AuthToken { get; set; } = new();
public YAuthToken? HeaderToken { get; set; } = new();
/// <summary>
/// Внутренние данные авторизации (CSRF, track_id и т.д.).
/// </summary>
public YAuthToken? AuthToken { get; set; } = new();
/// <summary>
/// Страна, используемая для авторизации (по умолчанию "ru"). Может влиять на язык интерфейса и доступные методы авторизации.
/// </summary>
public object Country { get; set; } = "ru";
/// <summary>
/// Устанавливает флаг авторизации и сохраняет информацию об аккаунте.

View File

@@ -3,7 +3,7 @@ using System.Text.Json.Serialization;
namespace YandexMusic.API.Converters;
public class IntToStringConverter : JsonConverter<string>
internal class IntToStringConverter : JsonConverter<string>
{
public override string Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{

View File

@@ -3,7 +3,7 @@ using System.Text.Json.Serialization;
namespace YandexMusic.API.Converters;
public class StringToIntConverter : JsonConverter<int>
internal class StringToIntConverter : JsonConverter<int>
{
public override int Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{

View File

@@ -1,6 +1,6 @@
using YandexMusic.API.Models.Album;
namespace YandexMusic.API.Extensions.API;
namespace YandexMusic.API;
/// <summary>
/// Методы-расширения для альбома.
@@ -15,7 +15,7 @@ public static class YAlbumExtensions
if (album.Volumes != null)
return album;
var result = await album.Context.API.Album.GetAsync(album.Id);
var result = await album.Context.Api.Album.GetAsync(album.Id);
return result ?? album;
}
@@ -23,11 +23,11 @@ public static class YAlbumExtensions
/// Добавляет альбом в список лайкнутых.
/// </summary>
public static async Task<string?> AddLikeAsync(this YAlbum album)
=> await album.Context.API.Library.AddAlbumLikeAsync(album);
=> await album.Context.Api.Library.AddAlbumLikeAsync(album);
/// <summary>
/// Удаляет альбом из списка лайкнутых.
/// </summary>
public static async Task<string?> RemoveLikeAsync(this YAlbum album)
=> await album.Context.API.Library.RemoveAlbumLikeAsync(album);
=> await album.Context.Api.Library.RemoveAlbumLikeAsync(album);
}

View File

@@ -1,7 +1,7 @@
using YandexMusic.API.Models.Artist;
using YandexMusic.API.Models.Track;
namespace YandexMusic.API.Extensions.API;
namespace YandexMusic.API;
/// <summary>
/// Методы-расширения для исполнителя.
@@ -12,29 +12,29 @@ public static class YArtistExtensions
/// Получает расширенную информацию об исполнителе.
/// </summary>
public static async Task<YArtistBriefInfo?> BriefInfoAsync(this YArtist artist)
=> await artist.Context.API.Artist.GetAsync(artist.Id);
=> await artist.Context.Api.Artist.GetAsync(artist.Id);
/// <summary>
/// Получает страницу треков исполнителя.
/// </summary>
public static async Task<YTracksPage?> GetTracksAsync(this YArtist artist, int page = 0, int pageSize = 20)
=> await artist.Context.API.Artist.GetTracksAsync(artist.Id, page, pageSize);
=> await artist.Context.Api.Artist.GetTracksAsync(artist.Id, page, pageSize);
/// <summary>
/// Получает все треки исполнителя.
/// </summary>
public static async Task<List<YTrack>?> GetAllTracksAsync(this YArtist artist)
=> (await artist.Context.API.Artist.GetAllTracksAsync(artist.Id))?.Tracks;
=> (await artist.Context.Api.Artist.GetAllTracksAsync(artist.Id))?.Tracks;
/// <summary>
/// Добавляет исполнителя в список лайкнутых.
/// </summary>
public static async Task<string?> AddLikeAsync(this YArtist artist)
=> await artist.Context.API.Library.AddArtistLikeAsync(artist);
=> await artist.Context.Api.Library.AddArtistLikeAsync(artist);
/// <summary>
/// Удаляет исполнителя из списка лайкнутых.
/// </summary>
public static async Task<string?> RemoveLikeAsync(this YArtist artist)
=> await artist.Context.API.Library.RemoveArtistLikeAsync(artist);
=> await artist.Context.Api.Library.RemoveArtistLikeAsync(artist);
}

View File

@@ -1,7 +1,7 @@
using YandexMusic.API.Models.Playlist;
using YandexMusic.API.Models.Track;
namespace YandexMusic.API.Extensions.API;
namespace YandexMusic.API;
/// <summary>
/// Методы-расширения для плейлиста.
@@ -18,44 +18,44 @@ public static class YPlaylistExtensions
{
if (playlist.Tracks != null)
return playlist;
return await playlist.Context.API.Playlist.GetAsync(playlist);
return await playlist.Context.Api.Playlist.GetAsync(playlist);
}
/// <summary>
/// Добавляет плейлист в список лайкнутых.
/// </summary>
public static async Task<string?> AddLikeAsync(this YPlaylist playlist)
=> await playlist.Context.API.Library.AddPlaylistLikeAsync(playlist);
=> await playlist.Context.Api.Library.AddPlaylistLikeAsync(playlist);
/// <summary>
/// Удаляет плейлист из списка лайкнутых.
/// </summary>
public static async Task<string?> RemoveLikeAsync(this YPlaylist playlist)
=> await playlist.Context.API.Library.RemovePlaylistLikeAsync(playlist);
=> await playlist.Context.Api.Library.RemovePlaylistLikeAsync(playlist);
/// <summary>
/// Переименовывает плейлист (только для владельца).
/// </summary>
public static async Task<YPlaylist?> RenameAsync(this YPlaylist playlist, string newName)
=> IsOwner(playlist) ? await playlist.Context.API.Playlist.RenameAsync(playlist, newName) : playlist;
=> IsOwner(playlist) ? await playlist.Context.Api.Playlist.RenameAsync(playlist, newName) : playlist;
/// <summary>
/// Удаляет плейлист (только для владельца).
/// </summary>
public static async Task<bool> DeleteAsync(this YPlaylist playlist)
=> IsOwner(playlist) && await playlist.Context.API.Playlist.DeleteAsync(playlist);
=> IsOwner(playlist) && await playlist.Context.Api.Playlist.DeleteAsync(playlist);
/// <summary>
/// Вставляет треки в начало плейлиста (только для владельца).
/// </summary>
public static async Task<YPlaylist?> InsertTracksAsync(this YPlaylist playlist, params YTrack[] tracks)
=> IsOwner(playlist) ? await playlist.Context.API.Playlist.InsertTracksAsync(playlist, tracks) : playlist;
=> IsOwner(playlist) ? await playlist.Context.Api.Playlist.InsertTracksAsync(playlist, tracks) : playlist;
/// <summary>
/// Удаляет треки из плейлиста (только для владельца).
/// </summary>
public static async Task<YPlaylist?> RemoveTracksAsync(this YPlaylist playlist, params YTrack[] tracks)
=> IsOwner(playlist) ? await playlist.Context.API.Playlist.DeleteTracksAsync(playlist, tracks) : playlist;
=> IsOwner(playlist) ? await playlist.Context.Api.Playlist.DeleteTracksAsync(playlist, tracks) : playlist;
/// <summary>
/// Загружает трек в плейлист (только для владельца).
@@ -63,7 +63,7 @@ public static class YPlaylistExtensions
public static async Task<bool> UploadTrackAsync(this YPlaylist playlist, string filePath, string fileName)
{
if (!IsOwner(playlist)) return false;
var result = await playlist.Context.API.UserGeneratedContent.UploadTrackToPlaylistAsync(playlist, fileName, filePath);
var result = await playlist.Context.Api.UserGeneratedContent.UploadTrackToPlaylistAsync(playlist, fileName, filePath);
return result == "CREATED";
}
}

View File

@@ -1,7 +1,7 @@
using YandexMusic.API.Models.Radio;
using YandexMusic.API.Models.Track;
namespace YandexMusic.API.Extensions.API;
namespace YandexMusic.API;
/// <summary>
/// Методы-расширения для радиостанции.
@@ -12,17 +12,17 @@ public static class YStationResultExtensions
/// Получает список треков для радиостанции.
/// </summary>
public static async Task<List<YSequenceItem>?> GetTracksAsync(this YStation station, string prevTrackId = "")
=> (await station.Context.API.Radio.GetStationTracksAsync(station, prevTrackId))?.Sequence;
=> (await station.Context.Api.Radio.GetStationTracksAsync(station, prevTrackId))?.Sequence;
/// <summary>
/// Устанавливает настройки станции.
/// </summary>
public static async Task<string?> SetSettings2Async(this YStation station, YStationSettings2 settings)
=> await station.Context.API.Radio.SetStationSettings2Async(station, settings);
=> await station.Context.Api.Radio.SetStationSettings2Async(station, settings);
/// <summary>
/// Отправляет обратную связь о прослушивании.
/// </summary>
public static Task<string?> SendFeedbackAsync(this YStation station, YStationFeedbackType type, YTrack? track = null, string batchId = "", double totalPlayedSeconds = 0)
=> station.Context.API.Radio.SendStationFeedbackAsync(station, type, track, batchId, totalPlayedSeconds);
=> station.Context.Api.Radio.SendStationFeedbackAsync(station, type, track, batchId, totalPlayedSeconds);
}

View File

@@ -1,6 +1,6 @@
using YandexMusic.API.Models.Track;
namespace YandexMusic.API.Extensions.API;
namespace YandexMusic.API;
/// <summary>
/// Методы-расширения для трека.
@@ -11,53 +11,53 @@ public static class YTrackExtensions
/// Получает прямую ссылку на скачивание трека.
/// </summary>
public static Task<string?> GetLinkAsync(this YTrack track)
=> track.Context.API.Track.GetFileLinkAsync(track);
=> track.Context.Api.Track.GetFileLinkAsync(track);
/// <summary>
/// Сохраняет трек в файл.
/// </summary>
public static Task SaveAsync(this YTrack track, string filePath)
=> track.Context.API.Track.ExtractToFileAsync(track, filePath);
=> track.Context.Api.Track.ExtractToFileAsync(track, filePath);
/// <summary>
/// Добавляет трек в список лайкнутых.
/// </summary>
public static async Task<int?> AddLikeAsync(this YTrack track)
=> await track.Context.API.Library.AddTrackLikeAsync(track);
=> await track.Context.Api.Library.AddTrackLikeAsync(track);
/// <summary>
/// Удаляет трек из списка лайкнутых.
/// </summary>
public static async Task<int?> RemoveLikeAsync(this YTrack track)
=> await track.Context.API.Library.RemoveTrackLikeAsync(track);
=> await track.Context.Api.Library.RemoveTrackLikeAsync(track);
/// <summary>
/// Добавляет трек в список дизлайкнутых.
/// </summary>
public static async Task<int?> AddDislikeAsync(this YTrack track)
=> await track.Context.API.Library.AddTrackDislikeAsync(track);
=> await track.Context.Api.Library.AddTrackDislikeAsync(track);
/// <summary>
/// Удаляет трек из списка дизлайкнутых.
/// </summary>
public static async Task<int?> RemoveDislikeAsync(this YTrack track)
=> await track.Context.API.Library.RemoveTrackDislikeAsync(track);
=> await track.Context.Api.Library.RemoveTrackDislikeAsync(track);
/// <summary>
/// Отправляет информацию о воспроизведении трека.
/// </summary>
public static Task<string?> SendPlayTrackInfoAsync(this YTrack track, string from, bool fromCache = false, string playId = "", string playlistId = "", double totalPlayedSeconds = 0, double endPositionSeconds = 0)
=> track.Context.API.Track.SendPlayTrackInfoAsync(track, from, fromCache, playId, playlistId, totalPlayedSeconds, endPositionSeconds);
=> track.Context.Api.Track.SendPlayTrackInfoAsync(track, from, fromCache, playId, playlistId, totalPlayedSeconds, endPositionSeconds);
/// <summary>
/// Получает дополнительную информацию о треке.
/// </summary>
public static async Task<YTrackSupplement?> SupplementAsync(this YTrack track)
=> await track.Context.API.Track.GetSupplementAsync(track);
=> await track.Context.Api.Track.GetSupplementAsync(track);
/// <summary>
/// Получает похожие треки.
/// </summary>
public static async Task<YTrackSimilar?> SimilarAsync(this YTrack track)
=> await track.Context.API.Track.GetSimilarAsync(track);
=> await track.Context.Api.Track.GetSimilarAsync(track);
}

View File

@@ -2,4 +2,4 @@
public class YAuthEmpty
{
}
}

View File

@@ -0,0 +1,9 @@
using System.Runtime.Serialization;
namespace YandexMusic.API.Models.Account;
public enum YAuthQrState
{
[EnumMember(Value = "otp_auth_finished")]
OtpAuthFinished,
}

View File

@@ -1,16 +1,13 @@
using System.Text.Json.Serialization;
namespace YandexMusic.API.Models.Account;
namespace YandexMusic.API.Models.Account;
public class YAuthToken
{
[JsonPropertyName("csfr_token")]
public string CsfrToken { get; set; }
[JsonPropertyName("track_id")]
public string TrackId { get; set; }
[JsonPropertyName("process_uuid")]
public string SessionTrackId { get; set; }
public string ProcessUuid { get; set; }
public Dictionary<string, string> Cookie { get; set; } = new();

View File

@@ -9,7 +9,7 @@ namespace YandexMusic.API.Models.Common;
public class YExecutionContext
{
/// <summary>Экземпляр основного API.</summary>
public YandexMusicApi API { get; internal set; } = null!;
public YandexMusicApi Api { get; internal set; } = null!;
/// <summary>Хранилище данных авторизации.</summary>
public AuthStorage Storage { get; internal set; } = null!;

View File

@@ -38,7 +38,7 @@ public class YExecutionContextConverter : JsonConverter<object>
var obj = JsonSerializer.Deserialize(ref reader, typeToConvert, innerOptions);
if (obj is YBaseModel baseModel)
{
baseModel.Context = new YExecutionContext { API = _api, Storage = _storage };
baseModel.Context = new YExecutionContext { Api = _api, Storage = _storage };
}
return obj;
}

View File

@@ -1,9 +1,16 @@
using System.Xml.Serialization;
namespace YandexMusic.API.Models.Common;
[XmlRoot("download-info")]
public class YStorageDownloadFile
{
[XmlElement("host")]
public string Host { get; set; }
[XmlElement("path")]
public string Path { get; set; }
[XmlElement("s")]
public string S { get; set; }
[XmlElement("ts")]
public string Ts { get; set; }
}

View File

@@ -1,8 +1,9 @@
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")]
public string TrackId { get; set; }

View File

@@ -1,12 +1,14 @@
using System.Text.Json.Serialization;
using YandexMusic.API.Models.Account;
namespace YandexMusic.API.Models.Account;
namespace YandexMusic.API.Models.Passport;
public class YAuthQRStatus : YAuthBase
public class YAuthQrSession
{
[JsonPropertyName("default_uid")]
public int DefaultUid { get; set; }
[JsonPropertyName("retpath")]
public string RetPath { get; set; }
[JsonPropertyName("track_id")]

View File

@@ -0,0 +1,12 @@
using System.Text.Json.Serialization;
namespace YandexMusic.API.Models.Passport;
public class YAuthQRStatus
{
[JsonPropertyName("state")]
public string? State { get; set; } = null;
[JsonPropertyName("trackId")]
public string TrackId { get; set; } = string.Empty;
}

View 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; }
}

View 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;
}

View 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; }
}

View 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; }
}

View 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;
}

View 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;
}

View 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;
}

View 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; }
}

View 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; }
}

View 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;
}

View 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();
}

View 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; }
}

View 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;
}

View File

@@ -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; }
}

View 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;
}

View File

@@ -3,7 +3,7 @@ using YandexMusic.API.Models.Account;
namespace YandexMusic.API.Requests.Account;
public class YGetAuthAppPasswordBuilder : YAuthRequestBuilder<YAuthBase?, string>
internal class YGetAuthAppPasswordBuilder : YPassportRequestBuilder<YAuthBase?, string>
{
public YGetAuthAppPasswordBuilder(YandexMusicApi api) : base(api) { }
protected override string Method => WebRequestMethods.Http.Post;

View File

@@ -3,7 +3,7 @@ using YandexMusic.API.Models.Account;
namespace YandexMusic.API.Requests.Account;
public class YGetAuthCaptchaBuilder : YAuthRequestBuilder<YAuthCaptcha?, object>
internal class YGetAuthCaptchaBuilder : YPassportRequestBuilder<YAuthCaptcha?, object>
{
public YGetAuthCaptchaBuilder(YandexMusicApi api) : base(api) { }
protected override string Method => WebRequestMethods.Http.Post;

View File

@@ -1,14 +1,55 @@
using System.Net;
using System.Net.Http.Headers;
using YandexMusic.API.Models.Account;
using YandexMusic.API.Requests.Common;
namespace YandexMusic.API.Requests.Account;
public class YGetAuthCookiesBuilder : YAuthRequestBuilder<YAccessToken?, object>
internal class YGetAuthCookiesBuilder : YPassportRequestBuilder<YAccessToken?, object>
{
public YGetAuthCookiesBuilder(YandexMusicApi api) : base(api) { }
protected override string Method => WebRequestMethods.Http.Post;
protected override string BaseUrl => YConstants.Endpoints.MobilePassportUrl;
protected override string PathTemplate => "1/bundle/oauth/token_by_sessionid";
protected override HttpContent? GetContent(object _)
=> new FormUrlEncodedContent(new Dictionary<string, string> { { "client_id", YConstants.XClientId }, { "client_secret", YConstants.XClientSecret } });
protected override void SetCustomHeaders(HttpRequestHeaders headers)
{
base.SetCustomHeaders(headers);
headers.Add("ya-client-host", "passport.yandex.ru");
var cookieString = GetCookieString();
if (!string.IsNullOrEmpty(cookieString))
headers.Add("Ya-Client-Cookie", cookieString);
}
private string GetCookieString()
{
var container = Storage.CookieContainer;
if (container == null) return string.Empty;
var uris = new[]
{
new Uri("https://yandex.ru"),
new Uri("https://passport.yandex.ru"),
new Uri("https://mobileproxy.passport.yandex.net")
};
var cookies = new List<string>();
foreach (var uri in uris)
{
var cookieCollection = container.GetCookies(uri);
foreach (Cookie cookie in cookieCollection)
{
cookies.Add($"{cookie.Name}={cookie.Value}");
}
}
var distinct = cookies
.Select(c => c.Split('=')[0])
.Distinct()
.Select(name => cookies.First(c => c.StartsWith(name + "=")))
.ToList();
return string.Join("; ", distinct);
}
}

View File

@@ -3,7 +3,7 @@ using YandexMusic.API.Models.Account;
namespace YandexMusic.API.Requests.Account;
public class YGetAuthInfoBuilder : YMusicRequestBuilder<YAccountResult?, object>
internal class YGetAuthInfoBuilder : YMusicRequestBuilder<YAccountResult?, object>
{
public YGetAuthInfoBuilder(YandexMusicApi api) : base(api) { }
protected override string Method => WebRequestMethods.Http.Get;

View File

@@ -3,7 +3,7 @@ using YandexMusic.API.Models.Account;
namespace YandexMusic.API.Requests.Account;
public class YGetAuthLetterBuilder : YAuthRequestBuilder<YAuthLetter?, object>
internal class YGetAuthLetterBuilder : YPassportRequestBuilder<YAuthLetter?, object>
{
public YGetAuthLetterBuilder(YandexMusicApi api) : base(api) { }
protected override string Method => WebRequestMethods.Http.Post;

View File

@@ -3,7 +3,7 @@ using YandexMusic.API.Models.Account;
namespace YandexMusic.API.Requests.Account;
public class YGetAuthLoginCaptchaBuilder : YAuthRequestBuilder<YAuthBase?, string>
internal class YGetAuthLoginCaptchaBuilder : YPassportRequestBuilder<YAuthBase?, string>
{
public YGetAuthLoginCaptchaBuilder(YandexMusicApi api) : base(api) { }
protected override string Method => WebRequestMethods.Http.Post;

View File

@@ -3,7 +3,7 @@ using YandexMusic.API.Models.Account;
namespace YandexMusic.API.Requests.Account;
public class YGetAuthLoginLetterBuilder : YAuthRequestBuilder<YAuthLetterStatus?, object>
internal class YGetAuthLoginLetterBuilder : YPassportRequestBuilder<YAuthLetterStatus?, object>
{
public YGetAuthLoginLetterBuilder(YandexMusicApi api) : base(api) { }
protected override string Method => WebRequestMethods.Http.Post;

View File

@@ -1,23 +0,0 @@
using System.Net;
using YandexMusic.API.Models.Account;
namespace YandexMusic.API.Requests.Account;
internal class YGetAuthLoginQRBuilder : YAuthRequestBuilder<YAuthQRStatus, string>
{
public YGetAuthLoginQRBuilder(YandexMusicApi yandex) : base(yandex)
{
}
protected override string Method => WebRequestMethods.Http.Post;
protected override string PathTemplate => "auth/new/magic/status/";
protected override HttpContent GetContent(string tuple)
{
return new FormUrlEncodedContent(new Dictionary<string, string> {
{ "csrf_token", Api.Storage.AuthToken.CsfrToken },
{ "track_id", Api.Storage.AuthToken.TrackId }
});
}
}

View File

@@ -3,7 +3,7 @@ using YandexMusic.API.Models.Account;
namespace YandexMusic.API.Requests.Account;
public class YGetAuthLoginUserBuilder : YAuthRequestBuilder<YAuthTypes?, (string token, string login)>
internal class YGetAuthLoginUserBuilder : YPassportRequestBuilder<YAuthTypes?, (string token, string login)>
{
public YGetAuthLoginUserBuilder(YandexMusicApi api) : base(api) { }
protected override string Method => WebRequestMethods.Http.Post;

View File

@@ -3,7 +3,7 @@ using YandexMusic.API.Requests.Common;
namespace YandexMusic.API.Requests.Account;
public class YGetAuthMethodsBuilder : YRequestBuilder<object>
internal class YGetAuthMethodsBuilder : YRequestBuilder<object>
{
public YGetAuthMethodsBuilder(YandexMusicApi api) : base(api) { }
protected override string Method => WebRequestMethods.Http.Get;

View File

@@ -1,19 +0,0 @@
using System.Net;
using System.Net.Http.Headers;
using YandexMusic.API.Models.Account;
namespace YandexMusic.API.Requests.Account;
public 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.AuthToken.CsfrToken);
headers.Add("Process-Uuid", Api.Storage.AuthToken.ProcessUuid);
}
}

View File

@@ -1,10 +1,9 @@
using System.Net;
using YandexMusic.API.Models.Account;
using YandexMusic.API.Requests.Common;
namespace YandexMusic.API.Requests.Account;
public class YGetLoginInfoBuilder : YAuthRequestBuilder<YLoginInfo?, object>
internal class YGetLoginInfoBuilder : YPassportRequestBuilder<YLoginInfo?, object>
{
public YGetLoginInfoBuilder(YandexMusicApi api) : base(api) { }
protected override string Method => WebRequestMethods.Http.Get;

View File

@@ -5,7 +5,7 @@ using YandexMusic.API.Models.Account;
namespace YandexMusic.API.Requests.Account;
public class YGetShortAccountInfoBuilder : YAuthRequestBuilder<YShortAccountInfo?, object>
internal class YGetShortAccountInfoBuilder : YPassportRequestBuilder<YShortAccountInfo?, object>
{
public YGetShortAccountInfoBuilder(YandexMusicApi api) : base(api) { }
protected override string Method => WebRequestMethods.Http.Get;

View File

@@ -4,7 +4,7 @@ using YandexMusic.API.Models.Account;
namespace YandexMusic.API.Requests.Account;
public class YPostAuthStats : YAuthRequestBuilder<YAuthEmpty?, object>
internal class YPostAuthStats : YPassportRequestBuilder<YAuthEmpty?, object>
{
public YPostAuthStats(YandexMusicApi api) : base(api) { }
protected override string Method => WebRequestMethods.Http.Post;
@@ -13,7 +13,7 @@ public class YPostAuthStats : YAuthRequestBuilder<YAuthEmpty?, object>
=> new FormUrlEncodedContent(new Dictionary<string, string> { { "messageType", "CLIENT_READY" } });
protected override void SetCustomHeaders(HttpRequestHeaders headers)
{
headers.Add("X-Csrf-Token", Api.Storage.AuthToken.CsfrToken);
headers.Add("Process-Uuid", Api.Storage.AuthToken.ProcessUuid);
headers.Add("X-Csrf-Token", Api.Storage.HeaderToken.CsfrToken);
headers.Add("Process-Uuid", Api.Storage.HeaderToken.ProcessUuid);
}
}

View File

@@ -3,7 +3,7 @@ using YandexMusic.API.Models.Album;
namespace YandexMusic.API.Requests.Album;
public class YGetAlbumBuilder : YMusicRequestBuilder<YAlbum?, string>
internal class YGetAlbumBuilder : YMusicRequestBuilder<YAlbum?, string>
{
public YGetAlbumBuilder(YandexMusicApi api) : base(api) { }
protected override string Method => WebRequestMethods.Http.Get;

View File

@@ -3,7 +3,7 @@ using YandexMusic.API.Models.Album;
namespace YandexMusic.API.Requests.Album;
public class YGetAlbumsBuilder : YMusicRequestBuilder<List<YAlbum>?, IEnumerable<string>>
internal class YGetAlbumsBuilder : YMusicRequestBuilder<List<YAlbum>?, IEnumerable<string>>
{
public YGetAlbumsBuilder(YandexMusicApi api) : base(api) { }
protected override string Method => WebRequestMethods.Http.Post;

View File

@@ -3,7 +3,7 @@ using YandexMusic.API.Models.Artist;
namespace YandexMusic.API.Requests.Artist;
public class YGetArtistBuilder : YMusicRequestBuilder<YArtistBriefInfo?, string>
internal class YGetArtistBuilder : YMusicRequestBuilder<YArtistBriefInfo?, string>
{
public YGetArtistBuilder(YandexMusicApi api) : base(api) { }
protected override string Method => WebRequestMethods.Http.Get;

View File

@@ -4,7 +4,7 @@ using YandexMusic.API.Models.Artist;
namespace YandexMusic.API.Requests.Artist;
public class YGetArtistTrackBuilder : YMusicRequestBuilder<YTracksPage?, (string id, int page, int pageSize)>
internal class YGetArtistTrackBuilder : YMusicRequestBuilder<YTracksPage?, (string id, int page, int pageSize)>
{
public YGetArtistTrackBuilder(YandexMusicApi api) : base(api) { }
protected override string Method => WebRequestMethods.Http.Get;
@@ -12,5 +12,8 @@ public class YGetArtistTrackBuilder : YMusicRequestBuilder<YTracksPage?, (string
protected override Dictionary<string, string> GetSubstitutions((string id, int page, int pageSize) tuple)
=> new() { { "artistId", tuple.id } };
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() },
};
}

View File

@@ -3,7 +3,7 @@ using YandexMusic.API.Models.Artist;
namespace YandexMusic.API.Requests.Artist;
public class YGetArtistsBuilder : YMusicRequestBuilder<List<YArtist>?, IEnumerable<string>>
internal class YGetArtistsBuilder : YMusicRequestBuilder<List<YArtist>?, IEnumerable<string>>
{
public YGetArtistsBuilder(YandexMusicApi api) : base(api) { }
protected override string Method => WebRequestMethods.Http.Post;

View File

@@ -12,5 +12,6 @@ internal class YConstants
{
public const string MusicUrl = "https://api.music.yandex.net";
public const string PassportUrl = "https://passport.yandex.ru/";
public const string MobilePassportUrl = "https://mobileproxy.passport.yandex.net";
}
}

View File

@@ -8,9 +8,19 @@ namespace YandexMusic.API.Requests.Common;
/// <summary>
/// Строитель запросов с десериализацией JSON-ответа в TResponse.
/// </summary>
public abstract class YJsonRequestBuilder<TResponse, TParams> : YRequestBuilder<TParams>
internal abstract class YJsonRequestBuilder<TResponse, TParams> : YRequestBuilder<TParams>
{
protected YJsonRequestBuilder(YandexMusicApi api) : base(api) { }
private readonly JsonSerializerOptions _jsonOptions;
protected YJsonRequestBuilder(YandexMusicApi api) : base(api)
{
_jsonOptions = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
Converters = { new JsonStringEnumConverter(JsonNamingPolicy.CamelCase) }
};
}
protected virtual async Task<TResponse?> DeserializeAsync(HttpResponseMessage response)
{
@@ -43,6 +53,8 @@ public abstract class YJsonRequestBuilder<TResponse, TParams> : YRequestBuilder<
}
}
protected string SerializeJson(object data) => JsonSerializer.Serialize(data, _jsonOptions);
/// <summary>
/// Выполняет запрос и возвращает десериализованный объект типа TResponse.
/// </summary>
@@ -51,4 +63,4 @@ public abstract class YJsonRequestBuilder<TResponse, TParams> : YRequestBuilder<
using var response = await ExecuteRawAsync(parameters);
return await DeserializeAsync(response);
}
}
}

View File

@@ -8,7 +8,7 @@ using YandexMusic.API.Requests.Common;
namespace YandexMusic.API.Requests;
/// <summary>Базовый класс для запросов к API Яндекс Музыки (api.music.yandex.net).</summary>
public abstract class YMusicRequestBuilder<TResponse, TParams> : YJsonRequestBuilder<TResponse, TParams>
internal abstract class YMusicRequestBuilder<TResponse, TParams> : YJsonRequestBuilder<TResponse, TParams>
{
protected override string BaseUrl => YConstants.Endpoints.MusicUrl;

View File

@@ -4,15 +4,20 @@ using YandexMusic.API.Requests.Common;
namespace YandexMusic.API.Requests;
/// <summary>Базовый класс для запросов к Passport (passport.yandex.ru).</summary>
public 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 YAuthRequestBuilder(YandexMusicApi api) : base(api) { }
protected YPassportRequestBuilder(YandexMusicApi api) : base(api) { }
protected override void SetCustomHeaders(HttpRequestHeaders headers)
{
base.SetCustomHeaders(headers);
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);
}
}
}

View File

@@ -2,8 +2,6 @@
using System.Net;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;
using System.Web;
using YandexMusic.API.Common;
@@ -12,7 +10,7 @@ namespace YandexMusic.API.Requests.Common;
/// <summary>Базовый строитель HTTP-запросов.</summary>
/// <typeparam name="TParams">Тип параметров запроса.</typeparam>
public abstract class YRequestBuilder<TParams>
internal abstract class YRequestBuilder<TParams>
{
/// <summary>HTTP-метод (GET, POST и т.д.).</summary>
protected abstract string Method { get; }
@@ -23,7 +21,8 @@ public abstract class YRequestBuilder<TParams>
/// <summary>Шаблон пути (может содержать плейсхолдеры вида {id}).</summary>
protected abstract string PathTemplate { get; }
private readonly JsonSerializerOptions _jsonOptions;
/// <summary>Определяет, нужно ли добавлять заголовок Authorization для этого запроса.</summary>
protected virtual bool ShouldAddAuthorization => true;
/// <summary>Основной экземпляр API.</summary>
protected YandexMusicApi Api { get; }
@@ -34,12 +33,6 @@ public abstract class YRequestBuilder<TParams>
protected YRequestBuilder(YandexMusicApi api)
{
Api = api;
_jsonOptions = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
Converters = { new JsonStringEnumConverter(JsonNamingPolicy.CamelCase) }
};
}
private string FullUrl => $"{BaseUrl.TrimEnd('/')}/{PathTemplate.TrimStart('/')}";
@@ -66,7 +59,7 @@ public abstract class YRequestBuilder<TParams>
};
msg.Headers.TryAddWithoutValidation(GetHeaderName(HttpRequestHeader.AcceptCharset), Encoding.UTF8.WebName);
msg.Headers.TryAddWithoutValidation(GetHeaderName(HttpRequestHeader.AcceptEncoding), "gzip");
if (!string.IsNullOrEmpty(Storage.Token))
if (ShouldAddAuthorization && !string.IsNullOrEmpty(Storage.Token))
msg.Headers.TryAddWithoutValidation(GetHeaderName(HttpRequestHeader.Authorization), $"OAuth {Storage.Token}");
SetCustomHeaders(msg.Headers);
return msg;
@@ -120,8 +113,6 @@ public abstract class YRequestBuilder<TParams>
protected virtual HttpContent? GetContent(TParams parameters) => null;
protected virtual void SetCustomHeaders(HttpRequestHeaders headers) { }
protected string SerializeJson(object data) => JsonSerializer.Serialize(data, _jsonOptions);
/// <summary>Выполняет запрос и возвращает десериализованный ответ.</summary>
public async Task<HttpResponseMessage?> ExecuteRawAsync(TParams parameters)
{

View File

@@ -0,0 +1,47 @@
using System.Xml;
using System.Xml.Serialization;
namespace YandexMusic.API.Requests.Common;
/// <summary>
/// Строитель запросов с десериализацией XML-ответа в TResponse.
/// </summary>
internal abstract class YXmlRequestBuilder<TResponse, TParams> : YRequestBuilder<TParams>
{
protected YXmlRequestBuilder(YandexMusicApi api) : base(api) { }
/// <summary>
/// Десериализует XML-ответ в объект типа TResponse.
/// </summary>
protected virtual async Task<TResponse?> DeserializeAsync(HttpResponseMessage response)
{
var xml = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
{
// Для XML-ошибок можно создать отдельную модель, но для простоты выбрасываем исключение
throw new Exception($"Ошибка HTTP {response.StatusCode}: {xml}");
}
try
{
using var stringReader = new StringReader(xml);
using var xmlReader = XmlReader.Create(stringReader, new XmlReaderSettings { Async = true });
var serializer = new XmlSerializer(typeof(TResponse));
return (TResponse?)serializer.Deserialize(xmlReader);
}
catch (Exception ex)
{
throw new Exception($"Ошибка десериализации XML: {ex.Message}\nXML: {xml}", ex);
}
}
/// <summary>
/// Выполняет запрос и возвращает десериализованный объект типа TResponse.
/// </summary>
public async Task<TResponse?> ExecuteAsync(TParams parameters)
{
using var response = await ExecuteRawAsync(parameters);
return await DeserializeAsync(response);
}
}

View File

@@ -1,10 +1,9 @@
using System.Net;
using YandexMusic.API.Models.Feed;
using YandexMusic.API.Requests.Common;
namespace YandexMusic.API.Requests.Feed;
public class YGetFeedBuilder : YMusicRequestBuilder<YFeed?, object>
internal class YGetFeedBuilder : YMusicRequestBuilder<YFeed?, object>
{
public YGetFeedBuilder(YandexMusicApi api) : base(api) { }
protected override string Method => WebRequestMethods.Http.Get;

View File

@@ -5,7 +5,7 @@ using YandexMusic.API.Models.Label;
namespace YandexMusic.API.Requests.Label;
public class YGetLabelAlbumsBuilder : YMusicRequestBuilder<YLabelAlbums?, (YLabel label, int pageNumber)>
internal class YGetLabelAlbumsBuilder : YMusicRequestBuilder<YLabelAlbums?, (YLabel label, int pageNumber)>
{
public YGetLabelAlbumsBuilder(YandexMusicApi api) : base(api) { }
protected override string Method => WebRequestMethods.Http.Get;

View File

@@ -5,7 +5,7 @@ using YandexMusic.API.Models.Label;
namespace YandexMusic.API.Requests.Label;
public class YGetLabelArtistsBuilder : YMusicRequestBuilder<YLabelArtists?, (YLabel label, int pageNumber)>
internal class YGetLabelArtistsBuilder : YMusicRequestBuilder<YLabelArtists?, (YLabel label, int pageNumber)>
{
public YGetLabelArtistsBuilder(YandexMusicApi api) : base(api) { }
protected override string Method => WebRequestMethods.Http.Get;

View File

@@ -1,10 +1,9 @@
using System.Net;
using YandexMusic.API.Models.Landing;
using YandexMusic.API.Requests.Common;
namespace YandexMusic.API.Requests.Landing;
public class YGetChildrenLandingBuilder : YMusicRequestBuilder<YChildrenLanding?, object>
internal class YGetChildrenLandingBuilder : YMusicRequestBuilder<YChildrenLanding?, object>
{
public YGetChildrenLandingBuilder(YandexMusicApi api) : base(api) { }
protected override string Method => WebRequestMethods.Http.Get;

View File

@@ -4,7 +4,7 @@ using YandexMusic.API.Models.Landing;
namespace YandexMusic.API.Requests.Landing;
public class YGetLandingBuilder : YMusicRequestBuilder<YLanding?, YLandingBlockType[]>
internal class YGetLandingBuilder : YMusicRequestBuilder<YLanding?, YLandingBlockType[]>
{
public YGetLandingBuilder(YandexMusicApi api) : base(api) { }
protected override string Method => WebRequestMethods.Http.Get;

View File

@@ -5,7 +5,7 @@ using YandexMusic.API.Models.Library;
namespace YandexMusic.API.Requests.Library;
public class YGetLibraryRecentlyListenedBuilder : YMusicRequestBuilder<YRecentlyListenedContext?, (IEnumerable<YPlayContextType> contextTypes, int trackCount, int contextCount)>
internal class YGetLibraryRecentlyListenedBuilder : YMusicRequestBuilder<YRecentlyListenedContext?, (IEnumerable<YPlayContextType> contextTypes, int trackCount, int contextCount)>
{
public YGetLibraryRecentlyListenedBuilder(YandexMusicApi api) : base(api) { }
protected override string Method => WebRequestMethods.Http.Get;

View File

@@ -3,7 +3,7 @@ using YandexMusic.API.Models.Library;
namespace YandexMusic.API.Requests.Library;
public class YGetLibrarySectionBuilder<T> : YMusicRequestBuilder<T?, (YLibrarySection section, YLibrarySectionType type)>
internal class YGetLibrarySectionBuilder<T> : YMusicRequestBuilder<T?, (YLibrarySection section, YLibrarySectionType type)>
{
public YGetLibrarySectionBuilder(YandexMusicApi api) : base(api) { }
protected override string Method => WebRequestMethods.Http.Get;

View File

@@ -3,7 +3,7 @@ using YandexMusic.API.Models.Library;
namespace YandexMusic.API.Requests.Library;
public class YLibraryAddBuilder<T> : YMusicRequestBuilder<T?, (string id, YLibrarySection section, YLibrarySectionType type)>
internal class YLibraryAddBuilder<T> : YMusicRequestBuilder<T?, (string id, YLibrarySection section, YLibrarySectionType type)>
{
public YLibraryAddBuilder(YandexMusicApi api) : base(api) { }
protected override string Method => WebRequestMethods.Http.Post;

View File

@@ -3,7 +3,7 @@ using YandexMusic.API.Models.Library;
namespace YandexMusic.API.Requests.Library;
public class YLibraryRemoveBuilder<T> : YMusicRequestBuilder<T?, (string id, YLibrarySection section, YLibrarySectionType type)>
internal class YLibraryRemoveBuilder<T> : YMusicRequestBuilder<T?, (string id, YLibrarySection section, YLibrarySectionType type)>
{
public YLibraryRemoveBuilder(YandexMusicApi api) : base(api) { }
protected override string Method => WebRequestMethods.Http.Post;

View File

@@ -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);
}
}

View 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);
}
}

View 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);
}
}

View 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", "" } });
}

View File

@@ -0,0 +1,22 @@
using System.Net;
using YandexMusic.API.Models.Passport;
namespace YandexMusic.API.Requests.Passport;
internal class YGetAuthLoginQRBuilder : YPassportRequestBuilder<YAuthQrSession, string>
{
public YGetAuthLoginQRBuilder(YandexMusicApi yandex) : base(yandex)
{
}
protected override string Method => WebRequestMethods.Http.Post;
protected override string PathTemplate => "pwl-yandex/api/passport/sessions/get_session";
protected override HttpContent GetContent(string tuple)
{
return new FormUrlEncodedContent(new Dictionary<string, string> {
{ "track_id", Api.Storage.AuthToken.SessionTrackId }
});
}
}

View File

@@ -3,20 +3,21 @@ using System.Net.Http.Headers;
using YandexMusic.API.Models.Account;
using YandexMusic.API.Requests.Common;
namespace YandexMusic.API.Requests.Account;
namespace YandexMusic.API.Requests.Passport;
public class YGetMusicTokenBuilder : YAuthRequestBuilder<YAccessToken?, object>
internal class YGetMusicTokenBuilder : YPassportRequestBuilder<YAccessToken?, string>
{
public YGetMusicTokenBuilder(YandexMusicApi api) : base(api) { }
protected override string BaseUrl => YConstants.Endpoints.MobilePassportUrl;
protected override string Method => WebRequestMethods.Http.Post;
protected override string PathTemplate => "/1/token";
protected override HttpContent? GetContent(object _)
protected override HttpContent? GetContent(string passportToken)
=> new FormUrlEncodedContent(new Dictionary<string, string>
{
{ "client_id", YConstants.ClientId },
{ "client_secret", YConstants.ClientSecret },
{ "grant_type", "x-token" },
{ "access_token", Api.Storage.AccessToken.AccessToken }
{ "access_token", passportToken }
});
protected override void SetCustomHeaders(HttpRequestHeaders headers)
{

View 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,
});
}

View 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);
}
}

View 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 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);
}
}

View 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);
}
}

View 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);
}
}

View 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);
}
}

View 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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -1,10 +1,9 @@
using System.Net;
using YandexMusic.API.Models.Pins;
using YandexMusic.API.Requests.Common;
namespace YandexMusic.API.Requests.Pins;
public class YGetPinsBuilder : YMusicRequestBuilder<YPins?, object>
internal class YGetPinsBuilder : YMusicRequestBuilder<YPins?, object>
{
public YGetPinsBuilder(YandexMusicApi api) : base(api) { }
protected override string Method => WebRequestMethods.Http.Get;

View File

@@ -3,7 +3,7 @@ using YandexMusic.API.Models.Playlist;
namespace YandexMusic.API.Requests.Playlist;
public class YGetPlaylistBuilder : YMusicRequestBuilder<YPlaylist?, (string user, string kind)>
internal class YGetPlaylistBuilder : YMusicRequestBuilder<YPlaylist?, (string user, string kind)>
{
public YGetPlaylistBuilder(YandexMusicApi api) : base(api) { }
protected override string Method => WebRequestMethods.Http.Get;

View File

@@ -3,7 +3,7 @@ using YandexMusic.API.Models.Playlist;
namespace YandexMusic.API.Requests.Playlist;
public class YGetPlaylistByUuidBuilder : YMusicRequestBuilder<YPlaylist?, string>
internal class YGetPlaylistByUuidBuilder : YMusicRequestBuilder<YPlaylist?, string>
{
public YGetPlaylistByUuidBuilder(YandexMusicApi api) : base(api) { }
protected override string Method => WebRequestMethods.Http.Get;

View File

@@ -3,7 +3,7 @@ using YandexMusic.API.Models.Playlist;
namespace YandexMusic.API.Requests.Playlist;
public class YGetPlaylistFavoritesBuilder : YMusicRequestBuilder<List<YPlaylist>?, object>
internal class YGetPlaylistFavoritesBuilder : YMusicRequestBuilder<List<YPlaylist>?, object>
{
public YGetPlaylistFavoritesBuilder(YandexMusicApi api) : base(api) { }
protected override string Method => WebRequestMethods.Http.Get;

View File

@@ -3,7 +3,7 @@ using YandexMusic.API.Models.Playlist;
namespace YandexMusic.API.Requests.Playlist;
public class YGetPlaylistsBuilder : YMusicRequestBuilder<List<YPlaylist>?, IEnumerable<(string User, string Kind)>>
internal class YGetPlaylistsBuilder : YMusicRequestBuilder<List<YPlaylist>?, IEnumerable<(string User, string Kind)>>
{
public YGetPlaylistsBuilder(YandexMusicApi api) : base(api) { }
protected override string Method => WebRequestMethods.Http.Post;

View File

@@ -3,7 +3,7 @@ using YandexMusic.API.Models.Playlist;
namespace YandexMusic.API.Requests.Playlist;
public class YPlaylistChangeBuilder : YMusicRequestBuilder<YPlaylist?, (YPlaylist playlist, IEnumerable<YPlaylistChange> changes)>
internal class YPlaylistChangeBuilder : YMusicRequestBuilder<YPlaylist?, (YPlaylist playlist, IEnumerable<YPlaylistChange> changes)>
{
public YPlaylistChangeBuilder(YandexMusicApi api) : base(api) { }
protected override string Method => WebRequestMethods.Http.Post;

View File

@@ -3,7 +3,7 @@ using YandexMusic.API.Models.Playlist;
namespace YandexMusic.API.Requests.Playlist;
public class YPlaylistCreateBuilder : YMusicRequestBuilder<YPlaylist?, string>
internal class YPlaylistCreateBuilder : YMusicRequestBuilder<YPlaylist?, string>
{
public YPlaylistCreateBuilder(YandexMusicApi api) : base(api) { }
protected override string Method => WebRequestMethods.Http.Post;

View File

@@ -2,7 +2,7 @@
namespace YandexMusic.API.Requests.Playlist;
public class YPlaylistRemoveBuilder : YMusicRequestBuilder<HttpResponseMessage, string>
internal class YPlaylistRemoveBuilder : YMusicRequestBuilder<HttpResponseMessage, string>
{
public YPlaylistRemoveBuilder(YandexMusicApi api) : base(api) { }
protected override string Method => WebRequestMethods.Http.Post;

View File

@@ -3,7 +3,7 @@ using YandexMusic.API.Models.Playlist;
namespace YandexMusic.API.Requests.Playlist;
public class YPlaylistRenameBuilder : YMusicRequestBuilder<YPlaylist?, (string kind, string name)>
internal class YPlaylistRenameBuilder : YMusicRequestBuilder<YPlaylist?, (string kind, string name)>
{
public YPlaylistRenameBuilder(YandexMusicApi api) : base(api) { }
protected override string Method => WebRequestMethods.Http.Post;

View File

@@ -3,7 +3,7 @@ using YandexMusic.API.Models.Queue;
namespace YandexMusic.API.Requests.Queue;
public class YGetQueueBuilder : YMusicRequestBuilder<YQueue?, string>
internal class YGetQueueBuilder : YMusicRequestBuilder<YQueue?, string>
{
public YGetQueueBuilder(YandexMusicApi api) : base(api) { }
protected override string Method => WebRequestMethods.Http.Get;

View File

@@ -8,7 +8,7 @@ using YandexMusic.API.Models.Queue;
namespace YandexMusic.API.Requests.Queue;
public class YQueueCreateBuilder : YMusicRequestBuilder<YNewQueue?, YQueue>
internal class YQueueCreateBuilder : YMusicRequestBuilder<YNewQueue?, YQueue>
{
private string? _device;
public YQueueCreateBuilder(YandexMusicApi api, string? device = null) : base(api) => _device = device;

View File

@@ -5,7 +5,7 @@ using YandexMusic.API.Models.Queue;
namespace YandexMusic.API.Requests.Queue;
public class YQueueUpdatePositionBuilder : YMusicRequestBuilder<YUpdatedQueue?, (string queueId, int currentIndex, bool isInteractive)>
internal class YQueueUpdatePositionBuilder : YMusicRequestBuilder<YUpdatedQueue?, (string queueId, int currentIndex, bool isInteractive)>
{
private string? _device;
public YQueueUpdatePositionBuilder(YandexMusicApi api, string? device = null) : base(api) => _device = device;

View File

@@ -4,7 +4,7 @@ using YandexMusic.API.Models.Queue;
namespace YandexMusic.API.Requests.Queue;
public class YQueuesListBuilder : YMusicRequestBuilder<YQueueItemsContainer?, string?>
internal class YQueuesListBuilder : YMusicRequestBuilder<YQueueItemsContainer?, string?>
{
private string? _device;
public YQueuesListBuilder(YandexMusicApi api, string? device = null) : base(api) => _device = device;

View File

@@ -3,7 +3,7 @@ using YandexMusic.API.Models.Radio;
namespace YandexMusic.API.Requests.Radio;
public class YGetStationBuilder : YMusicRequestBuilder<List<YStation>?, (string type, string tag)>
internal class YGetStationBuilder : YMusicRequestBuilder<List<YStation>?, (string type, string tag)>
{
public YGetStationBuilder(YandexMusicApi api) : base(api) { }
protected override string Method => WebRequestMethods.Http.Get;

View File

@@ -4,7 +4,7 @@ using YandexMusic.API.Models.Radio;
namespace YandexMusic.API.Requests.Radio;
public class YGetStationTracksBuilder : YMusicRequestBuilder<YStationSequence?, (YStationDescription station, string prevTrackId)>
internal class YGetStationTracksBuilder : YMusicRequestBuilder<YStationSequence?, (YStationDescription station, string prevTrackId)>
{
public YGetStationTracksBuilder(YandexMusicApi api) : base(api) { }
protected override string Method => WebRequestMethods.Http.Get;

Some files were not shown because too many files have changed in this diff Show More