123 lines
5.7 KiB
C#
123 lines
5.7 KiB
C#
using System.Collections.Specialized;
|
||
using System.Net;
|
||
using System.Net.Http.Headers;
|
||
using System.Text;
|
||
using System.Text.RegularExpressions;
|
||
using System.Web;
|
||
using YandexMusic.API.Common;
|
||
|
||
namespace YandexMusic.API.Requests.Common;
|
||
|
||
/// <summary>Базовый строитель HTTP-запросов.</summary>
|
||
/// <typeparam name="TParams">Тип параметров запроса.</typeparam>
|
||
internal 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; }
|
||
|
||
/// <summary>Определяет, нужно ли добавлять заголовок Authorization для этого запроса.</summary>
|
||
protected virtual bool ShouldAddAuthorization => true;
|
||
|
||
/// <summary>Основной экземпляр API.</summary>
|
||
protected YandexMusicApi Api { get; }
|
||
|
||
/// <summary>Хранилище авторизации (сокращение для Api.Storage).</summary>
|
||
protected AuthStorage Storage => Api.Storage;
|
||
|
||
protected YRequestBuilder(YandexMusicApi api)
|
||
{
|
||
Api = api;
|
||
}
|
||
|
||
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 (ShouldAddAuthorization && !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) { }
|
||
|
||
/// <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);
|
||
}
|
||
} |