]> git.giacomolli.uk - onechat/blobdiff - telegram_markdown.go
Keep the connection of a new WhatsApp link
[onechat] / telegram_markdown.go
index 16b83f2f851d81bddd19353a1af8e6c43dfb6c97..8738bcdb38f57e673bd195e11d630505bbee0074 100644 (file)
@@ -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("&", "&amp;", "<", "&lt;")
+
+       // telegramTagUnescaper reverses telegramTagEscaper.
+       telegramTagUnescaper = strings.NewReplacer("&amp;", "&", "&lt;", "<")
+)
+
 // 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 &amp; and each < as &lt;. 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 &amp; or
+// &lt;. 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