mirror of
https://github.com/go-gitea/gitea.git
synced 2026-07-03 23:42:15 +02:00
feat(webhook): support Telegram Bot API 10.1 Rich Messages (#38298)
Upgrades Gitea's Telegram webhook integration to support Telegram Bot API 10.1 (June 2026 release). This enables Gitea webhooks to take advantage of rich formatted messages (tables, nested blocks, collapsible details, etc.) by routing them through the /sendRichMessage endpoint with the new rich_message payload structure. Old `/sendMessage` webhook URLs are written to `/sendRichMessage` at runtime to prevent the need for database migrations Fixes https://github.com/go-gitea/gitea/issues/38118 --------- Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
parent
c6184ed184
commit
b09920a537
@ -429,7 +429,7 @@ func telegramHookParams(ctx *context.Context) webhookParams {
|
||||
|
||||
return webhookParams{
|
||||
Type: webhook_module.TELEGRAM,
|
||||
URL: fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage?chat_id=%s&message_thread_id=%s", url.PathEscape(form.BotToken), url.QueryEscape(form.ChatID), url.QueryEscape(form.ThreadID)),
|
||||
URL: fmt.Sprintf("https://api.telegram.org/bot%s/sendRichMessage?chat_id=%s&message_thread_id=%s", url.PathEscape(form.BotToken), url.QueryEscape(form.ChatID), url.QueryEscape(form.ThreadID)),
|
||||
ContentType: webhook.ContentTypeJSON,
|
||||
WebhookForm: form.WebhookForm,
|
||||
Meta: &webhook_service.TelegramMeta{
|
||||
|
||||
@ -8,6 +8,7 @@ import (
|
||||
"fmt"
|
||||
"html"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
webhook_model "gitea.dev/models/webhook"
|
||||
@ -23,9 +24,12 @@ import (
|
||||
type (
|
||||
// TelegramPayload represents
|
||||
TelegramPayload struct {
|
||||
Message string `json:"text"`
|
||||
ParseMode string `json:"parse_mode"`
|
||||
DisableWebPreview bool `json:"disable_web_page_preview"`
|
||||
RichMessage InputRichMessage `json:"rich_message"`
|
||||
}
|
||||
|
||||
// InputRichMessage represents input rich message
|
||||
InputRichMessage struct {
|
||||
HTML string `json:"html"`
|
||||
}
|
||||
|
||||
// TelegramMeta contains the telegram metadata
|
||||
@ -195,13 +199,21 @@ func (telegramConvertor) WorkflowJob(p *api.WorkflowJobPayload) (TelegramPayload
|
||||
func createTelegramPayloadHTML(msgHTML string) TelegramPayload {
|
||||
// https://core.telegram.org/bots/api#formatting-options
|
||||
return TelegramPayload{
|
||||
Message: strings.TrimSpace(string(markup.Sanitize(msgHTML))),
|
||||
ParseMode: "HTML",
|
||||
DisableWebPreview: true,
|
||||
RichMessage: InputRichMessage{
|
||||
HTML: strings.TrimSpace(string(markup.Sanitize(msgHTML))),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func newTelegramRequest(_ context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) {
|
||||
u, err := url.Parse(w.URL)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if urlPrefix, ok := strings.CutSuffix(u.Path, "/sendMessage"); ok {
|
||||
u.Path = urlPrefix + "/sendRichMessage"
|
||||
w.URL = u.String()
|
||||
}
|
||||
var pc payloadConvertor[TelegramPayload] = telegramConvertor{}
|
||||
return newJSONRequest(pc, w, t, true)
|
||||
}
|
||||
|
||||
@ -21,9 +21,9 @@ func TestTelegramPayload(t *testing.T) {
|
||||
t.Run("Correct webhook params", func(t *testing.T) {
|
||||
p := createTelegramPayloadHTML(`<a href=".">testMsg</a> <bad>`)
|
||||
assert.Equal(t, TelegramPayload{
|
||||
Message: `<a href="." rel="nofollow">testMsg</a>`,
|
||||
ParseMode: "HTML",
|
||||
DisableWebPreview: true,
|
||||
RichMessage: InputRichMessage{
|
||||
HTML: `<a href="." rel="nofollow">testMsg</a>`,
|
||||
},
|
||||
}, p)
|
||||
})
|
||||
|
||||
@ -33,7 +33,7 @@ func TestTelegramPayload(t *testing.T) {
|
||||
pl, err := tc.Create(p)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, `[<a href="http://localhost:3000/test/repo" rel="nofollow">test/repo</a>] branch <a href="http://localhost:3000/test/repo/src/test" rel="nofollow">test</a> created`, pl.Message)
|
||||
assert.Equal(t, `[<a href="http://localhost:3000/test/repo" rel="nofollow">test/repo</a>] branch <a href="http://localhost:3000/test/repo/src/test" rel="nofollow">test</a> created`, pl.RichMessage.HTML)
|
||||
})
|
||||
|
||||
t.Run("Delete", func(t *testing.T) {
|
||||
@ -42,7 +42,7 @@ func TestTelegramPayload(t *testing.T) {
|
||||
pl, err := tc.Delete(p)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, `[<a href="http://localhost:3000/test/repo" rel="nofollow">test/repo</a>] branch <a href="http://localhost:3000/test/repo/src/test" rel="nofollow">test</a> deleted`, pl.Message)
|
||||
assert.Equal(t, `[<a href="http://localhost:3000/test/repo" rel="nofollow">test/repo</a>] branch <a href="http://localhost:3000/test/repo/src/test" rel="nofollow">test</a> deleted`, pl.RichMessage.HTML)
|
||||
})
|
||||
|
||||
t.Run("Fork", func(t *testing.T) {
|
||||
@ -51,7 +51,7 @@ func TestTelegramPayload(t *testing.T) {
|
||||
pl, err := tc.Fork(p)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, `test/repo2 is forked to <a href="http://localhost:3000/test/repo" rel="nofollow">test/repo</a>`, pl.Message)
|
||||
assert.Equal(t, `test/repo2 is forked to <a href="http://localhost:3000/test/repo" rel="nofollow">test/repo</a>`, pl.RichMessage.HTML)
|
||||
})
|
||||
|
||||
t.Run("Push", func(t *testing.T) {
|
||||
@ -62,7 +62,7 @@ func TestTelegramPayload(t *testing.T) {
|
||||
|
||||
assert.Equal(t, `[<a href="http://localhost:3000/test/repo" rel="nofollow">test/repo</a>:<a href="http://localhost:3000/test/repo/src/test" rel="nofollow">test</a>] 2 new commits
|
||||
[<a href="http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778" rel="nofollow">2020558</a>] commit message - user1
|
||||
[<a href="http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778" rel="nofollow">2020558</a>] commit message - user1`, pl.Message)
|
||||
[<a href="http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778" rel="nofollow">2020558</a>] commit message - user1`, pl.RichMessage.HTML)
|
||||
})
|
||||
|
||||
t.Run("Issue", func(t *testing.T) {
|
||||
@ -74,13 +74,13 @@ func TestTelegramPayload(t *testing.T) {
|
||||
|
||||
assert.Equal(t, `[<a href="http://localhost:3000/test/repo" rel="nofollow">test/repo</a>] Issue opened: <a href="http://localhost:3000/test/repo/issues/2" rel="nofollow">#2 crash</a> by <a href="https://try.gitea.io/user1" rel="nofollow">user1</a>
|
||||
|
||||
issue body`, pl.Message)
|
||||
issue body`, pl.RichMessage.HTML)
|
||||
|
||||
p.Action = api.HookIssueClosed
|
||||
pl, err = tc.Issue(p)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, `[<a href="http://localhost:3000/test/repo" rel="nofollow">test/repo</a>] Issue closed: <a href="http://localhost:3000/test/repo/issues/2" rel="nofollow">#2 crash</a> by <a href="https://try.gitea.io/user1" rel="nofollow">user1</a>`, pl.Message)
|
||||
assert.Equal(t, `[<a href="http://localhost:3000/test/repo" rel="nofollow">test/repo</a>] Issue closed: <a href="http://localhost:3000/test/repo/issues/2" rel="nofollow">#2 crash</a> by <a href="https://try.gitea.io/user1" rel="nofollow">user1</a>`, pl.RichMessage.HTML)
|
||||
})
|
||||
|
||||
t.Run("IssueComment", func(t *testing.T) {
|
||||
@ -90,7 +90,7 @@ issue body`, pl.Message)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, `[<a href="http://localhost:3000/test/repo" rel="nofollow">test/repo</a>] New comment on issue <a href="http://localhost:3000/test/repo/issues/2" rel="nofollow">#2 crash</a> by <a href="https://try.gitea.io/user1" rel="nofollow">user1</a>
|
||||
more info needed`, pl.Message)
|
||||
more info needed`, pl.RichMessage.HTML)
|
||||
})
|
||||
|
||||
t.Run("PullRequest", func(t *testing.T) {
|
||||
@ -100,7 +100,7 @@ more info needed`, pl.Message)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, `[<a href="http://localhost:3000/test/repo" rel="nofollow">test/repo</a>] Pull request opened: <a href="http://localhost:3000/test/repo/pulls/12" rel="nofollow">#12 Fix bug</a> by <a href="https://try.gitea.io/user1" rel="nofollow">user1</a>
|
||||
fixes bug #2`, pl.Message)
|
||||
fixes bug #2`, pl.RichMessage.HTML)
|
||||
})
|
||||
|
||||
t.Run("PullRequestComment", func(t *testing.T) {
|
||||
@ -110,7 +110,7 @@ fixes bug #2`, pl.Message)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, `[<a href="http://localhost:3000/test/repo" rel="nofollow">test/repo</a>] New comment on pull request <a href="http://localhost:3000/test/repo/pulls/12" rel="nofollow">#12 Fix bug</a> by <a href="https://try.gitea.io/user1" rel="nofollow">user1</a>
|
||||
changes requested`, pl.Message)
|
||||
changes requested`, pl.RichMessage.HTML)
|
||||
})
|
||||
|
||||
t.Run("Review", func(t *testing.T) {
|
||||
@ -121,7 +121,7 @@ changes requested`, pl.Message)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, `[test/repo] Pull request review approved: #12 Fix bug
|
||||
good job`, pl.Message)
|
||||
good job`, pl.RichMessage.HTML)
|
||||
})
|
||||
|
||||
t.Run("Repository", func(t *testing.T) {
|
||||
@ -130,7 +130,7 @@ good job`, pl.Message)
|
||||
pl, err := tc.Repository(p)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, `[<a href="http://localhost:3000/test/repo" rel="nofollow">test/repo</a>] Repository created`, pl.Message)
|
||||
assert.Equal(t, `[<a href="http://localhost:3000/test/repo" rel="nofollow">test/repo</a>] Repository created`, pl.RichMessage.HTML)
|
||||
})
|
||||
|
||||
t.Run("Package", func(t *testing.T) {
|
||||
@ -139,7 +139,7 @@ good job`, pl.Message)
|
||||
pl, err := tc.Package(p)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, `Package created: <a href="http://localhost:3000/user1/-/packages/container/GiteaContainer/latest" rel="nofollow">GiteaContainer:latest</a> by <a href="https://try.gitea.io/user1" rel="nofollow">user1</a>`, pl.Message)
|
||||
assert.Equal(t, `Package created: <a href="http://localhost:3000/user1/-/packages/container/GiteaContainer/latest" rel="nofollow">GiteaContainer:latest</a> by <a href="https://try.gitea.io/user1" rel="nofollow">user1</a>`, pl.RichMessage.HTML)
|
||||
})
|
||||
|
||||
t.Run("Wiki", func(t *testing.T) {
|
||||
@ -149,19 +149,19 @@ good job`, pl.Message)
|
||||
pl, err := tc.Wiki(p)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, `[<a href="http://localhost:3000/test/repo" rel="nofollow">test/repo</a>] New wiki page '<a href="http://localhost:3000/test/repo/wiki/index" rel="nofollow">index</a>' (Wiki change comment) by <a href="https://try.gitea.io/user1" rel="nofollow">user1</a>`, pl.Message)
|
||||
assert.Equal(t, `[<a href="http://localhost:3000/test/repo" rel="nofollow">test/repo</a>] New wiki page '<a href="http://localhost:3000/test/repo/wiki/index" rel="nofollow">index</a>' (Wiki change comment) by <a href="https://try.gitea.io/user1" rel="nofollow">user1</a>`, pl.RichMessage.HTML)
|
||||
|
||||
p.Action = api.HookWikiEdited
|
||||
pl, err = tc.Wiki(p)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, `[<a href="http://localhost:3000/test/repo" rel="nofollow">test/repo</a>] Wiki page '<a href="http://localhost:3000/test/repo/wiki/index" rel="nofollow">index</a>' edited (Wiki change comment) by <a href="https://try.gitea.io/user1" rel="nofollow">user1</a>`, pl.Message)
|
||||
assert.Equal(t, `[<a href="http://localhost:3000/test/repo" rel="nofollow">test/repo</a>] Wiki page '<a href="http://localhost:3000/test/repo/wiki/index" rel="nofollow">index</a>' edited (Wiki change comment) by <a href="https://try.gitea.io/user1" rel="nofollow">user1</a>`, pl.RichMessage.HTML)
|
||||
|
||||
p.Action = api.HookWikiDeleted
|
||||
pl, err = tc.Wiki(p)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, `[<a href="http://localhost:3000/test/repo" rel="nofollow">test/repo</a>] Wiki page '<a href="http://localhost:3000/test/repo/wiki/index" rel="nofollow">index</a>' deleted by <a href="https://try.gitea.io/user1" rel="nofollow">user1</a>`, pl.Message)
|
||||
assert.Equal(t, `[<a href="http://localhost:3000/test/repo" rel="nofollow">test/repo</a>] Wiki page '<a href="http://localhost:3000/test/repo/wiki/index" rel="nofollow">index</a>' deleted by <a href="https://try.gitea.io/user1" rel="nofollow">user1</a>`, pl.RichMessage.HTML)
|
||||
})
|
||||
|
||||
t.Run("Release", func(t *testing.T) {
|
||||
@ -170,7 +170,7 @@ good job`, pl.Message)
|
||||
pl, err := tc.Release(p)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, `[<a href="http://localhost:3000/test/repo" rel="nofollow">test/repo</a>] Release created: <a href="http://localhost:3000/test/repo/releases/tag/v1.0" rel="nofollow">v1.0</a> by <a href="https://try.gitea.io/user1" rel="nofollow">user1</a>`, pl.Message)
|
||||
assert.Equal(t, `[<a href="http://localhost:3000/test/repo" rel="nofollow">test/repo</a>] Release created: <a href="http://localhost:3000/test/repo/releases/tag/v1.0" rel="nofollow">v1.0</a> by <a href="https://try.gitea.io/user1" rel="nofollow">user1</a>`, pl.RichMessage.HTML)
|
||||
})
|
||||
}
|
||||
|
||||
@ -183,7 +183,7 @@ func TestTelegramJSONPayload(t *testing.T) {
|
||||
RepoID: 3,
|
||||
IsActive: true,
|
||||
Type: webhook_module.TELEGRAM,
|
||||
URL: "https://telegram.example.com/",
|
||||
URL: "https://telegram.example.com/sendMessage",
|
||||
Meta: ``,
|
||||
HTTPMethod: "POST",
|
||||
}
|
||||
@ -200,7 +200,7 @@ func TestTelegramJSONPayload(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "POST", req.Method)
|
||||
assert.Equal(t, "https://telegram.example.com/", req.URL.String())
|
||||
assert.Equal(t, "https://telegram.example.com/sendRichMessage", req.URL.String())
|
||||
assert.Equal(t, "sha256=", req.Header.Get("X-Hub-Signature-256"))
|
||||
assert.Equal(t, "application/json", req.Header.Get("Content-Type"))
|
||||
var body TelegramPayload
|
||||
@ -208,5 +208,5 @@ func TestTelegramJSONPayload(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, `[<a href="http://localhost:3000/test/repo" rel="nofollow">test/repo</a>:<a href="http://localhost:3000/test/repo/src/test" rel="nofollow">test</a>] 2 new commits
|
||||
[<a href="http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778" rel="nofollow">2020558</a>] commit message - user1
|
||||
[<a href="http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778" rel="nofollow">2020558</a>] commit message - user1`, body.Message)
|
||||
[<a href="http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778" rel="nofollow">2020558</a>] commit message - user1`, body.RichMessage.HTML)
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user