Files
YandexMusic/YandexMusic.API/Requests/Common/YRequestBuilder.cs
FrigaT 36e28ce3fe
All checks were successful
Release / pack-and-publish (release) Successful in 36s
Полностью переписанное api
2026-04-19 17:00:05 +03:00

132 lines
6.0 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.Collections.Specialized;
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;
namespace YandexMusic.API.Requests.Common;
/// <summary>Базовый строитель HTTP-запросов.</summary>
/// <typeparam name="TParams">Тип параметров запроса.</typeparam>
public abstract class YRequestBuilder<TParams>
{
/// <summary>HTTP-метод (GET, POST и т.д.).</summary>
protected abstract string Method { get; }
/// <summary>Базовый URL (например, "https://api.music.yandex.net").</summary>
protected abstract string BaseUrl { get; }
/// <summary>Шаблон пути (может содержать плейсхолдеры вида {id}).</summary>
protected abstract string PathTemplate { get; }
private readonly JsonSerializerOptions _jsonOptions;
/// <summary>Основной экземпляр API.</summary>
protected YandexMusicApi Api { get; }
/// <summary>Хранилище авторизации (сокращение для Api.Storage).</summary>
protected AuthStorage Storage => Api.Storage;
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('/')}";
private Uri BuildUri(TParams parameters, Dictionary<string, string> substitutions)
{
var queryParams = GetQueryParams(parameters);
var modifiedParams = HttpUtility.ParseQueryString(string.Empty);
foreach (string? key in queryParams)
if (key != null)
modifiedParams[key] = ReplaceSubs(queryParams[key]!, substitutions);
var endpoint = ReplaceSubs(FullUrl, substitutions);
var builder = new UriBuilder(endpoint) { Query = modifiedParams.ToString() ?? string.Empty };
return builder.Uri;
}
private HttpRequestMessage CreateMessage(TParams parameters, Dictionary<string, string> substitutions)
{
var msg = new HttpRequestMessage
{
RequestUri = BuildUri(parameters, substitutions),
Method = new HttpMethod(Method),
Content = GetContent(parameters)
};
msg.Headers.TryAddWithoutValidation(GetHeaderName(HttpRequestHeader.AcceptCharset), Encoding.UTF8.WebName);
msg.Headers.TryAddWithoutValidation(GetHeaderName(HttpRequestHeader.AcceptEncoding), "gzip");
if (!string.IsNullOrEmpty(Storage.Token))
msg.Headers.TryAddWithoutValidation(GetHeaderName(HttpRequestHeader.Authorization), $"OAuth {Storage.Token}");
SetCustomHeaders(msg.Headers);
return msg;
}
// Вспомогательный метод: преобразование HttpRequestHeader в строку (как было в HttpRequestHeaderExtensions)
private static string GetHeaderName(HttpRequestHeader header)
{
return SplitByCapitalLetter(header.ToString(), "-");
}
// Вспомогательный метод: разбиение строки по заглавным буквам (из StringExtensions)
private static string SplitByCapitalLetter(string str, string delimiter)
{
var matches = Regex.Matches(str, @"([A-Z]+)(?=([A-Z][a-z]|$)) | [A-Z][a-z].+?(?=([A-Z]|$))", RegexOptions.IgnorePatternWhitespace);
return string.Join(delimiter, matches.Cast<Match>().Select(m => m.Value));
}
// Вспомогательный метод: замена всех вхождений регулярного выражения (из StringExtensions)
private static string ReplaceRegex(string input, string pattern, string replacement, RegexOptions options = RegexOptions.IgnoreCase)
{
return string.IsNullOrEmpty(input) ? string.Empty : Regex.Replace(input, pattern, replacement, options);
}
// Вспомогательный метод: получение совпадений по регулярному выражению (из StringExtensions)
private static string[] GetMatches(string input, string pattern, RegexOptions options = RegexOptions.IgnoreCase)
{
if (string.IsNullOrEmpty(input) || !Regex.IsMatch(input, pattern, options))
return Array.Empty<string>();
return Regex.Matches(input, pattern, options)
.Cast<Match>()
.Select(m => m.Value)
.ToArray();
}
private string ReplaceSubs(string str, Dictionary<string, string> substitutions)
{
var subs = GetMatches(str, @"\{.+?\}");
foreach (var s in subs)
{
var key = ReplaceRegex(s, @"[\{\}]", 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 parameters) => [];
protected virtual NameValueCollection GetQueryParams(TParams parameters) => [];
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)
{
var substitutions = GetSubstitutions(parameters);
using var msg = CreateMessage(parameters, substitutions);
return await Api.HttpClient.SendAsync(msg);
}
}