From ec0e68ef544f0da1f784022c3264fd51014dd1c5 Mon Sep 17 00:00:00 2001 From: ayn2op Date: Thu, 30 Jul 2026 14:32:26 -0400 Subject: [PATCH] feat(markdown): mask spoilers (#820) --- internal/config/config.go | 5 ++-- internal/config/config.toml | 2 ++ internal/markdown/renderer.go | 6 +++++ internal/markdown/renderer_test.go | 39 ++++++++++++++++++++++++++++++ 4 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 internal/markdown/renderer_test.go diff --git a/internal/config/config.go b/internal/config/config.go index 1a3b18b..0560eed 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -65,8 +65,9 @@ type ( } MarkdownConfig struct { - Enabled bool `toml:"enabled"` - Theme string `toml:"theme"` + Enabled bool `toml:"enabled"` + MaskSpoilers bool `toml:"mask_spoilers"` + Theme string `toml:"theme"` } HelpConfig struct { diff --git a/internal/config/config.toml b/internal/config/config.toml index 7ea2110..4d04c92 100644 --- a/internal/config/config.toml +++ b/internal/config/config.toml @@ -28,6 +28,8 @@ max_height = 10 [markdown] # Whether to parse and render markdown in messages or not. enabled = true +# Replace spoiler contents with a placeholder. +mask_spoilers = false # Theme for fenced code blocks. Available themes: https://xyproto.github.io/splash/docs theme = "monokai" diff --git a/internal/markdown/renderer.go b/internal/markdown/renderer.go index 1513961..e550607 100644 --- a/internal/markdown/renderer.go +++ b/internal/markdown/renderer.go @@ -118,6 +118,12 @@ func (r *Renderer) RenderLines(source []byte, node ast.Node, base tcell.Style) [ builder.NewLine() } case *discordmd.Inline: + if r.cfg.Markdown.MaskSpoilers && node.Attr&discordmd.AttrSpoiler != 0 { + if entering { + builder.Write("[spoiler]", currentStyle()) + } + return ast.WalkSkipChildren, nil + } if entering { pushStyle(applyInlineAttr(currentStyle(), node.Attr, linkDepth > 0)) } else { diff --git a/internal/markdown/renderer_test.go b/internal/markdown/renderer_test.go new file mode 100644 index 0000000..cc49398 --- /dev/null +++ b/internal/markdown/renderer_test.go @@ -0,0 +1,39 @@ +package markdown + +import ( + "strings" + "testing" + + "github.com/ayn2op/discordo/internal/config" + "github.com/ayn2op/ningen/v3/discordmd" + "github.com/gdamore/tcell/v3" + "github.com/yuin/goldmark/parser" + "github.com/yuin/goldmark/text" +) + +func TestRendererMasksSpoilers(t *testing.T) { + source := []byte("before ||secret|| after") + node := parser.NewParser( + parser.WithBlockParsers(discordmd.BlockParsers()...), + parser.WithInlineParsers(discordmd.InlineParserWithLink()...), + ).Parse(text.NewReader(source)) + + for _, test := range []struct { + mask bool + want string + }{ + {true, "before [spoiler] after"}, + {false, "before secret after"}, + } { + lines := NewRenderer(&config.Config{Markdown: config.MarkdownConfig{MaskSpoilers: test.mask}}).RenderLines(source, node, tcell.StyleDefault) + var got strings.Builder + for _, line := range lines { + for _, segment := range line { + got.WriteString(segment.Text) + } + } + if got.String() != test.want { + t.Fatalf("mask=%t: got %q, want %q", test.mask, got.String(), test.want) + } + } +}