package telegram import ( "fmt" "html" "strings" ) type parseMode string const ( parseModeUnset parseMode = "" parseModeMarkdown parseMode = "Markdown" parseModeMarkdownV2 parseMode = "MarkdownV2" parseModeHTML parseMode = "HTML" ) func normalizeParseMode(value string) parseMode { switch strings.ToLower(strings.TrimSpace(value)) { case "markdown": return parseModeMarkdown case "markdownv2": return parseModeMarkdownV2 case "html": return parseModeHTML default: return parseModeUnset } } func (pm parseMode) String() string { return string(pm) } type messageField struct { label string value string } type messageTemplate struct { title string fields []messageField emphasize []string } func (mt messageTemplate) Format(mode parseMode) string { var builder strings.Builder builder.WriteString(formatTitle(mode, mt.title, mt.emphasize)) builder.WriteString("\n") builder.WriteString("-----------------------------\n") formatter := selectValueFormatter(mode) for _, field := range mt.fields { appendMessageField(&builder, field.label, field.value, formatter) } return builder.String() } type valueFormatter func(string) string func formatTitle(mode parseMode, title string, emphasize []string) string { switch mode { case parseModeMarkdown: return highlightMarkdown(title, emphasize, escapeMarkdown) case parseModeMarkdownV2: return highlightMarkdown(title, emphasize, escapeMarkdownV2) case parseModeHTML: return highlightHTML(title, emphasize) default: return title } } func highlightMarkdown(title string, emphasize []string, esc func(string) string) string { if len(emphasize) == 0 { return title } result := title for _, word := range emphasize { word = strings.TrimSpace(word) if word == "" { continue } escaped := esc(word) replacement := fmt.Sprintf("*%s*", escaped) result = strings.ReplaceAll(result, word, replacement) } return result } func highlightHTML(title string, emphasize []string) string { if len(emphasize) == 0 { return html.EscapeString(title) } result := html.EscapeString(title) for _, word := range emphasize { word = strings.TrimSpace(word) if word == "" { continue } escaped := html.EscapeString(word) replacement := fmt.Sprintf("%s", escaped) result = strings.ReplaceAll(result, escaped, replacement) } return result } func appendMessageField(builder *strings.Builder, label, value string, formatter valueFormatter) { value = strings.TrimSpace(value) if value == "" { value = "—" } else if formatter != nil { value = formatter(value) } fmt.Fprintf(builder, "• %s: %s\n", label, value) } func selectValueFormatter(mode parseMode) valueFormatter { switch mode { case parseModeMarkdown: return func(value string) string { return fmt.Sprintf("*%s*", escapeMarkdown(value)) } case parseModeMarkdownV2: return func(value string) string { return fmt.Sprintf("*%s*", escapeMarkdownV2(value)) } case parseModeHTML: return func(value string) string { return fmt.Sprintf("%s", html.EscapeString(value)) } default: return nil } } var markdownEscaper = strings.NewReplacer( "*", "\\*", "_", "\\_", "[", "\\[", "]", "\\]", "(", "\\(", ")", "\\)", "`", "\\`", ) var markdownV2Escaper = strings.NewReplacer( "_", "\\_", "*", "\\*", "[", "\\[", "]", "\\]", "(", "\\(", ")", "\\)", "~", "\\~", "`", "\\`", ">", "\\>", "#", "\\#", "+", "\\+", "-", "\\-", "=", "\\=", "|", "\\|", "{", "\\{", "}", "\\}", ".", "\\.", "!", "\\!", ) func escapeMarkdown(value string) string { return markdownEscaper.Replace(value) } func escapeMarkdownV2(value string) string { return markdownV2Escaper.Replace(value) }