Добавлен вывод QR яндекса
This commit is contained in:
138
PlaylistShared.Api/Services/Yandex/YandexApiService.cs
Normal file
138
PlaylistShared.Api/Services/Yandex/YandexApiService.cs
Normal file
@@ -0,0 +1,138 @@
|
||||
using Microsoft.AspNetCore.DataProtection;
|
||||
using PlaylistShared.Api.Entities;
|
||||
using System.Net;
|
||||
using YandexMusic;
|
||||
using YandexMusic.API.Common;
|
||||
|
||||
namespace PlaylistShared.Api.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Сервис для работы с API Яндекс Музыки в ASP.NET Core.
|
||||
/// </summary>
|
||||
public class YandexApiService : IDisposable
|
||||
{
|
||||
private readonly IDataProtector _dataProtector;
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly YandexMusicClient _client;
|
||||
private readonly CookieContainer _cookieContainer;
|
||||
|
||||
/// <summary>
|
||||
/// Экземпляр клиента Яндекс Музыки.
|
||||
/// </summary>
|
||||
public YandexMusicClient Client => _client;
|
||||
|
||||
/// <summary>
|
||||
/// Контейнер кук, используемый клиентом.
|
||||
/// </summary>
|
||||
public CookieContainer CookieContainer => _cookieContainer;
|
||||
|
||||
/// <summary>
|
||||
/// Создаёт сервис с автоматическим созданием HttpClient (рекомендуется).
|
||||
/// </summary>
|
||||
public YandexApiService(IDataProtectionProvider provider, IWebProxy? proxy = null, TimeSpan? timeout = null)
|
||||
{
|
||||
_dataProtector = provider.CreateProtector("YandexTokens");
|
||||
_cookieContainer = new();
|
||||
_httpClient = YandexMusicHttpClientFactory.CreateDefault(
|
||||
cookieContainer: _cookieContainer,
|
||||
proxy: proxy,
|
||||
timeout: timeout
|
||||
);
|
||||
_client = new YandexMusicClient(_httpClient);
|
||||
}
|
||||
|
||||
|
||||
public async Task<bool?> AuthAsync(ApplicationUser user)
|
||||
{
|
||||
if (string.IsNullOrEmpty(user.YandexAccessToken))
|
||||
return null;
|
||||
|
||||
var decryptedToken = DecryptToken(user.YandexAccessToken);
|
||||
if (decryptedToken == null)
|
||||
return null;
|
||||
|
||||
return await _client.Authorize(decryptedToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Засшифровывает и возвращает токен для хранения в базе данных.
|
||||
/// </summary>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
public string EncryptToken(string token) => _dataProtector.Protect(token);
|
||||
|
||||
/// <summary>
|
||||
/// Расшифровывает ключ из базы данных. Если токен повреждён или недействителен, возвращает null.
|
||||
/// </summary>
|
||||
/// <param name="encryptedToken"></param>
|
||||
/// <returns></returns>
|
||||
public string DecryptToken(string encryptedToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
return _dataProtector.Unprotect(encryptedToken);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Устанавливает куки из строки для указанного домена.
|
||||
/// </summary>
|
||||
public void SetCookies(string cookieString, string domain)
|
||||
{
|
||||
var uri = new Uri(domain.StartsWith("http") ? domain : $"https://{domain}");
|
||||
_cookieContainer.SetCookies(uri, cookieString);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получает все куки для указанного домена в виде строки.
|
||||
/// </summary>
|
||||
public string GetCookies(string domain)
|
||||
{
|
||||
var uri = new Uri(domain.StartsWith("http") ? domain : $"https://{domain}");
|
||||
var cookies = _cookieContainer.GetCookies(uri);
|
||||
return string.Join("; ", cookies.Cast<Cookie>().Select(c => $"{c.Name}={c.Value}"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получает значение конкретной куки.
|
||||
/// </summary>
|
||||
public string? GetCookie(string domain, string cookieName)
|
||||
{
|
||||
var uri = new Uri(domain.StartsWith("http") ? domain : $"https://{domain}");
|
||||
var cookie = _cookieContainer.GetCookies(uri)[cookieName];
|
||||
return cookie?.Value;
|
||||
}
|
||||
|
||||
private void UpdateHttpClientCookieContainer(CookieContainer container)
|
||||
{
|
||||
var handler = GetInnerHandler(_httpClient);
|
||||
if (handler is HttpClientHandler httpHandler)
|
||||
httpHandler.CookieContainer = container;
|
||||
}
|
||||
|
||||
private static HttpMessageHandler GetInnerHandler(HttpClient client)
|
||||
{
|
||||
var field = client.GetType().GetField("_handler", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
||||
if (field?.GetValue(client) is HttpMessageHandler handler)
|
||||
return handler;
|
||||
return new HttpClientHandler();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Авторизуется с помощью OAuth-токена.
|
||||
/// </summary>
|
||||
public async Task<bool> AuthorizeAsync(string token)
|
||||
{
|
||||
return await _client.Authorize(token);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_client.Dispose();
|
||||
_httpClient.Dispose();
|
||||
}
|
||||
}
|
||||
123
PlaylistShared.Api/Services/Yandex/YandexAuthService.cs
Normal file
123
PlaylistShared.Api/Services/Yandex/YandexAuthService.cs
Normal file
@@ -0,0 +1,123 @@
|
||||
using PlaylistShared.Api.Data;
|
||||
using PlaylistShared.Api.Entities;
|
||||
using PlaylistShared.Shared.Yandex;
|
||||
using System.Net;
|
||||
using System.Text.Json;
|
||||
using YandexMusic.API.Models.Account;
|
||||
|
||||
namespace PlaylistShared.Api.Services;
|
||||
|
||||
public class YandexAuthService
|
||||
{
|
||||
private readonly YandexApiService _apiService;
|
||||
private readonly ApplicationDbContext _dbContext;
|
||||
|
||||
public YandexApiService Service => _apiService;
|
||||
|
||||
public YandexAuthService(YandexApiService apiService, ApplicationDbContext dbContext)
|
||||
{
|
||||
_apiService = apiService;
|
||||
_dbContext = dbContext;
|
||||
}
|
||||
|
||||
internal async Task<YandexAuthQr> GenerateQrAsync(ApplicationUser user)
|
||||
{
|
||||
var client = _apiService.Client;
|
||||
var qr = await client.GetAuthQRLink();
|
||||
var trackId = client.AuthStorage.AuthToken.TrackId;
|
||||
var csfrToken = client.AuthStorage.AuthToken.CsfrToken;
|
||||
|
||||
if (string.IsNullOrEmpty(qr))
|
||||
throw new Exception("Не удалось получить QR-ссылку");
|
||||
|
||||
var cookiesJson = SerializeCookies(_apiService.CookieContainer);
|
||||
|
||||
var session = new YandexAuthSession
|
||||
{
|
||||
UserId = user.Id,
|
||||
QrCodeUrl = qr,
|
||||
SerializedCookies = cookiesJson,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
IsConfirmed = false,
|
||||
TrackId = trackId,
|
||||
CsfrToken = csfrToken,
|
||||
|
||||
};
|
||||
|
||||
_dbContext.YandexAuthSessions.Add(session);
|
||||
await _dbContext.SaveChangesAsync();
|
||||
|
||||
return new YandexAuthQr
|
||||
{
|
||||
QrLink = qr,
|
||||
SessionId = session.Id.ToString()
|
||||
};
|
||||
}
|
||||
|
||||
internal async Task<YandexAuthQrCheck?> CheckQrAsync(int sessionId)
|
||||
{
|
||||
var session = await _dbContext.YandexAuthSessions.FindAsync(sessionId);
|
||||
if (session == null) return null;
|
||||
|
||||
RestoreCookies(_apiService.CookieContainer, session.SerializedCookies);
|
||||
if (_apiService.Client.AuthStorage.AuthToken is null)
|
||||
{
|
||||
_apiService.Client.AuthStorage.AuthToken = new();
|
||||
}
|
||||
|
||||
_apiService.Client.AuthStorage.AuthToken.CsfrToken = session?.CsfrToken ?? "";
|
||||
_apiService.Client.AuthStorage.AuthToken.TrackId = session?.TrackId ?? "";
|
||||
|
||||
var status = await _apiService.Client.AuthorizeByQR();
|
||||
|
||||
if (status?.Status == YAuthStatus.Ok)
|
||||
{
|
||||
session.ConfirmedAt = DateTime.UtcNow;
|
||||
session.IsConfirmed = true;
|
||||
await _dbContext.SaveChangesAsync();
|
||||
|
||||
return new()
|
||||
{
|
||||
Status = Shared.Enums.YandexAuthQrStatus.Authorized,
|
||||
};
|
||||
}
|
||||
|
||||
return new()
|
||||
{
|
||||
Status = Shared.Enums.YandexAuthQrStatus.Pending,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
private string SerializeCookies(CookieContainer container)
|
||||
{
|
||||
var domains = new[] { "yandex.ru", "passport.yandex.ru", ".yandex.ru" };
|
||||
var allCookies = new List<object>();
|
||||
|
||||
var cookies = container.GetAllCookies();
|
||||
foreach (Cookie cookie in cookies)
|
||||
{
|
||||
allCookies.Add(new { cookie.Name, cookie.Value, cookie.Domain, cookie.Path });
|
||||
}
|
||||
|
||||
return JsonSerializer.Serialize(allCookies);
|
||||
}
|
||||
|
||||
private void RestoreCookies(CookieContainer container, string serializedCookies)
|
||||
{
|
||||
var cookies = JsonSerializer.Deserialize<List<CookieData>>(serializedCookies);
|
||||
foreach (var c in cookies)
|
||||
{
|
||||
var uri = new Uri($"{c.Domain}");
|
||||
container.Add(uri, new Cookie(c.Name, c.Value, c.Path, c.Domain));
|
||||
}
|
||||
}
|
||||
|
||||
private class CookieData
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Value { get; set; }
|
||||
public string Domain { get; set; }
|
||||
public string Path { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -95,20 +95,6 @@ public class YandexMusicService
|
||||
return track;
|
||||
}
|
||||
|
||||
public string EncryptToken(string token) => _dataProtector.Protect(token);
|
||||
|
||||
public string DecryptToken(string encryptedToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
return _dataProtector.Unprotect(encryptedToken);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<YandexSearchResult> SearchAsync(
|
||||
ApplicationUser user,
|
||||
string query,
|
||||
Reference in New Issue
Block a user