Files
YandexMusic/YandexMusic.API/Requests/Common/YRequestBuilder.cs
FrigaT 526353d679
All checks were successful
Release / pack-and-publish (release) Successful in 36s
Переделано воспроизведение аудио
2026-04-21 11:14:36 +03:00

123 lines
5.7 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.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);
}
}