summaryrefslogtreecommitdiff
path: root/internal/transcoder/markdown.go
diff refs
from: back
to: back
| flip
diff options
context:
space:
mode:
Diffstat (limited to 'internal/transcoder/markdown.go')
-rw-r--r--internal/transcoder/markdown.go164
1 files changed, 0 insertions, 164 deletions
diff --git a/internal/transcoder/markdown.go b/internal/transcoder/markdown.go
deleted file mode 100644
index 975c3bc..0000000
--- a/internal/transcoder/markdown.go
+++ /dev/null
@@ -1,164 +0,0 @@
-package transcoder
-
-import (
- "fmt"
- "strings"
-
- "github.com/yuin/goldmark"
- "github.com/yuin/goldmark/ast"
- "github.com/yuin/goldmark/extension"
- xast "github.com/yuin/goldmark/extension/ast"
- "github.com/yuin/goldmark/text"
-)
-
-// telegramMD parses CommonMark once and is reused across calls.
-var telegramMD = goldmark.New(goldmark.WithExtensions(extension.Strikethrough, extension.Linkify))
-
-// markdownToTelegramHTML converts CommonMark source into the limited HTML
-// subset Telegram accepts (parse_mode=HTML). Telegram supports only
-// b/i/u/s/a/code/pre/blockquote — block constructs with no Telegram tag
-// (paragraphs, headings, lists) are flattened to text + newlines. Anything
-// outside the subset is dropped rather than emitted, so a message is never
-// rejected for an unsupported tag.
-func markdownToTelegramHTML(src string) string {
- source := []byte(src)
- doc := telegramMD.Parser().Parse(text.NewReader(source))
-
- var b strings.Builder
- renderNodes(&b, doc, source)
-
- // Collapse the runs of blank lines block rendering can leave behind.
- out := strings.TrimSpace(b.String())
- for strings.Contains(out, "\n\n\n") {
- out = strings.ReplaceAll(out, "\n\n\n", "\n\n")
- }
- return out
-}
-
-func renderNodes(b *strings.Builder, parent ast.Node, source []byte) {
- for n := parent.FirstChild(); n != nil; n = n.NextSibling() {
- renderNode(b, n, source)
- }
-}
-
-func renderNode(b *strings.Builder, n ast.Node, source []byte) {
- switch node := n.(type) {
- case *ast.Document:
- renderNodes(b, node, source)
-
- case *ast.Paragraph, *ast.TextBlock:
- renderNodes(b, node, source)
- b.WriteString("\n\n")
-
- case *ast.Heading:
- // Telegram has no headings; render the line in bold.
- b.WriteString("<b>")
- renderNodes(b, node, source)
- b.WriteString("</b>\n\n")
-
- case *ast.Blockquote:
- b.WriteString("<blockquote>")
- renderNodes(b, node, source)
- trimTrailingNewlines(b)
- b.WriteString("</blockquote>\n\n")
-
- case *ast.List:
- renderList(b, node, source)
- b.WriteString("\n")
-
- case *ast.FencedCodeBlock, *ast.CodeBlock:
- b.WriteString("<pre>")
- writeRawLines(b, n, source)
- b.WriteString("</pre>\n\n")
-
- case *ast.ThematicBreak:
- // horizontal rule — nothing meaningful in a Telegram message
-
- // --- inline ---
- case *ast.Text:
- b.WriteString(escapeHTML(string(node.Segment.Value(source))))
- if node.HardLineBreak() || node.SoftLineBreak() {
- b.WriteByte('\n')
- }
- case *ast.String:
- b.WriteString(escapeHTML(string(node.Value)))
-
- case *ast.Emphasis:
- tag := "i"
- if node.Level == 2 {
- tag = "b"
- }
- fmt.Fprintf(b, "<%s>", tag)
- renderNodes(b, node, source)
- fmt.Fprintf(b, "</%s>", tag)
-
- case *xast.Strikethrough:
- b.WriteString("<s>")
- renderNodes(b, node, source)
- b.WriteString("</s>")
-
- case *ast.CodeSpan:
- b.WriteString("<code>")
- renderNodes(b, node, source)
- b.WriteString("</code>")
-
- case *ast.Link:
- fmt.Fprintf(b, `<a href="%s">`, escapeHTML(string(node.Destination)))
- renderNodes(b, node, source)
- b.WriteString("</a>")
-
- case *ast.AutoLink:
- url := string(node.URL(source))
- fmt.Fprintf(b, `<a href="%s">%s</a>`, escapeHTML(url), escapeHTML(url))
-
- case *ast.Image:
- // Images can't render inline in text; keep the alt text only.
- renderNodes(b, node, source)
-
- case *ast.RawHTML, *ast.HTMLBlock:
- // Drop raw HTML — it is almost certainly not in Telegram's tag subset.
-
- default:
- // Unknown node: recurse so inline text inside it is not lost.
- renderNodes(b, n, source)
- }
-}
-
-func renderList(b *strings.Builder, list *ast.List, source []byte) {
- i := list.Start
- for item := list.FirstChild(); item != nil; item = item.NextSibling() {
- if list.IsOrdered() {
- fmt.Fprintf(b, "%d. ", i)
- i++
- } else {
- b.WriteString("• ")
- }
- renderNodes(b, item, source)
- trimTrailingNewlines(b)
- b.WriteByte('\n')
- }
-}
-
-func writeRawLines(b *strings.Builder, n ast.Node, source []byte) {
- lines := n.Lines()
- for i := 0; i < lines.Len(); i++ {
- seg := lines.At(i)
- b.WriteString(escapeHTML(string(seg.Value(source))))
- }
-}
-
-func trimTrailingNewlines(b *strings.Builder) {
- s := strings.TrimRight(b.String(), "\n")
- b.Reset()
- b.WriteString(s)
-}
-
-// escapeHTML escapes the three characters Telegram's HTML parser treats as
-// markup. Quotes are escaped too so the value is safe inside an href="...".
-func escapeHTML(s string) string {
- s = strings.ReplaceAll(s, "&", "&amp;")
- s = strings.ReplaceAll(s, "<", "&lt;")
- s = strings.ReplaceAll(s, ">", "&gt;")
- s = strings.ReplaceAll(s, `"`, "&quot;")
- return s
-}