Files
YandexMusic/YandexMusic.API/Common/Ynison/YnisonWebSocket.cs
2026-04-10 12:12:33 +03:00

179 lines
5.4 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Net.WebSockets;
using System.Text;
namespace YandexMusic.API.Common.Ynison
{
public class YnisonWebSocket : IDisposable
{
#region Поля
private readonly JsonSerializerSettings jsonSettings = new()
{
Converters = new List<JsonConverter> {
new StringEnumConverter {
NamingStrategy = new CamelCaseNamingStrategy()
}
},
NullValueHandling = NullValueHandling.Ignore
};
private readonly ClientWebSocket socketClient = new();
private CancellationTokenSource cancellationTokenSource = new();
private CancellationToken cancellation;
private readonly StringBuilder data = new();
private readonly int size = 4096;
#endregion Поля
#region Свойства
public bool IsConnected => socketClient.State == WebSocketState.Open;
#endregion Свойства
#region События
public class ReceiveEventArgs
{
public string Data { get; internal set; }
}
public delegate void OnReceiveEventHandler(YnisonWebSocket socket, ReceiveEventArgs args);
/// <summary>
/// Получение данных
/// </summary>
public event OnReceiveEventHandler OnReceive;
public class CloseEventArgs
{
public WebSocketCloseStatus? Status { get; set; }
public string Description { get; set; }
}
public delegate void OnCloseEventHandler(YnisonWebSocket socket, CloseEventArgs args);
/// <summary>
/// Закрытие соединения
/// </summary>
public event OnCloseEventHandler OnClose;
#endregion События
#region Вспомогательные функции
private string SerializeJson(object obj)
{
return JsonConvert.SerializeObject(obj, jsonSettings);
}
private string GetProtocolData(string deviceId, string redirectTicket)
{
Dictionary<string, object> deviceInfo = new() {
{ "app_name", "Chrome" },
{ "type", 1 }
};
Dictionary<string, string> protocol = new() {
{ "Ynison-Device-Id", deviceId },
{ "Ynison-Device-Info", SerializeJson(deviceInfo) }
};
if (!string.IsNullOrEmpty(redirectTicket))
protocol.Add("Ynison-Redirect-Ticket", redirectTicket);
return SerializeJson(protocol);
}
private async Task<string> ReadSocketContent()
{
byte[] buffer = new byte[size];
WebSocketReceiveResult result;
do
{
result = await socketClient.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
data.Append(Encoding.UTF8.GetString(buffer, 0, result.Count));
} while (!result.EndOfMessage);
return data.ToString();
}
#endregion Вспомогательные функции
public bool Connect(AuthStorage storage, string url, string redirectTicket = null)
{
socketClient.Options.AddSubProtocol("Bearer");
socketClient.Options.SetRequestHeader("Sec-WebSocket-Protocol", $"Bearer, v2, {GetProtocolData(storage.DeviceId, redirectTicket)}");
socketClient.Options.SetRequestHeader("Origin", "https://music.yandex.ru");
socketClient.Options.SetRequestHeader("Authorization", $"OAuth {storage.Token}");
socketClient.Options.Proxy = storage.Context.WebProxy;
socketClient.ConnectAsync(new Uri(url), CancellationToken.None)
.GetAwaiter()
.GetResult();
cancellation = cancellationTokenSource.Token;
return socketClient.State == WebSocketState.Open;
}
public async Task BeginReceive()
{
if (socketClient.State != WebSocketState.Open)
return;
do
{
string content = await ReadSocketContent();
OnReceive?.Invoke(this, new ReceiveEventArgs
{
Data = content
});
data.Clear();
} while (!cancellation.IsCancellationRequested && socketClient.State == WebSocketState.Open);
OnClose?.Invoke(this, new CloseEventArgs
{
Status = socketClient.CloseStatus,
Description = socketClient.CloseStatusDescription
});
await socketClient.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);
}
public ValueTask Send(string json)
{
ReadOnlyMemory<byte> message = new(Encoding.UTF8.GetBytes(json));
return socketClient.SendAsync(message, WebSocketMessageType.Text, false, CancellationToken.None);
}
public Task StopReceive()
{
if (socketClient.State != WebSocketState.Open)
return Task.CompletedTask;
cancellationTokenSource.Cancel(false);
return Task.CompletedTask;
}
#region IDisposable
public void Dispose()
{
socketClient?.Dispose();
cancellationTokenSource?.Dispose();
}
#endregion IDisposable
}
}