< Summary

Information
Class: Services.ApiServices.TelegramBotApi.TelegramBotApiClient
Assembly: Services
File(s): /home/runner/work/Studio2Prod/Studio2Prod/AppRepository/ApiServices/TelegramBotApi/TelegramBotApiClient.cs
Line coverage
69%
Covered lines: 27
Uncovered lines: 12
Coverable lines: 39
Total lines: 97
Line coverage: 69.2%
Branch coverage
57%
Covered branches: 15
Total branches: 26
Branch coverage: 57.6%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
get_IsConfigured()100%210%
GetMeAsync(...)100%11100%
SendMessageAsync(...)100%11100%
SendPhotoAsync(...)100%11100%
GetChatAsync(...)100%210%
EditMessageTextAsync(...)100%11100%
PinChatMessageAsync(...)100%210%
UnpinChatMessageAsync(...)100%210%
GetUpdatesAsync(...)0%620%
CallAsync()63.63%342270.83%
Sanitize(...)50%22100%

File(s)

/home/runner/work/Studio2Prod/Studio2Prod/AppRepository/ApiServices/TelegramBotApi/TelegramBotApiClient.cs

#LineLine coverage
 1using System.Net.Sockets;
 2using System.Text;
 3using Microsoft.Extensions.Logging;
 4using Newtonsoft.Json;
 5using Services.ApiServices.TelegramBotApi.Models;
 6
 7namespace Services.ApiServices.TelegramBotApi;
 8
 9/// <summary>
 10/// Тонкий клиент Telegram Bot API. Токен входит в путь запроса, поэтому он вычищается из любого
 11/// текста ошибки, а стандартное логирование <c>HttpClient</c> для этого клиента отключено при регистрации.
 12/// </summary>
 13public class TelegramBotApiClient
 14{
 15    private readonly HttpClient _httpClient;
 16    private readonly TelegramBotConfig _config;
 17    private readonly ILogger<TelegramBotApiClient>? _logger;
 18
 1419    public TelegramBotApiClient(HttpClient httpClient, TelegramBotConfig config, ILogger<TelegramBotApiClient>? logger =
 20    {
 1421        _httpClient = httpClient;
 1422        _config = config;
 1423        _logger = logger;
 1424    }
 25
 026    public bool IsConfigured => _config.IsConfigured;
 27
 28    public Task<TelegramApiResult<TelegramBotInfo>> GetMeAsync(CancellationToken ct = default) =>
 129        CallAsync<TelegramBotInfo>("getMe", null, ct);
 30
 31    public Task<TelegramApiResult<TelegramSentMessage>> SendMessageAsync(SendMessageRequest request, CancellationToken c
 1532        CallAsync<TelegramSentMessage>("sendMessage", request, ct);
 33
 34    public Task<TelegramApiResult<TelegramSentMessage>> SendPhotoAsync(SendPhotoRequest request, CancellationToken ct = 
 135        CallAsync<TelegramSentMessage>("sendPhoto", request, ct);
 36
 37    public Task<TelegramApiResult<TelegramChatInfo>> GetChatAsync(long chatId, CancellationToken ct = default) =>
 038        CallAsync<TelegramChatInfo>("getChat", new { chat_id = chatId }, ct);
 39
 40    /// <summary>Для обычных сообщений Telegram возвращает в <c>result</c> объект сообщения, для inline — <c>true</c>, п
 41    public Task<TelegramApiResult<Newtonsoft.Json.Linq.JToken>> EditMessageTextAsync(EditMessageTextRequest request, Can
 242        CallAsync<Newtonsoft.Json.Linq.JToken>("editMessageText", request, ct);
 43
 44    public Task<TelegramApiResult<bool>> PinChatMessageAsync(PinChatMessageRequest request, CancellationToken ct = defau
 045        CallAsync<bool>("pinChatMessage", request, ct);
 46
 47    public Task<TelegramApiResult<bool>> UnpinChatMessageAsync(UnpinChatMessageRequest request, CancellationToken ct = d
 048        CallAsync<bool>("unpinChatMessage", request, ct);
 49
 50    /// <summary>Входящие сообщения — удобно, чтобы узнать <c>chat_id</c> группы или пользователя.</summary>
 51    public Task<TelegramApiResult<List<TelegramUpdate>>> GetUpdatesAsync(long? offset = null, CancellationToken ct = def
 052        CallAsync<List<TelegramUpdate>>("getUpdates", offset.HasValue ? new { offset = offset.Value } : null, ct);
 53
 54    private async Task<TelegramApiResult<T>> CallAsync<T>(string method, object? body, CancellationToken ct)
 55    {
 1956        if (!_config.IsConfigured)
 057            return TelegramApiResult<T>.Failure("Telegram-бот не настроен или выключен");
 58
 59        try
 60        {
 61            // «./» обязателен: токен содержит «:», и без него «bot123:ABC/…» разбирается как абсолютный URI со схемой «
 1962            using var request = new HttpRequestMessage(HttpMethod.Post, new Uri($"./bot{_config.Token}/{method}", UriKin
 1963            if (body != null)
 1864                request.Content = new StringContent(JsonConvert.SerializeObject(body), Encoding.UTF8, "application/json"
 65
 1966            using var response = await _httpClient.SendAsync(request, ct);
 1567            var content = await response.Content.ReadAsStringAsync(ct);
 1568            var envelope = JsonConvert.DeserializeObject<TelegramEnvelope<T>>(content);
 69
 1570            if (envelope is { Ok: true })
 1171                return TelegramApiResult<T>.Success(envelope.Result);
 72
 473            var error = Sanitize(envelope?.Description ?? $"HTTP {(int)response.StatusCode}");
 474            _logger?.LogWarning("Telegram {Method}: {Error}", method, error);
 475            return TelegramApiResult<T>.Failure(error, envelope?.ErrorCode ?? (int)response.StatusCode);
 76        }
 077        catch (OperationCanceledException) when (ct.IsCancellationRequested)
 78        {
 079            throw;
 80        }
 481        catch (Exception ex) when (ex is HttpRequestException or TaskCanceledException or SocketException or JsonExcepti
 82        {
 483            var error = Sanitize(ex.Message);
 484            _logger?.LogWarning("Telegram {Method}: сбой запроса ({Type}): {Error}", method, ex.GetType().Name, error);
 485            return TelegramApiResult<T>.Failure(error);
 86        }
 087        catch (Exception ex)
 88        {
 089            var error = Sanitize(ex.Message);
 090            _logger?.LogError("Telegram {Method}: непредвиденная ошибка ({Type}): {Error}", method, ex.GetType().Name, e
 091            return TelegramApiResult<T>.Failure(error);
 92        }
 1993    }
 94
 95    private string Sanitize(string text) =>
 896        string.IsNullOrEmpty(_config.Token) ? text : text.Replace(_config.Token, "***");
 97}