diff --git a/internal/adapter/dingtalk.go b/internal/adapter/dingtalk.go index 5f4cd03..7e5a467 100644 --- a/internal/adapter/dingtalk.go +++ b/internal/adapter/dingtalk.go @@ -10,6 +10,7 @@ import ( "fmt" "net/http" "net/url" + "strings" "time" ) @@ -69,7 +70,7 @@ func (s *DingTalkSender) Send(title, content string, config json.RawMessage) err MsgType: "markdown", Markdown: &dingtalkMD{ Title: title, - Text: content, + Text: dingtalkMarkdownText(content), }, } @@ -86,6 +87,18 @@ func (s *DingTalkSender) Send(title, content string, config json.RawMessage) err return nil } +// dingtalkMarkdownText turns template newlines into DingTalk markdown paragraph +// breaks. A single \n is collapsed to a space by DingTalk markdown, so each +// logical line must be separated by \n\n. +func dingtalkMarkdownText(content string) string { + content = strings.ReplaceAll(content, "\r\n", "\n") + content = strings.ReplaceAll(content, "\r", "\n") + for strings.Contains(content, "\n\n") { + content = strings.ReplaceAll(content, "\n\n", "\n") + } + return strings.ReplaceAll(content, "\n", "\n\n") +} + func dingtalkSign(timestamp int64, secret string) string { mac := hmac.New(sha256.New, []byte(secret)) fmt.Fprintf(mac, "%d\n%s", timestamp, secret) diff --git a/internal/adapter/dingtalk_test.go b/internal/adapter/dingtalk_test.go index 0716e19..bb00def 100644 --- a/internal/adapter/dingtalk_test.go +++ b/internal/adapter/dingtalk_test.go @@ -2,6 +2,15 @@ package adapter import "testing" +func TestDingTalkMarkdownTextDoublesNewlines(t *testing.T) { + in := "空单开仓\n交易品种: ETHUSDT\n开仓价格: 2542.33\n\n推送时间: 2026.08.27 16:30:12" + got := dingtalkMarkdownText(in) + want := "空单开仓\n\n交易品种: ETHUSDT\n\n开仓价格: 2542.33\n\n推送时间: 2026.08.27 16:30:12" + if got != want { + t.Fatalf("got %q\nwant %q", got, want) + } +} + func TestDingtalkConfigWebhookURL(t *testing.T) { tests := []struct { name string