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;
/// Базовый строитель HTTP-запросов.
/// Тип параметров запроса.
public abstract class YRequestBuilder
{
/// HTTP-метод (GET, POST и т.д.).
protected abstract string Method { get; }
/// Базовый URL (например, "https://api.music.yandex.net").
protected abstract string BaseUrl { get; }
/// Шаблон пути (может содержать плейсхолдеры вида {id}).
protected abstract string PathTemplate { get; }
private readonly JsonSerializerOptions _jsonOptions;
/// Основной экземпляр API.
protected YandexMusicApi Api { get; }
/// Хранилище авторизации (сокращение для Api.Storage).
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 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 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().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();
return Regex.Matches(input, pattern, options)
.Cast()
.Select(m => m.Value)
.ToArray();
}
private string ReplaceSubs(string str, Dictionary 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 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);
/// Выполняет запрос и возвращает десериализованный ответ.
public async Task ExecuteRawAsync(TParams parameters)
{
var substitutions = GetSubstitutions(parameters);
using var msg = CreateMessage(parameters, substitutions);
return await Api.HttpClient.SendAsync(msg);
}
}