< Summary

Information
Class: Services.ApiServices.TelegramBotApi.TelegramMessageBuilder
Assembly: Services
File(s): /home/runner/work/Studio2Prod/Studio2Prod/AppRepository/ApiServices/TelegramBotApi/TelegramMessageBuilder.cs
Line coverage
100%
Covered lines: 42
Uncovered lines: 0
Coverable lines: 42
Total lines: 121
Line coverage: 100%
Branch coverage
94%
Covered branches: 36
Total branches: 38
Branch coverage: 94.7%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor()100%11100%
Encode(...)50%22100%
Title(...)100%22100%
Line(...)100%22100%
Lines(...)100%66100%
Field(...)100%44100%
Link(...)100%11100%
Pre(...)100%22100%
Raw(...)100%11100%
BuildChunks(...)91.66%1212100%
Blank()100%11100%
Build(...)100%88100%

File(s)

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

#LineLine coverage
 1using System.Net;
 2using System.Text;
 3
 4namespace Services.ApiServices.TelegramBotApi;
 5
 6/// <summary>
 7/// Собирает HTML-сообщение построчно. Весь пользовательский текст экранируется, длина режется по границе строки,
 8/// чтобы не разорвать открытый тег.
 9/// </summary>
 10public sealed class TelegramMessageBuilder
 11{
 12    /// <summary>Лимит Bot API на текст сообщения — 4096; оставляем запас под маркер обрезки.</summary>
 13    public const int MaxLength = 4000;
 14    public const int MaxCaptionLength = 1000;
 15
 16    private const string TruncatedMark = "… <i>(обрезано)</i>";
 7417    private readonly List<string> _lines = [];
 18
 253119    public static string Encode(string? text) => WebUtility.HtmlEncode(text ?? "");
 20
 21    /// <summary>Жирный заголовок.</summary>
 22    public TelegramMessageBuilder Title(string text, string? icon = null)
 23    {
 8424        _lines.Add($"{(icon == null ? "" : icon + " ")}<b>{Encode(text)}</b>");
 8425        return this;
 26    }
 27
 28    public TelegramMessageBuilder Line(string? text)
 29    {
 103830        if (!string.IsNullOrWhiteSpace(text)) _lines.Add(Encode(text));
 51931        return this;
 32    }
 33
 34    public TelegramMessageBuilder Lines(IEnumerable<string>? lines, string bullet = "• ")
 35    {
 1836        foreach (var line in lines ?? [])
 437            if (!string.IsNullOrWhiteSpace(line)) _lines.Add(bullet + Encode(line));
 738        return this;
 39    }
 40
 41    /// <summary>Строка «Метка: значение»; пустые значения пропускаются.</summary>
 42    public TelegramMessageBuilder Field(string label, string? value, bool code = false)
 43    {
 13244        if (string.IsNullOrWhiteSpace(value)) return this;
 13045        _lines.Add(code ? $"{Encode(label)}: <code>{Encode(value)}</code>" : $"{Encode(label)}: {Encode(value)}");
 13046        return this;
 47    }
 48
 49    public TelegramMessageBuilder Link(string text, string url)
 50    {
 5351        _lines.Add($"<a href=\"{WebUtility.HtmlEncode(url)}\">{Encode(text)}</a>");
 5352        return this;
 53    }
 54
 55    /// <summary>Моноширинный блок (стектрейсы, JSON).</summary>
 56    public TelegramMessageBuilder Pre(string? text)
 57    {
 5358        if (!string.IsNullOrWhiteSpace(text)) _lines.Add($"<pre>{Encode(text)}</pre>");
 2759        return this;
 60    }
 61
 62    /// <summary>Готовый HTML-блок (может быть многострочным); экранирование — на вызывающем.</summary>
 63    public TelegramMessageBuilder Raw(string html)
 64    {
 28265        _lines.Add(html);
 28266        return this;
 67    }
 68
 69    /// <summary>
 70    /// Разбивает содержимое на сообщения не длиннее <paramref name="maxLength"/> по границам блоков.
 71    /// Блок, который сам не влезает, обрезается.
 72    /// </summary>
 73    public IReadOnlyList<string> BuildChunks(int maxLength = MaxLength)
 74    {
 3875        var chunks = new List<string>();
 3876        var sb = new StringBuilder();
 85477        foreach (var raw in _lines)
 78        {
 38979            var block = raw.Length > maxLength ? raw[..(maxLength - TruncatedMark.Length - 1)] + TruncatedMark : raw;
 38980            if (sb.Length > 0 && sb.Length + 1 + block.Length > maxLength)
 81            {
 1082                chunks.Add(sb.ToString());
 1083                sb.Clear();
 84            }
 85
 73086            if (sb.Length > 0) sb.Append('\n');
 38987            sb.Append(block);
 88        }
 89
 7690        if (sb.Length > 0) chunks.Add(sb.ToString());
 3891        return chunks;
 92    }
 93
 94    public TelegramMessageBuilder Blank()
 95    {
 9496        _lines.Add("");
 9497        return this;
 98    }
 99
 100    public string Build(int maxLength = MaxLength)
 101    {
 36102        var sb = new StringBuilder();
 830103        for (var i = 0; i < _lines.Count; i++)
 104        {
 380105            var line = _lines[i];
 380106            var extra = (sb.Length > 0 ? 1 : 0) + line.Length;
 380107            if (sb.Length + extra > maxLength - TruncatedMark.Length - 1)
 108            {
 109                // Единственная слишком длинная строка (например <pre>) целиком не помещается — режем нечем, оставляем м
 2110                if (sb.Length > 0) sb.Append('\n');
 1111                sb.Append(TruncatedMark);
 1112                return sb.ToString();
 113            }
 114
 722115            if (sb.Length > 0) sb.Append('\n');
 379116            sb.Append(line);
 117        }
 118
 35119        return sb.ToString();
 120    }
 121}