Обнновлено до .net10
This commit is contained in:
@@ -1,10 +1,9 @@
|
||||
namespace YandexMusic.API.Requests.Common.Attributes
|
||||
namespace YandexMusic.API.Requests.Common.Attributes;
|
||||
|
||||
public class YApiRequestAttribute : YBasePathRequestAttribute
|
||||
{
|
||||
public class YApiRequestAttribute : YBasePathRequestAttribute
|
||||
public YApiRequestAttribute(string method, string url) : base(method, url)
|
||||
{
|
||||
public YApiRequestAttribute(string method, string url) : base(method, url)
|
||||
{
|
||||
basePath = "https://api.music.yandex.net";
|
||||
}
|
||||
basePath = "https://api.music.yandex.net";
|
||||
}
|
||||
}
|
||||
@@ -1,32 +1,31 @@
|
||||
namespace YandexMusic.API.Requests.Common.Attributes
|
||||
namespace YandexMusic.API.Requests.Common.Attributes;
|
||||
|
||||
/// <summary>
|
||||
/// Атрибут запроса относительно базового адреса
|
||||
/// </summary>
|
||||
public class YBasePathRequestAttribute : YRequestAttribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Атрибут запроса относительно базового адреса
|
||||
/// </summary>
|
||||
public class YBasePathRequestAttribute : YRequestAttribute
|
||||
#region Поля
|
||||
|
||||
protected string basePath;
|
||||
|
||||
#endregion Поля
|
||||
|
||||
#region Свойства
|
||||
public override string Url => GetFullUrl();
|
||||
|
||||
#endregion Свойства
|
||||
|
||||
#region Вспомогательные функции
|
||||
|
||||
private string GetFullUrl()
|
||||
{
|
||||
#region Поля
|
||||
return $"{basePath.TrimEnd('/')}/{path.TrimStart('/')}";
|
||||
}
|
||||
|
||||
protected string basePath;
|
||||
#endregion Вспомогательные функции
|
||||
|
||||
#endregion Поля
|
||||
|
||||
#region Свойства
|
||||
public override string Url => GetFullUrl();
|
||||
|
||||
#endregion Свойства
|
||||
|
||||
#region Вспомогательные функции
|
||||
|
||||
private string GetFullUrl()
|
||||
{
|
||||
return $"{basePath.TrimEnd('/')}/{path.TrimStart('/')}";
|
||||
}
|
||||
|
||||
#endregion Вспомогательные функции
|
||||
|
||||
public YBasePathRequestAttribute(string method, string url) : base(method, url)
|
||||
{
|
||||
}
|
||||
public YBasePathRequestAttribute(string method, string url) : base(method, url)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,9 @@
|
||||
namespace YandexMusic.API.Requests.Common.Attributes
|
||||
namespace YandexMusic.API.Requests.Common.Attributes;
|
||||
|
||||
public class YLoginRequestAttribute : YBasePathRequestAttribute
|
||||
{
|
||||
public class YLoginRequestAttribute : YBasePathRequestAttribute
|
||||
public YLoginRequestAttribute(string method, string url) : base(method, url)
|
||||
{
|
||||
public YLoginRequestAttribute(string method, string url) : base(method, url)
|
||||
{
|
||||
basePath = "https://login.yandex.ru";
|
||||
}
|
||||
basePath = "https://login.yandex.ru";
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,9 @@
|
||||
namespace YandexMusic.API.Requests.Common.Attributes
|
||||
namespace YandexMusic.API.Requests.Common.Attributes;
|
||||
|
||||
public class YMobileProxyRequestAttribute : YBasePathRequestAttribute
|
||||
{
|
||||
public class YMobileProxyRequestAttribute : YBasePathRequestAttribute
|
||||
public YMobileProxyRequestAttribute(string method, string url) : base(method, url)
|
||||
{
|
||||
public YMobileProxyRequestAttribute(string method, string url) : base(method, url)
|
||||
{
|
||||
basePath = "https://mobileproxy.passport.yandex.net";
|
||||
}
|
||||
basePath = "https://mobileproxy.passport.yandex.net";
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,9 @@
|
||||
namespace YandexMusic.API.Requests.Common.Attributes
|
||||
namespace YandexMusic.API.Requests.Common.Attributes;
|
||||
|
||||
public class YOAuthMobileAttribute : YBasePathRequestAttribute
|
||||
{
|
||||
public class YOAuthMobileAttribute : YBasePathRequestAttribute
|
||||
public YOAuthMobileAttribute(string method, string url) : base(method, url)
|
||||
{
|
||||
public YOAuthMobileAttribute(string method, string url) : base(method, url)
|
||||
{
|
||||
basePath = "https://oauth.mobile.yandex.net";
|
||||
}
|
||||
basePath = "https://oauth.mobile.yandex.net";
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,9 @@
|
||||
namespace YandexMusic.API.Requests.Common.Attributes
|
||||
namespace YandexMusic.API.Requests.Common.Attributes;
|
||||
|
||||
public class YOAuthRequestAttribute : YBasePathRequestAttribute
|
||||
{
|
||||
public class YOAuthRequestAttribute : YBasePathRequestAttribute
|
||||
public YOAuthRequestAttribute(string method, string url) : base(method, url)
|
||||
{
|
||||
public YOAuthRequestAttribute(string method, string url) : base(method, url)
|
||||
{
|
||||
basePath = "https://oauth.yandex.ru";
|
||||
}
|
||||
basePath = "https://oauth.yandex.ru";
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,9 @@
|
||||
namespace YandexMusic.API.Requests.Common.Attributes
|
||||
namespace YandexMusic.API.Requests.Common.Attributes;
|
||||
|
||||
public class YPassportRequestAttribute : YBasePathRequestAttribute
|
||||
{
|
||||
public class YPassportRequestAttribute : YBasePathRequestAttribute
|
||||
public YPassportRequestAttribute(string method, string url) : base(method, url)
|
||||
{
|
||||
public YPassportRequestAttribute(string method, string url) : base(method, url)
|
||||
{
|
||||
basePath = "https://passport.yandex.ru";
|
||||
}
|
||||
basePath = "https://passport.yandex.ru";
|
||||
}
|
||||
}
|
||||
@@ -1,27 +1,26 @@
|
||||
namespace YandexMusic.API.Requests.Common.Attributes
|
||||
namespace YandexMusic.API.Requests.Common.Attributes;
|
||||
|
||||
/// <summary>
|
||||
/// Атрибут запроса без привязки к базовому адресу
|
||||
/// </summary>
|
||||
public class YRequestAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Атрибут запроса без привязки к базовому адресу
|
||||
/// </summary>
|
||||
public class YRequestAttribute : Attribute
|
||||
#region Поля
|
||||
|
||||
protected string path;
|
||||
|
||||
#endregion Поля
|
||||
|
||||
#region Свойства
|
||||
|
||||
public string Method { get; }
|
||||
public virtual string Url => path;
|
||||
|
||||
#endregion Свойства
|
||||
|
||||
public YRequestAttribute(string method, string url)
|
||||
{
|
||||
#region Поля
|
||||
|
||||
protected string path;
|
||||
|
||||
#endregion Поля
|
||||
|
||||
#region Свойства
|
||||
|
||||
public string Method { get; }
|
||||
public virtual string Url => path;
|
||||
|
||||
#endregion Свойства
|
||||
|
||||
public YRequestAttribute(string method, string url)
|
||||
{
|
||||
Method = method;
|
||||
path = url;
|
||||
}
|
||||
Method = method;
|
||||
path = url;
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,9 @@
|
||||
namespace YandexMusic.API.Requests.Common.Attributes
|
||||
namespace YandexMusic.API.Requests.Common.Attributes;
|
||||
|
||||
public class YWebApiRequestAttribute : YBasePathRequestAttribute
|
||||
{
|
||||
public class YWebApiRequestAttribute : YBasePathRequestAttribute
|
||||
public YWebApiRequestAttribute(string method, string url) : base(method, url)
|
||||
{
|
||||
public YWebApiRequestAttribute(string method, string url) : base(method, url)
|
||||
{
|
||||
basePath = "https://music.yandex.ru";
|
||||
}
|
||||
basePath = "https://music.yandex.ru";
|
||||
}
|
||||
}
|
||||
@@ -1,26 +1,25 @@
|
||||
using System.Net;
|
||||
|
||||
namespace YandexMusic.API.Requests.Common
|
||||
namespace YandexMusic.API.Requests.Common;
|
||||
|
||||
public class HttpContext
|
||||
{
|
||||
public class HttpContext
|
||||
public CookieContainer Cookies;
|
||||
|
||||
public HttpContext()
|
||||
{
|
||||
public CookieContainer Cookies;
|
||||
Cookies = new CookieContainer();
|
||||
}
|
||||
|
||||
public HttpContext()
|
||||
{
|
||||
Cookies = new CookieContainer();
|
||||
}
|
||||
public IWebProxy WebProxy { get; set; }
|
||||
|
||||
public IWebProxy WebProxy { get; set; }
|
||||
public long GetTimeInterval()
|
||||
{
|
||||
DateTime dt = TimeZoneInfo.ConvertTimeToUtc(DateTime.Now);
|
||||
DateTime dt1970 = new(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
|
||||
TimeSpan tsInterval = dt.Subtract(dt1970);
|
||||
long iMilliseconds = Convert.ToInt64(tsInterval.TotalMilliseconds);
|
||||
|
||||
public long GetTimeInterval()
|
||||
{
|
||||
DateTime dt = TimeZoneInfo.ConvertTimeToUtc(DateTime.Now);
|
||||
DateTime dt1970 = new(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
|
||||
TimeSpan tsInterval = dt.Subtract(dt1970);
|
||||
long iMilliseconds = Convert.ToInt64(tsInterval.TotalMilliseconds);
|
||||
|
||||
return iMilliseconds;
|
||||
}
|
||||
return iMilliseconds;
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,10 @@
|
||||
namespace YandexMusic.API.Requests.Common
|
||||
{
|
||||
internal class YConstants
|
||||
{
|
||||
public static string ClientId = "23cabbbdc6cd418abb4b39c32c41195d";
|
||||
public static string ClientSecret = "53bc75238f0c4d08a118e51fe9203300";
|
||||
namespace YandexMusic.API.Requests.Common;
|
||||
|
||||
public const string XClientId = "c0ebe342af7d48fbbbfcf2d2eedb8f9e";
|
||||
public const string XClientSecret = "ad0a908f0aa341a182a37ecd75bc319e";
|
||||
}
|
||||
internal class YConstants
|
||||
{
|
||||
public static string ClientId = "23cabbbdc6cd418abb4b39c32c41195d";
|
||||
public static string ClientSecret = "53bc75238f0c4d08a118e51fe9203300";
|
||||
|
||||
public const string XClientId = "c0ebe342af7d48fbbbfcf2d2eedb8f9e";
|
||||
public const string XClientSecret = "ad0a908f0aa341a182a37ecd75bc319e";
|
||||
}
|
||||
@@ -1,48 +1,45 @@
|
||||
using YandexMusic.API.Common;
|
||||
using YandexMusic.API.Common.Providers;
|
||||
|
||||
namespace YandexMusic.API.Requests.Common
|
||||
namespace YandexMusic.API.Requests.Common;
|
||||
|
||||
internal class YRequest<T>
|
||||
{
|
||||
internal class YRequest<T>
|
||||
#region Поля
|
||||
|
||||
private HttpRequestMessage msg;
|
||||
private IRequestProvider provider;
|
||||
|
||||
protected YandexMusicApi api;
|
||||
|
||||
#endregion Поля
|
||||
|
||||
public YRequest(HttpRequestMessage message, YandexMusicApi yandex, AuthStorage auth)
|
||||
{
|
||||
#region Поля
|
||||
|
||||
private HttpRequestMessage msg;
|
||||
private IRequestProvider provider;
|
||||
|
||||
protected YandexMusicApi api;
|
||||
|
||||
#endregion Поля
|
||||
|
||||
|
||||
|
||||
public YRequest(HttpRequestMessage message, YandexMusicApi yandex, AuthStorage auth)
|
||||
{
|
||||
msg = message;
|
||||
api = yandex;
|
||||
provider = auth.Provider;
|
||||
}
|
||||
|
||||
public async Task<T> GetResponseAsync()
|
||||
{
|
||||
if (msg == null)
|
||||
return default;
|
||||
|
||||
HttpResponseMessage response = await provider.GetWebResponseAsync(msg);
|
||||
|
||||
if (typeof(T) == typeof(HttpResponseMessage))
|
||||
return (T)(object)response;
|
||||
|
||||
try
|
||||
{
|
||||
return await provider.GetDataFromResponseAsync<T>(api, response);
|
||||
}
|
||||
finally
|
||||
{
|
||||
response.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
msg = message;
|
||||
api = yandex;
|
||||
provider = auth.Provider;
|
||||
}
|
||||
|
||||
public async Task<T> GetResponseAsync()
|
||||
{
|
||||
if (msg == null)
|
||||
return default;
|
||||
|
||||
HttpResponseMessage response = await provider.GetWebResponseAsync(msg);
|
||||
|
||||
if (typeof(T) == typeof(HttpResponseMessage))
|
||||
return (T)(object)response;
|
||||
|
||||
try
|
||||
{
|
||||
return await provider.GetDataFromResponseAsync<T>(api, response);
|
||||
}
|
||||
finally
|
||||
{
|
||||
response.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -3,156 +3,95 @@ using System.Net;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Web;
|
||||
|
||||
using YandexMusic.API.Common;
|
||||
using YandexMusic.API.Extensions;
|
||||
using YandexMusic.API.Requests.Common.Attributes;
|
||||
|
||||
namespace YandexMusic.API.Requests.Common
|
||||
namespace YandexMusic.API.Requests.Common;
|
||||
|
||||
/// <summary>Базовый строитель HTTP-запросов к API Яндекс.Музыки.</summary>
|
||||
/// <typeparam name="TResponse">Тип ответа.</typeparam>
|
||||
/// <typeparam name="TParams">Тип параметров запроса.</typeparam>
|
||||
public abstract class YRequestBuilder<TResponse, TParams>
|
||||
{
|
||||
public class YRequestBuilder<ResponseType, ParamsTuple>
|
||||
private readonly YRequestAttribute _requestInfo;
|
||||
private Dictionary<string, string> _substitutions = null!;
|
||||
private readonly JsonSerializerOptions _jsonOptions;
|
||||
|
||||
protected readonly YandexMusicApi api;
|
||||
protected readonly AuthStorage storage;
|
||||
protected string device;
|
||||
|
||||
protected YRequestBuilder(YandexMusicApi yandex, AuthStorage auth)
|
||||
{
|
||||
#region Поля
|
||||
_requestInfo = GetType().GetCustomAttribute<YRequestAttribute>()
|
||||
?? throw new NotImplementedException($"Отсутствует атрибут {nameof(YRequestAttribute)}");
|
||||
api = yandex;
|
||||
storage = auth;
|
||||
device = $"os=CSharp; os_version=; manufacturer=K1llM@n; model=Yandex Music API; clid=; device_id={storage.DeviceId}; uuid=random";
|
||||
|
||||
private readonly JsonSerializerSettings jsonSettings = new()
|
||||
_jsonOptions = new JsonSerializerOptions
|
||||
{
|
||||
Converters = new List<JsonConverter> {
|
||||
new StringEnumConverter {
|
||||
NamingStrategy = new CamelCaseNamingStrategy()
|
||||
}
|
||||
},
|
||||
NullValueHandling = NullValueHandling.Ignore,
|
||||
ContractResolver = new CamelCasePropertyNamesContractResolver()
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||
Converters = { new JsonStringEnumConverter(JsonNamingPolicy.CamelCase) }
|
||||
};
|
||||
|
||||
private YRequestAttribute requestInfo;
|
||||
private Dictionary<string, string> subs;
|
||||
|
||||
protected YandexMusicApi api;
|
||||
protected AuthStorage storage;
|
||||
protected string device;
|
||||
|
||||
#endregion Поля
|
||||
|
||||
#region Свойства
|
||||
|
||||
protected YandexMusicApi API => api;
|
||||
protected AuthStorage Storage => storage;
|
||||
|
||||
#endregion Свойства
|
||||
|
||||
#region Вспомогательные функции
|
||||
|
||||
private Uri BuildUri(ParamsTuple tuple)
|
||||
{
|
||||
NameValueCollection queryParams = GetQueryParams(tuple);
|
||||
NameValueCollection modifiedParams = HttpUtility.ParseQueryString(string.Empty);
|
||||
|
||||
// Подстановка в параметры
|
||||
foreach (string key in queryParams.Keys)
|
||||
{
|
||||
modifiedParams.Set(key, ReplaceSubs(queryParams.Get(key)));
|
||||
}
|
||||
|
||||
string endpoint = ReplaceSubs(requestInfo.Url);
|
||||
|
||||
UriBuilder builder = new(endpoint)
|
||||
{
|
||||
Query = modifiedParams.ToString() ?? string.Empty
|
||||
};
|
||||
|
||||
|
||||
return builder.Uri;
|
||||
}
|
||||
|
||||
private HttpRequestMessage CreateMessage(ParamsTuple tuple)
|
||||
{
|
||||
HttpRequestMessage msg = new()
|
||||
{
|
||||
RequestUri = BuildUri(tuple),
|
||||
Method = new HttpMethod(requestInfo.Method),
|
||||
Content = GetContent(tuple)
|
||||
};
|
||||
|
||||
msg.Headers.TryAddWithoutValidation(HttpRequestHeader.AcceptCharset.GetName(), Encoding.UTF8.WebName);
|
||||
msg.Headers.TryAddWithoutValidation(HttpRequestHeader.AcceptEncoding.GetName(), "gzip");
|
||||
|
||||
// Добавление заголовка авторизации
|
||||
if (!string.IsNullOrEmpty(storage.Token))
|
||||
msg.Headers.TryAddWithoutValidation(HttpRequestHeader.Authorization.GetName(), $"OAuth {storage.Token}");
|
||||
|
||||
SetCustomHeaders(msg.Headers);
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
protected string ReplaceSubs(string str)
|
||||
{
|
||||
string[] sub = str.GetMatches(@"\{.+?\}");
|
||||
|
||||
foreach (string s in sub)
|
||||
{
|
||||
if (!subs.TryGetValue(s.ReplaceRegex(@"[\{\}]", string.Empty), out string value))
|
||||
throw new Exception($"Не найдена подстановка {s}");
|
||||
|
||||
str = str.Replace(s, value);
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
protected virtual Dictionary<string, string> GetSubstitutions(ParamsTuple tuple)
|
||||
{
|
||||
return new Dictionary<string, string>();
|
||||
}
|
||||
|
||||
protected virtual NameValueCollection GetQueryParams(ParamsTuple tuple)
|
||||
{
|
||||
return new NameValueCollection();
|
||||
}
|
||||
|
||||
protected virtual HttpContent GetContent(ParamsTuple tuple)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
protected virtual void SetCustomHeaders(HttpRequestHeaders headers)
|
||||
{
|
||||
}
|
||||
|
||||
protected string SerializeJson(object data)
|
||||
{
|
||||
return JsonConvert.SerializeObject(data, jsonSettings);
|
||||
}
|
||||
|
||||
#endregion Вспомогательные функции
|
||||
|
||||
|
||||
|
||||
public YRequestBuilder(YandexMusicApi yandex, AuthStorage auth)
|
||||
{
|
||||
requestInfo = GetType()
|
||||
.GetCustomAttributes<YRequestAttribute>()
|
||||
.FirstOrDefault();
|
||||
|
||||
if (requestInfo == null)
|
||||
throw new NotImplementedException($"Отсутствует атрибут {nameof(YRequestAttribute)}");
|
||||
|
||||
api = yandex;
|
||||
storage = auth;
|
||||
|
||||
// Устройство по умолчанию
|
||||
device = $"os=CSharp; os_version=; manufacturer=K1llM@n; model=Yandex Music API; clid=; device_id={storage.DeviceId}; uuid=random";
|
||||
}
|
||||
|
||||
internal YRequest<ResponseType> Build(ParamsTuple tuple)
|
||||
{
|
||||
subs = GetSubstitutions(tuple);
|
||||
HttpRequestMessage msg = CreateMessage(tuple);
|
||||
|
||||
return new YRequest<ResponseType>(msg, api, storage);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private Uri BuildUri(TParams tuple)
|
||||
{
|
||||
var queryParams = GetQueryParams(tuple);
|
||||
var modifiedParams = HttpUtility.ParseQueryString(string.Empty);
|
||||
foreach (string? key in queryParams)
|
||||
if (key != null)
|
||||
modifiedParams[key] = ReplaceSubs(queryParams[key]!);
|
||||
var endpoint = ReplaceSubs(_requestInfo.Url);
|
||||
var builder = new UriBuilder(endpoint) { Query = modifiedParams.ToString() ?? string.Empty };
|
||||
return builder.Uri;
|
||||
}
|
||||
|
||||
private HttpRequestMessage CreateMessage(TParams tuple)
|
||||
{
|
||||
var msg = new HttpRequestMessage
|
||||
{
|
||||
RequestUri = BuildUri(tuple),
|
||||
Method = new HttpMethod(_requestInfo.Method),
|
||||
Content = GetContent(tuple)
|
||||
};
|
||||
msg.Headers.TryAddWithoutValidation(HttpRequestHeader.AcceptCharset.GetName(), Encoding.UTF8.WebName);
|
||||
msg.Headers.TryAddWithoutValidation(HttpRequestHeader.AcceptEncoding.GetName(), "gzip");
|
||||
if (!string.IsNullOrEmpty(storage.Token))
|
||||
msg.Headers.TryAddWithoutValidation(HttpRequestHeader.Authorization.GetName(), $"OAuth {storage.Token}");
|
||||
SetCustomHeaders(msg.Headers);
|
||||
return msg;
|
||||
}
|
||||
|
||||
protected string ReplaceSubs(string str)
|
||||
{
|
||||
var subs = str.GetMatches(@"\{.+?\}");
|
||||
foreach (var s in subs)
|
||||
{
|
||||
var key = s.ReplaceRegex(@"[\{\}]", string.Empty);
|
||||
if (!_substitutions.TryGetValue(key, out var value))
|
||||
throw new Exception($"Не найдена подстановка {s}");
|
||||
str = str.Replace(s, value);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
protected virtual Dictionary<string, string> GetSubstitutions(TParams tuple) => [];
|
||||
protected virtual NameValueCollection GetQueryParams(TParams tuple) => [];
|
||||
protected virtual HttpContent? GetContent(TParams tuple) => null;
|
||||
protected virtual void SetCustomHeaders(HttpRequestHeaders headers) { }
|
||||
protected string SerializeJson(object data) => JsonSerializer.Serialize(data, _jsonOptions);
|
||||
|
||||
internal YRequest<TResponse> Build(TParams tuple)
|
||||
{
|
||||
_substitutions = GetSubstitutions(tuple);
|
||||
var msg = CreateMessage(tuple);
|
||||
return new YRequest<TResponse>(msg, api, storage);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user