From 793e77b073273390fca158669d71a9a489fc879a Mon Sep 17 00:00:00 2001 From: Giteabot Date: Thu, 8 Oct 2026 04:31:11 -0700 Subject: [PATCH] fix(webhook): keep line breaks in Telegram rich messages (#39650) (#39677) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Backport #39650 by @Kshot3000 Since the Telegram webhook switched to Bot API rich messages (https://github.com/go-gitea/gitea/pull/38298), a bare newline in `rich_message.html` is treated as insignificant whitespace, so multi-line messages — issue and PR bodies, comments, push commit lists — arrive in Telegram as a single paragraph. This restores the old layout by converting line breaks (`\n`, `\r\n`, `\r`) to `
` in `createTelegramPayloadHTML`, after sanitizing, covering every Telegram payload type at once. Verified: the new `Line breaks are kept in rich messages` test plus the updated Push/Issue/IssueComment/PullRequest/Review expectations fail on unpatched code (literal `\n` in the payload) and pass with the fix; the full `services/webhook` package passes, gofmt/vet clean. Fixes https://github.com/go-gitea/gitea/issues/39649 --- Tips welcome: PayPal kyleblake0659@gmail.com · BTC 3GnR7TWBXAB3pPztBWpNF4LMNEX5yX8vZK Co-authored-by: KShot --- services/webhook/telegram.go | 5 ++++- services/webhook/telegram_test.go | 30 +++++++++++++----------------- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/services/webhook/telegram.go b/services/webhook/telegram.go index 4f1d98407da..d661786d7ee 100644 --- a/services/webhook/telegram.go +++ b/services/webhook/telegram.go @@ -201,9 +201,12 @@ func (telegramConvertor) WorkflowJob(p *api.WorkflowJobPayload) (TelegramPayload func createTelegramPayloadHTML(msgHTML string) TelegramPayload { // https://core.telegram.org/bots/api#formatting-options + sanitized := strings.TrimSpace(string(markup.Sanitize(msgHTML))) + // Rich messages collapse bare newlines like HTML, so keep line breaks as
. + sanitized = strings.NewReplacer("\r\n", "
", "\r", "
", "\n", "
").Replace(sanitized) return TelegramPayload{ RichMessage: InputRichMessage{ - HTML: strings.TrimSpace(string(markup.Sanitize(msgHTML))), + HTML: sanitized, }, } } diff --git a/services/webhook/telegram_test.go b/services/webhook/telegram_test.go index 3e615af8140..64c3f70e2f4 100644 --- a/services/webhook/telegram_test.go +++ b/services/webhook/telegram_test.go @@ -27,6 +27,12 @@ func TestTelegramPayload(t *testing.T) { }, p) }) + t.Run("Line breaks are kept in rich messages", func(t *testing.T) { + // Rich messages collapse bare newlines, so they must become
. + p := createTelegramPayloadHTML("first line\r\nsecond line\nthird line") + assert.Equal(t, "first line
second line
third line", p.RichMessage.HTML) + }) + t.Run("Create", func(t *testing.T) { p := createTestPayload() @@ -60,9 +66,7 @@ func TestTelegramPayload(t *testing.T) { pl, err := tc.Push(p) require.NoError(t, err) - assert.Equal(t, `[test/repo:test] 2 new commits -[2020558] commit message - user1 -[2020558] commit message - user1`, pl.RichMessage.HTML) + assert.Equal(t, `[test/repo:test] 2 new commits
[2020558] commit message - user1
[2020558] commit message - user1`, pl.RichMessage.HTML) }) t.Run("Issue", func(t *testing.T) { @@ -72,9 +76,7 @@ func TestTelegramPayload(t *testing.T) { pl, err := tc.Issue(p) require.NoError(t, err) - assert.Equal(t, `[test/repo] Issue opened: #2 crash by user1 - -issue body`, pl.RichMessage.HTML) + assert.Equal(t, `[test/repo] Issue opened: #2 crash by user1

issue body`, pl.RichMessage.HTML) p.Action = api.HookIssueClosed pl, err = tc.Issue(p) @@ -89,8 +91,7 @@ issue body`, pl.RichMessage.HTML) pl, err := tc.IssueComment(p) require.NoError(t, err) - assert.Equal(t, `[test/repo] New comment on issue #2 crash by user1 -more info needed`, pl.RichMessage.HTML) + assert.Equal(t, `[test/repo] New comment on issue #2 crash by user1
more info needed`, pl.RichMessage.HTML) }) t.Run("PullRequest", func(t *testing.T) { @@ -99,8 +100,7 @@ more info needed`, pl.RichMessage.HTML) pl, err := tc.PullRequest(p) require.NoError(t, err) - assert.Equal(t, `[test/repo] Pull request opened: #12 Fix bug by user1 -fixes bug #2`, pl.RichMessage.HTML) + assert.Equal(t, `[test/repo] Pull request opened: #12 Fix bug by user1
fixes bug #2`, pl.RichMessage.HTML) }) t.Run("PullRequestComment", func(t *testing.T) { @@ -109,8 +109,7 @@ fixes bug #2`, pl.RichMessage.HTML) pl, err := tc.IssueComment(p) require.NoError(t, err) - assert.Equal(t, `[test/repo] New comment on pull request #12 Fix bug by user1 -changes requested`, pl.RichMessage.HTML) + assert.Equal(t, `[test/repo] New comment on pull request #12 Fix bug by user1
changes requested`, pl.RichMessage.HTML) }) t.Run("Review", func(t *testing.T) { @@ -120,8 +119,7 @@ changes requested`, pl.RichMessage.HTML) pl, err := tc.Review(p, webhook_module.HookEventPullRequestReviewApproved) require.NoError(t, err) - assert.Equal(t, `[test/repo] Pull request review approved: #12 Fix bug -good job`, pl.RichMessage.HTML) + assert.Equal(t, `[test/repo] Pull request review approved: #12 Fix bug
good job`, pl.RichMessage.HTML) }) t.Run("Repository", func(t *testing.T) { @@ -206,7 +204,5 @@ func TestTelegramJSONPayload(t *testing.T) { var body TelegramPayload err = json.NewDecoder(req.Body).Decode(&body) assert.NoError(t, err) - assert.Equal(t, `[test/repo:test] 2 new commits -[2020558] commit message - user1 -[2020558] commit message - user1`, body.RichMessage.HTML) + assert.Equal(t, `[test/repo:test] 2 new commits
[2020558] commit message - user1
[2020558] commit message - user1`, body.RichMessage.HTML) }