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; /// Базовый строитель HTTP-запросов. /// Тип параметров запроса. internal 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; } /// Определяет, нужно ли добавлять заголовок Authorization для этого запроса. protected virtual bool ShouldAddAuthorization => true; /// Основной экземпляр API. protected YandexMusicApi Api { get; } /// Хранилище авторизации (сокращение для Api.Storage). protected AuthStorage Storage => Api.Storage; protected YRequestBuilder(YandexMusicApi api) { Api = api; } 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 (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().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) { } /// Выполняет запрос и возвращает десериализованный ответ. public async Task ExecuteRawAsync(TParams parameters) { var substitutions = GetSubstitutions(parameters); using var msg = CreateMessage(parameters, substitutions); return await Api.HttpClient.SendAsync(msg); } }