X-Git-Url: http://git.giacomolli.uk/onechat/blobdiff_plain/de170709599e849c6b409d247b800dc68b0c7ab7..0b97bb2cc4bc0b8f8fe27cc7aa3f9d1d89ed28c8:/telegram_markdown.go?ds=inline diff --git a/telegram_markdown.go b/telegram_markdown.go index 16b83f2..8738bcd 100644 --- a/telegram_markdown.go +++ b/telegram_markdown.go @@ -11,6 +11,15 @@ import ( // plain. const telegramMarkdownPunctuation = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~" +var ( + // telegramTagEscaper writes the characters that rich Markdown reads as HTML + // as HTML entities. + telegramTagEscaper = strings.NewReplacer("&", "&", "<", "<") + + // telegramTagUnescaper reverses telegramTagEscaper. + telegramTagUnescaper = strings.NewReplacer("&", "&", "<", "<") +) + // telegramMarkdownEntities converts the Markdown of a reply to plain text and // Telegram message entities. It knows bold (** and __), italic (* and _), // strikethrough (~~), inline code, fenced code blocks with an optional @@ -68,35 +77,21 @@ func telegramMarkdownEntities(markdown string) (string, []TelegramEntity) { return out.text.String(), out.entities } -// telegramEscapeTags puts a backslash before each < of markdown outside inline -// code and fenced code blocks, so that rich Markdown shows a tag name as text -// and reads no raw HTML. A < that a backslash already makes plain stays as it -// is. A code block that does not close runs to the end, as in Markdown. +// telegramEscapeTags writes each & of markdown outside inline code and fenced +// code blocks as & and each < as <. Rich Markdown reads raw HTML and +// HTML entities in prose, and it does not read a backslash before < as an +// escape. With the entities, it shows a tag name and an entity that the model +// wrote in prose as text. func telegramEscapeTags(markdown string) string { - var out strings.Builder - lines := strings.Split(markdown, "\n") - for index := 0; index < len(lines); index++ { - if index > 0 { - out.WriteString("\n") - } - - line := lines[index] - _, fence := telegramCodeFence(line) - if fence == "" { - telegramEscapeLineTags(&out, line) - continue - } - - end := telegramClosingFence(lines, index, fence) - if end < 0 { - end = len(lines) - 1 - } - - out.WriteString(strings.Join(lines[index:end+1], "\n")) - index = end - } + return telegramRewriteProse(markdown, telegramTagEscaper.Replace) +} - return out.String() +// telegramUnescapeTags gives back the Markdown that telegramEscapeTags escaped. +// It is exact, because after the escape each & outside code starts & or +// <. It also works on a part of telegramSplitRich, because a part has the +// same code as the text that it comes from. +func telegramUnescapeTags(markdown string) string { + return telegramRewriteProse(markdown, telegramTagUnescaper.Replace) } // telegramCodeFence returns the language of a line that opens a fenced code @@ -256,17 +251,46 @@ func telegramHeadingText(line string) string { return rest } -// telegramEscapeLineTags writes line to out with a backslash before each < that -// is outside inline code and not already plain. A run of backticks that opens -// no inline code stays as text whole, as in Markdown. -func telegramEscapeLineTags(out *strings.Builder, line string) { +// telegramRewriteProse returns markdown with rewrite applied to each part of a +// line outside inline code and fenced code blocks. A code block that does not +// close runs to the end, as in Markdown. +func telegramRewriteProse(markdown string, rewrite func(string) string) string { + var out strings.Builder + lines := strings.Split(markdown, "\n") + for index := 0; index < len(lines); index++ { + if index > 0 { + out.WriteString("\n") + } + + line := lines[index] + _, fence := telegramCodeFence(line) + if fence == "" { + telegramRewriteLineProse(&out, line, rewrite) + continue + } + + end := telegramClosingFence(lines, index, fence) + if end < 0 { + end = len(lines) - 1 + } + + out.WriteString(strings.Join(lines[index:end+1], "\n")) + index = end + } + + return out.String() +} + +// telegramRewriteLineProse writes line to out with rewrite applied to each part +// outside inline code. A backtick after a backslash opens no code. A run of +// backticks that opens no inline code stays as text whole, as in Markdown. +func telegramRewriteLineProse(out *strings.Builder, line string, rewrite func(string) string) { + prose := 0 for index := 0; index < len(line); { switch line[index] { case '\\': if index+1 < len(line) && strings.IndexByte(telegramMarkdownPunctuation, line[index+1]) >= 0 { - out.WriteString(line[index : index+2]) index += 2 - continue } case '`': @@ -276,17 +300,18 @@ func telegramEscapeLineTags(out *strings.Builder, line string) { end = span.end } + out.WriteString(rewrite(line[prose:index])) out.WriteString(line[index:end]) index = end + prose = end continue - case '<': - out.WriteByte('\\') } - out.WriteByte(line[index]) index++ } + + out.WriteString(rewrite(line[prose:])) } // telegramMarkSpan returns the inline mark that starts at index of line and