From 64cc883ac1fdd22b8c70aac963b2a381486385f2 Mon Sep 17 00:00:00 2001 From: Mopcho <47301161+Mopcho@users.noreply.github.com> Date: Fri, 4 Apr 2025 21:09:40 +0300 Subject: [PATCH] Fix discord webhook 400 status code when description limit is exceeded (#34084) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes [#34027](https://github.com/go-gitea/gitea/issues/34027) Discord does not allow for description bigger than 2048 bytes. If the description is bigger than that it will throw 400 and the event won't appear in discord. To fix that, in the createPayload method we now slice the description to ensure it doesn’t exceed the limit. --------- Co-authored-by: wxiaoguang (cherry picked from commit 013b2686fe6d306c4fb800147207b099866683b9) --- modules/util/truncate.go | 9 +++++++++ modules/util/truncate_test.go | 15 +++++++++++++++ services/webhook/discord.go | 19 +++++++++++++------ services/webhook/discord_test.go | 2 +- 4 files changed, 38 insertions(+), 7 deletions(-) diff --git a/modules/util/truncate.go b/modules/util/truncate.go index f2edbdc673..7207a89177 100644 --- a/modules/util/truncate.go +++ b/modules/util/truncate.go @@ -54,3 +54,12 @@ func SplitTrimSpace(input, sep string) []string { return stringList } + +// TruncateRunes returns a truncated string with given rune limit, +// it returns input string if its rune length doesn't exceed the limit. +func TruncateRunes(str string, limit int) string { + if utf8.RuneCountInString(str) < limit { + return str + } + return string([]rune(str)[:limit]) +} diff --git a/modules/util/truncate_test.go b/modules/util/truncate_test.go index dfe1230fd4..8187b13eb2 100644 --- a/modules/util/truncate_test.go +++ b/modules/util/truncate_test.go @@ -44,3 +44,18 @@ func TestSplitString(t *testing.T) { } test(tc, SplitStringAtByteN) } + +func TestTruncateRunes(t *testing.T) { + assert.Empty(t, TruncateRunes("", 0)) + assert.Empty(t, TruncateRunes("", 1)) + + assert.Empty(t, TruncateRunes("ab", 0)) + assert.Equal(t, "a", TruncateRunes("ab", 1)) + assert.Equal(t, "ab", TruncateRunes("ab", 2)) + assert.Equal(t, "ab", TruncateRunes("ab", 3)) + + assert.Empty(t, TruncateRunes("测试", 0)) + assert.Equal(t, "测", TruncateRunes("测试", 1)) + assert.Equal(t, "测试", TruncateRunes("测试", 2)) + assert.Equal(t, "测试", TruncateRunes("测试", 3)) +} diff --git a/services/webhook/discord.go b/services/webhook/discord.go index 3970a2552d..f60903c276 100644 --- a/services/webhook/discord.go +++ b/services/webhook/discord.go @@ -151,6 +151,18 @@ var ( redColor = color("ff3232") ) +// https://discord.com/developers/docs/resources/message#embed-object-embed-limits +// Discord has some limits in place for the embeds. +// According to some tests, there is no consistent limit for different character sets. +// For example: 4096 ASCII letters are allowed, but only 2490 emoji characters are allowed. +// To keep it simple, we currently truncate at 2000. +const discordDescriptionCharactersLimit = 2000 + +type discordConvertor struct { + Username string + AvatarURL string +} + // Create implements PayloadConvertor Create method func (d discordConvertor) Create(p *api.CreatePayload) (DiscordPayload, error) { // created tag/branch @@ -312,11 +324,6 @@ func (d discordConvertor) Package(p *api.PackagePayload) (DiscordPayload, error) return d.createPayload(p.Sender, text, "", p.Package.HTMLURL, color), nil } -type discordConvertor struct { - Username string - AvatarURL string -} - var _ shared.PayloadConvertor[DiscordPayload] = discordConvertor{} func (discordHandler) NewRequest(ctx context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) { @@ -357,7 +364,7 @@ func (d discordConvertor) createPayload(s *api.User, title, text, url string, co Embeds: []DiscordEmbed{ { Title: title, - Description: text, + Description: util.TruncateRunes(text, discordDescriptionCharactersLimit), URL: url, Color: color, Author: DiscordEmbedAuthor{ diff --git a/services/webhook/discord_test.go b/services/webhook/discord_test.go index ce3aaa10cf..b04be30bc6 100644 --- a/services/webhook/discord_test.go +++ b/services/webhook/discord_test.go @@ -175,7 +175,7 @@ func TestDiscordPayload(t *testing.T) { require.NoError(t, err) assert.Len(t, pl.Embeds, 1) - assert.Len(t, pl.Embeds[0].Description, 4096) + assert.Len(t, pl.Embeds[0].Description, 2000) }) t.Run("IssueComment", func(t *testing.T) {