using System.Net.Mime; using Microsoft.Extensions.Primitives; namespace InstaFollowersOverseer; /// /// Class that builds selegram message with html tags. /// It is a state machine. /// Exapmple: /// SetUrl("https://x.com").BeginStyle(TextStyle.Url).Text("X").EndStyle() /// opens html a tag with href=https://x.com and content X, then closes tag /// // supported tags: // bold // italic // crossed // underline // spoiler // inline URL // inline mention of a user // inlne code //
code block
public class HtmlMessageBuilder { record struct BuilderState(TextStyle Style, string? Url = null, long? UserId = null, string? CodeLang = null) { public void Reset() { Style = TextStyle.PlainText; Url = null; UserId = null; CodeLang = null; } } private BuilderState _state=new(TextStyle.PlainText); StringBuilder _plainText=new(); StringBuilder _html=new(); protected void ReplaceHtmlReservedChar(char c) => _html.Append(c switch { '<'=>"<", '>'=>">", '&'=>"&apm", '"'=>""", '\''=>"&apos", _ => c }); protected void ReplaceHtmlReservedChars(ReadOnlySpan text) { for (int i = 0; i < text.Length; i++) ReplaceHtmlReservedChar(text[i]); } /// opens html tags enabled in state fields protected void OpenTags() { if(_state.Style==TextStyle.PlainText) return; // the order of fields is very importang, it must be in reversed in CloseTags() if (0!=(_state.Style & TextStyle.Bold)) _html.Append(""); if (0!=(_state.Style & TextStyle.Italic)) _html.Append(""); if (0!=(_state.Style & TextStyle.Crossed)) _html.Append(""); if (0!=(_state.Style & TextStyle.Underline)) _html.Append(""); if (0!=(_state.Style & TextStyle.Spoiler)) _html.Append(""); if (0!=(_state.Style & TextStyle.Link)) { _html.Append(""); } if (0!=(_state.Style & TextStyle.CodeLine)) _html.Append(""); if (0!=(_state.Style & TextStyle.CodeBlock)) { _html.Append("'); } } /// closes opened html tags protected void CloseTags() { if(_state.Style==TextStyle.PlainText) return; // the order of fields is very importang, it must be in reversed in CloseTags() if (0!=(_state.Style & TextStyle.CodeBlock)) _html.Append(""); if (0!=(_state.Style & TextStyle.CodeLine)) _html.Append(""); if (0!=(_state.Style & TextStyle.Link)) _html.Append(""); if (0!=(_state.Style & TextStyle.Spoiler)) _html.Append(""); if (0!=(_state.Style & TextStyle.Underline)) _html.Append(""); if (0!=(_state.Style & TextStyle.Crossed)) _html.Append(""); if (0!=(_state.Style & TextStyle.Italic)) _html.Append(""); if (0!=(_state.Style & TextStyle.Bold)) _html.Append(""); _state.Reset(); } /// appends text to builder public HtmlMessageBuilder Text(string text) { _plainText.Append(text); ReplaceHtmlReservedChars(text); return this; } public HtmlMessageBuilder Text(char ch) { _plainText.Append(ch); ReplaceHtmlReservedChar(ch); return this; } public HtmlMessageBuilder Text(int o) { string text = o.ToString(); _plainText.Append(text); ReplaceHtmlReservedChars(text); return this; } public HtmlMessageBuilder Text(long o) { string text = o.ToString(); _plainText.Append(text); ReplaceHtmlReservedChars(text); return this; } public HtmlMessageBuilder Text(object o) { if (o is null) throw new NullReferenceException("object is null"); string text = o.ToString()!; _plainText.Append(text); ReplaceHtmlReservedChars(text); return this; } /// enables specified styles public HtmlMessageBuilder BeginStyle(TextStyle style) { if (_state.Style != TextStyle.PlainText) throw new Exception("can't begin new style before ending previous"); _state.Style = style; OpenTags(); return this; } /// removes all styles public HtmlMessageBuilder EndStyle() { CloseTags(); return this; } // use before BeginStyle public HtmlMessageBuilder SetUrl(string url) { _state.Url = url; return this; } public HtmlMessageBuilder SetUserMention(long id) { _state.UserId=id ; return this; } public HtmlMessageBuilder SetCodeLanguage(string codeLang="") { _state.CodeLang=codeLang ; return this; } public string ToPlainText() => _plainText.ToString(); public string ToHtml() => _html.ToString(); #if DEBUG public override string ToString() => ToHtml(); #else public override string ToString() => ToPlainText(); #endif public void Clear() { _plainText.Clear(); _html.Clear(); _state.Reset(); } }