1
0
mirror of https://github.com/go-gitea/gitea synced 2024-11-14 01:35:54 +01:00

Refactor webhook (#31587)

A more complete fix for #31588

1. Make "generic" code more readable
2. Clarify HTML or Markdown for the payload content
This commit is contained in:
wxiaoguang 2024-07-10 19:37:16 +08:00 committed by GitHub
parent 4ea2a6de81
commit 72b6bc8caf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 173 additions and 182 deletions

@ -15,6 +15,7 @@ import (
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/test"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -33,7 +34,8 @@ func TestAction_GetRepoLink(t *testing.T) {
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
comment := unittest.AssertExistsAndLoadBean(t, &issue_model.Comment{ID: 2}) comment := unittest.AssertExistsAndLoadBean(t, &issue_model.Comment{ID: 2})
action := &activities_model.Action{RepoID: repo.ID, CommentID: comment.ID} action := &activities_model.Action{RepoID: repo.ID, CommentID: comment.ID}
setting.AppSubURL = "/suburl" defer test.MockVariableValue(&setting.AppURL, "https://try.gitea.io/suburl/")()
defer test.MockVariableValue(&setting.AppSubURL, "/suburl")()
expected := path.Join(setting.AppSubURL, owner.Name, repo.Name) expected := path.Join(setting.AppSubURL, owner.Name, repo.Name)
assert.Equal(t, expected, action.GetRepoLink(db.DefaultContext)) assert.Equal(t, expected, action.GetRepoLink(db.DefaultContext))
assert.Equal(t, repo.HTMLURL(), action.GetRepoAbsoluteLink(db.DefaultContext)) assert.Equal(t, repo.HTMLURL(), action.GetRepoAbsoluteLink(db.DefaultContext))

@ -18,6 +18,7 @@ import (
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/httplib"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/optional" "code.gitea.io/gitea/modules/optional"
@ -321,8 +322,12 @@ func (repo *Repository) FullName() string {
} }
// HTMLURL returns the repository HTML URL // HTMLURL returns the repository HTML URL
func (repo *Repository) HTMLURL() string { func (repo *Repository) HTMLURL(ctxs ...context.Context) string {
return setting.AppURL + url.PathEscape(repo.OwnerName) + "/" + url.PathEscape(repo.Name) ctx := context.TODO()
if len(ctxs) > 0 {
ctx = ctxs[0]
}
return httplib.MakeAbsoluteURL(ctx, repo.Link())
} }
// CommitLink make link to by commit full ID // CommitLink make link to by commit full ID

@ -668,7 +668,7 @@ func SearchRepo(ctx *context.Context) {
Template: repo.IsTemplate, Template: repo.IsTemplate,
Mirror: repo.IsMirror, Mirror: repo.IsMirror,
Stars: repo.NumStars, Stars: repo.NumStars,
HTMLURL: repo.HTMLURL(), HTMLURL: repo.HTMLURL(ctx),
Link: repo.Link(), Link: repo.Link(),
Internal: !repo.IsPrivate && repo.Owner.Visibility == api.VisibleTypePrivate, Internal: !repo.IsPrivate && repo.Owner.Visibility == api.VisibleTypePrivate,
}, },

@ -191,7 +191,7 @@ func innerToRepo(ctx context.Context, repo *repo_model.Repository, permissionInR
Fork: repo.IsFork, Fork: repo.IsFork,
Parent: parent, Parent: parent,
Mirror: repo.IsMirror, Mirror: repo.IsMirror,
HTMLURL: repo.HTMLURL(), HTMLURL: repo.HTMLURL(ctx),
URL: repoAPIURL, URL: repoAPIURL,
SSHURL: cloneLink.SSH, SSHURL: cloneLink.SSH,
CloneURL: cloneLink.HTTPS, CloneURL: cloneLink.HTTPS,

@ -20,8 +20,8 @@ import (
) )
type ( type (
// DingtalkPayload represents
DingtalkPayload dingtalk.Payload DingtalkPayload dingtalk.Payload
dingtalkConvertor struct{}
) )
// Create implements PayloadConvertor Create method // Create implements PayloadConvertor Create method
@ -92,9 +92,9 @@ func (dc dingtalkConvertor) Push(p *api.PushPayload) (DingtalkPayload, error) {
// Issue implements PayloadConvertor Issue method // Issue implements PayloadConvertor Issue method
func (dc dingtalkConvertor) Issue(p *api.IssuePayload) (DingtalkPayload, error) { func (dc dingtalkConvertor) Issue(p *api.IssuePayload) (DingtalkPayload, error) {
text, issueTitle, attachmentText, _ := getIssuesPayloadInfo(p, noneLinkFormatter, true) text, issueTitle, extraMarkdown, _ := getIssuesPayloadInfo(p, noneLinkFormatter, true)
return createDingtalkPayload(issueTitle, text+"\r\n\r\n"+attachmentText, "view issue", p.Issue.HTMLURL), nil return createDingtalkPayload(issueTitle, text+"\r\n\r\n"+extraMarkdown, "view issue", p.Issue.HTMLURL), nil
} }
// Wiki implements PayloadConvertor Wiki method // Wiki implements PayloadConvertor Wiki method
@ -114,9 +114,9 @@ func (dc dingtalkConvertor) IssueComment(p *api.IssueCommentPayload) (DingtalkPa
// PullRequest implements PayloadConvertor PullRequest method // PullRequest implements PayloadConvertor PullRequest method
func (dc dingtalkConvertor) PullRequest(p *api.PullRequestPayload) (DingtalkPayload, error) { func (dc dingtalkConvertor) PullRequest(p *api.PullRequestPayload) (DingtalkPayload, error) {
text, issueTitle, attachmentText, _ := getPullRequestPayloadInfo(p, noneLinkFormatter, true) text, issueTitle, extraMarkdown, _ := getPullRequestPayloadInfo(p, noneLinkFormatter, true)
return createDingtalkPayload(issueTitle, text+"\r\n\r\n"+attachmentText, "view pull request", p.PullRequest.HTMLURL), nil return createDingtalkPayload(issueTitle, text+"\r\n\r\n"+extraMarkdown, "view pull request", p.PullRequest.HTMLURL), nil
} }
// Review implements PayloadConvertor Review method // Review implements PayloadConvertor Review method
@ -186,10 +186,7 @@ func createDingtalkPayload(title, text, singleTitle, singleURL string) DingtalkP
} }
} }
type dingtalkConvertor struct{}
var _ payloadConvertor[DingtalkPayload] = dingtalkConvertor{}
func newDingtalkRequest(_ context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) { func newDingtalkRequest(_ context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) {
return newJSONRequest(dingtalkConvertor{}, w, t, true) var pc payloadConvertor[DingtalkPayload] = dingtalkConvertor{}
return newJSONRequest(pc, w, t, true)
} }

@ -100,6 +100,11 @@ var (
redColor = color("ff3232") redColor = color("ff3232")
) )
type discordConvertor struct {
Username string
AvatarURL string
}
// Create implements PayloadConvertor Create method // Create implements PayloadConvertor Create method
func (d discordConvertor) Create(p *api.CreatePayload) (DiscordPayload, error) { func (d discordConvertor) Create(p *api.CreatePayload) (DiscordPayload, error) {
// created tag/branch // created tag/branch
@ -162,9 +167,9 @@ func (d discordConvertor) Push(p *api.PushPayload) (DiscordPayload, error) {
// Issue implements PayloadConvertor Issue method // Issue implements PayloadConvertor Issue method
func (d discordConvertor) Issue(p *api.IssuePayload) (DiscordPayload, error) { func (d discordConvertor) Issue(p *api.IssuePayload) (DiscordPayload, error) {
title, _, text, color := getIssuesPayloadInfo(p, noneLinkFormatter, false) title, _, extraMarkdown, color := getIssuesPayloadInfo(p, noneLinkFormatter, false)
return d.createPayload(p.Sender, title, text, p.Issue.HTMLURL, color), nil return d.createPayload(p.Sender, title, extraMarkdown, p.Issue.HTMLURL, color), nil
} }
// IssueComment implements PayloadConvertor IssueComment method // IssueComment implements PayloadConvertor IssueComment method
@ -176,9 +181,9 @@ func (d discordConvertor) IssueComment(p *api.IssueCommentPayload) (DiscordPaylo
// PullRequest implements PayloadConvertor PullRequest method // PullRequest implements PayloadConvertor PullRequest method
func (d discordConvertor) PullRequest(p *api.PullRequestPayload) (DiscordPayload, error) { func (d discordConvertor) PullRequest(p *api.PullRequestPayload) (DiscordPayload, error) {
title, _, text, color := getPullRequestPayloadInfo(p, noneLinkFormatter, false) title, _, extraMarkdown, color := getPullRequestPayloadInfo(p, noneLinkFormatter, false)
return d.createPayload(p.Sender, title, text, p.PullRequest.HTMLURL, color), nil return d.createPayload(p.Sender, title, extraMarkdown, p.PullRequest.HTMLURL, color), nil
} }
// Review implements PayloadConvertor Review method // Review implements PayloadConvertor Review method
@ -253,23 +258,16 @@ func (d discordConvertor) Package(p *api.PackagePayload) (DiscordPayload, error)
return d.createPayload(p.Sender, text, "", p.Package.HTMLURL, color), nil return d.createPayload(p.Sender, text, "", p.Package.HTMLURL, color), nil
} }
type discordConvertor struct {
Username string
AvatarURL string
}
var _ payloadConvertor[DiscordPayload] = discordConvertor{}
func newDiscordRequest(_ context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) { func newDiscordRequest(_ context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) {
meta := &DiscordMeta{} meta := &DiscordMeta{}
if err := json.Unmarshal([]byte(w.Meta), meta); err != nil { if err := json.Unmarshal([]byte(w.Meta), meta); err != nil {
return nil, nil, fmt.Errorf("newDiscordRequest meta json: %w", err) return nil, nil, fmt.Errorf("newDiscordRequest meta json: %w", err)
} }
sc := discordConvertor{ var pc payloadConvertor[DiscordPayload] = discordConvertor{
Username: meta.Username, Username: meta.Username,
AvatarURL: meta.IconURL, AvatarURL: meta.IconURL,
} }
return newJSONRequest(sc, w, t, true) return newJSONRequest(pc, w, t, true)
} }
func parseHookPullRequestEventType(event webhook_module.HookEventType) (string, error) { func parseHookPullRequestEventType(event webhook_module.HookEventType) (string, error) {

@ -36,6 +36,8 @@ func newFeishuTextPayload(text string) FeishuPayload {
} }
} }
type feishuConvertor struct{}
// Create implements PayloadConvertor Create method // Create implements PayloadConvertor Create method
func (fc feishuConvertor) Create(p *api.CreatePayload) (FeishuPayload, error) { func (fc feishuConvertor) Create(p *api.CreatePayload) (FeishuPayload, error) {
// created tag/branch // created tag/branch
@ -164,10 +166,7 @@ func (fc feishuConvertor) Package(p *api.PackagePayload) (FeishuPayload, error)
return newFeishuTextPayload(text), nil return newFeishuTextPayload(text), nil
} }
type feishuConvertor struct{}
var _ payloadConvertor[FeishuPayload] = feishuConvertor{}
func newFeishuRequest(_ context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) { func newFeishuRequest(_ context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) {
return newJSONRequest(feishuConvertor{}, w, t, true) var pc payloadConvertor[FeishuPayload] = feishuConvertor{}
return newJSONRequest(pc, w, t, true)
} }

@ -91,12 +91,11 @@ func getIssuesCommentInfo(p *api.IssueCommentPayload) (title, link, by, operator
return title, link, by, operator return title, link, by, operator
} }
func getIssuesPayloadInfo(p *api.IssuePayload, linkFormatter linkFormatter, withSender bool) (string, string, string, int) { func getIssuesPayloadInfo(p *api.IssuePayload, linkFormatter linkFormatter, withSender bool) (text, issueTitle, extraMarkdown string, color int) {
repoLink := linkFormatter(p.Repository.HTMLURL, p.Repository.FullName) color = yellowColor
issueTitle := fmt.Sprintf("#%d %s", p.Index, p.Issue.Title) issueTitle = fmt.Sprintf("#%d %s", p.Index, p.Issue.Title)
titleLink := linkFormatter(fmt.Sprintf("%s/issues/%d", p.Repository.HTMLURL, p.Index), issueTitle) titleLink := linkFormatter(fmt.Sprintf("%s/issues/%d", p.Repository.HTMLURL, p.Index), issueTitle)
var text string repoLink := linkFormatter(p.Repository.HTMLURL, p.Repository.FullName)
color := yellowColor
switch p.Action { switch p.Action {
case api.HookIssueOpened: case api.HookIssueOpened:
@ -135,26 +134,23 @@ func getIssuesPayloadInfo(p *api.IssuePayload, linkFormatter linkFormatter, with
text += fmt.Sprintf(" by %s", linkFormatter(setting.AppURL+url.PathEscape(p.Sender.UserName), p.Sender.UserName)) text += fmt.Sprintf(" by %s", linkFormatter(setting.AppURL+url.PathEscape(p.Sender.UserName), p.Sender.UserName))
} }
var attachmentText string
if p.Action == api.HookIssueOpened || p.Action == api.HookIssueEdited { if p.Action == api.HookIssueOpened || p.Action == api.HookIssueEdited {
attachmentText = p.Issue.Body extraMarkdown = p.Issue.Body
} }
return text, issueTitle, attachmentText, color return text, issueTitle, extraMarkdown, color
} }
func getPullRequestPayloadInfo(p *api.PullRequestPayload, linkFormatter linkFormatter, withSender bool) (string, string, string, int) { func getPullRequestPayloadInfo(p *api.PullRequestPayload, linkFormatter linkFormatter, withSender bool) (text, issueTitle, extraMarkdown string, color int) {
repoLink := linkFormatter(p.Repository.HTMLURL, p.Repository.FullName) color = yellowColor
issueTitle := fmt.Sprintf("#%d %s", p.Index, p.PullRequest.Title) issueTitle = fmt.Sprintf("#%d %s", p.Index, p.PullRequest.Title)
titleLink := linkFormatter(p.PullRequest.URL, issueTitle) titleLink := linkFormatter(p.PullRequest.URL, issueTitle)
var text string repoLink := linkFormatter(p.Repository.HTMLURL, p.Repository.FullName)
var attachmentText string
color := yellowColor
switch p.Action { switch p.Action {
case api.HookIssueOpened: case api.HookIssueOpened:
text = fmt.Sprintf("[%s] Pull request opened: %s", repoLink, titleLink) text = fmt.Sprintf("[%s] Pull request opened: %s", repoLink, titleLink)
attachmentText = p.PullRequest.Body extraMarkdown = p.PullRequest.Body
color = greenColor color = greenColor
case api.HookIssueClosed: case api.HookIssueClosed:
if p.PullRequest.HasMerged { if p.PullRequest.HasMerged {
@ -168,7 +164,7 @@ func getPullRequestPayloadInfo(p *api.PullRequestPayload, linkFormatter linkForm
text = fmt.Sprintf("[%s] Pull request re-opened: %s", repoLink, titleLink) text = fmt.Sprintf("[%s] Pull request re-opened: %s", repoLink, titleLink)
case api.HookIssueEdited: case api.HookIssueEdited:
text = fmt.Sprintf("[%s] Pull request edited: %s", repoLink, titleLink) text = fmt.Sprintf("[%s] Pull request edited: %s", repoLink, titleLink)
attachmentText = p.PullRequest.Body extraMarkdown = p.PullRequest.Body
case api.HookIssueAssigned: case api.HookIssueAssigned:
list := make([]string, len(p.PullRequest.Assignees)) list := make([]string, len(p.PullRequest.Assignees))
for i, user := range p.PullRequest.Assignees { for i, user := range p.PullRequest.Assignees {
@ -193,7 +189,7 @@ func getPullRequestPayloadInfo(p *api.PullRequestPayload, linkFormatter linkForm
text = fmt.Sprintf("[%s] Pull request milestone cleared: %s", repoLink, titleLink) text = fmt.Sprintf("[%s] Pull request milestone cleared: %s", repoLink, titleLink)
case api.HookIssueReviewed: case api.HookIssueReviewed:
text = fmt.Sprintf("[%s] Pull request reviewed: %s", repoLink, titleLink) text = fmt.Sprintf("[%s] Pull request reviewed: %s", repoLink, titleLink)
attachmentText = p.Review.Content extraMarkdown = p.Review.Content
case api.HookIssueReviewRequested: case api.HookIssueReviewRequested:
text = fmt.Sprintf("[%s] Pull request review requested: %s", repoLink, titleLink) text = fmt.Sprintf("[%s] Pull request review requested: %s", repoLink, titleLink)
case api.HookIssueReviewRequestRemoved: case api.HookIssueReviewRequestRemoved:
@ -203,7 +199,7 @@ func getPullRequestPayloadInfo(p *api.PullRequestPayload, linkFormatter linkForm
text += fmt.Sprintf(" by %s", linkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName)) text += fmt.Sprintf(" by %s", linkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName))
} }
return text, issueTitle, attachmentText, color return text, issueTitle, extraMarkdown, color
} }
func getReleasePayloadInfo(p *api.ReleasePayload, linkFormatter linkFormatter, withSender bool) (text string, color int) { func getReleasePayloadInfo(p *api.ReleasePayload, linkFormatter linkFormatter, withSender bool) (text string, color int) {

@ -424,10 +424,10 @@ func TestGetIssuesPayloadInfo(t *testing.T) {
for i, c := range cases { for i, c := range cases {
p.Action = c.action p.Action = c.action
text, issueTitle, attachmentText, color := getIssuesPayloadInfo(p, noneLinkFormatter, true) text, issueTitle, extraMarkdown, color := getIssuesPayloadInfo(p, noneLinkFormatter, true)
assert.Equal(t, c.text, text, "case %d", i) assert.Equal(t, c.text, text, "case %d", i)
assert.Equal(t, c.issueTitle, issueTitle, "case %d", i) assert.Equal(t, c.issueTitle, issueTitle, "case %d", i)
assert.Equal(t, c.attachmentText, attachmentText, "case %d", i) assert.Equal(t, c.attachmentText, extraMarkdown, "case %d", i)
assert.Equal(t, c.color, color, "case %d", i) assert.Equal(t, c.color, color, "case %d", i)
} }
} }
@ -523,10 +523,10 @@ func TestGetPullRequestPayloadInfo(t *testing.T) {
for i, c := range cases { for i, c := range cases {
p.Action = c.action p.Action = c.action
text, issueTitle, attachmentText, color := getPullRequestPayloadInfo(p, noneLinkFormatter, true) text, issueTitle, extraMarkdown, color := getPullRequestPayloadInfo(p, noneLinkFormatter, true)
assert.Equal(t, c.text, text, "case %d", i) assert.Equal(t, c.text, text, "case %d", i)
assert.Equal(t, c.issueTitle, issueTitle, "case %d", i) assert.Equal(t, c.issueTitle, issueTitle, "case %d", i)
assert.Equal(t, c.attachmentText, attachmentText, "case %d", i) assert.Equal(t, c.attachmentText, extraMarkdown, "case %d", i)
assert.Equal(t, c.color, color, "case %d", i) assert.Equal(t, c.color, color, "case %d", i)
} }
} }

@ -29,10 +29,10 @@ func newMatrixRequest(_ context.Context, w *webhook_model.Webhook, t *webhook_mo
if err := json.Unmarshal([]byte(w.Meta), meta); err != nil { if err := json.Unmarshal([]byte(w.Meta), meta); err != nil {
return nil, nil, fmt.Errorf("GetMatrixPayload meta json: %w", err) return nil, nil, fmt.Errorf("GetMatrixPayload meta json: %w", err)
} }
mc := matrixConvertor{ var pc payloadConvertor[MatrixPayload] = matrixConvertor{
MsgType: messageTypeText[meta.MessageType], MsgType: messageTypeText[meta.MessageType],
} }
payload, err := newPayload(mc, []byte(t.PayloadContent), t.EventType) payload, err := newPayload(pc, []byte(t.PayloadContent), t.EventType)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -87,8 +87,6 @@ type MatrixPayload struct {
Commits []*api.PayloadCommit `json:"io.gitea.commits,omitempty"` Commits []*api.PayloadCommit `json:"io.gitea.commits,omitempty"`
} }
var _ payloadConvertor[MatrixPayload] = matrixConvertor{}
type matrixConvertor struct { type matrixConvertor struct {
MsgType string MsgType string
} }

@ -58,6 +58,8 @@ type (
} }
) )
type msteamsConvertor struct{}
// Create implements PayloadConvertor Create method // Create implements PayloadConvertor Create method
func (m msteamsConvertor) Create(p *api.CreatePayload) (MSTeamsPayload, error) { func (m msteamsConvertor) Create(p *api.CreatePayload) (MSTeamsPayload, error) {
// created tag/branch // created tag/branch
@ -152,13 +154,13 @@ func (m msteamsConvertor) Push(p *api.PushPayload) (MSTeamsPayload, error) {
// Issue implements PayloadConvertor Issue method // Issue implements PayloadConvertor Issue method
func (m msteamsConvertor) Issue(p *api.IssuePayload) (MSTeamsPayload, error) { func (m msteamsConvertor) Issue(p *api.IssuePayload) (MSTeamsPayload, error) {
title, _, attachmentText, color := getIssuesPayloadInfo(p, noneLinkFormatter, false) title, _, extraMarkdown, color := getIssuesPayloadInfo(p, noneLinkFormatter, false)
return createMSTeamsPayload( return createMSTeamsPayload(
p.Repository, p.Repository,
p.Sender, p.Sender,
title, title,
attachmentText, extraMarkdown,
p.Issue.HTMLURL, p.Issue.HTMLURL,
color, color,
&MSTeamsFact{"Issue #:", fmt.Sprintf("%d", p.Issue.ID)}, &MSTeamsFact{"Issue #:", fmt.Sprintf("%d", p.Issue.ID)},
@ -182,13 +184,13 @@ func (m msteamsConvertor) IssueComment(p *api.IssueCommentPayload) (MSTeamsPaylo
// PullRequest implements PayloadConvertor PullRequest method // PullRequest implements PayloadConvertor PullRequest method
func (m msteamsConvertor) PullRequest(p *api.PullRequestPayload) (MSTeamsPayload, error) { func (m msteamsConvertor) PullRequest(p *api.PullRequestPayload) (MSTeamsPayload, error) {
title, _, attachmentText, color := getPullRequestPayloadInfo(p, noneLinkFormatter, false) title, _, extraMarkdown, color := getPullRequestPayloadInfo(p, noneLinkFormatter, false)
return createMSTeamsPayload( return createMSTeamsPayload(
p.Repository, p.Repository,
p.Sender, p.Sender,
title, title,
attachmentText, extraMarkdown,
p.PullRequest.HTMLURL, p.PullRequest.HTMLURL,
color, color,
&MSTeamsFact{"Pull request #:", fmt.Sprintf("%d", p.PullRequest.ID)}, &MSTeamsFact{"Pull request #:", fmt.Sprintf("%d", p.PullRequest.ID)},
@ -343,10 +345,7 @@ func createMSTeamsPayload(r *api.Repository, s *api.User, title, text, actionTar
} }
} }
type msteamsConvertor struct{}
var _ payloadConvertor[MSTeamsPayload] = msteamsConvertor{}
func newMSTeamsRequest(_ context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) { func newMSTeamsRequest(_ context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) {
return newJSONRequest(msteamsConvertor{}, w, t, true) var pc payloadConvertor[MSTeamsPayload] = msteamsConvertor{}
return newJSONRequest(pc, w, t, true)
} }

@ -23,7 +23,7 @@ import (
) )
func init() { func init() {
notify_service.RegisterNotifier(&webhookNotifier{}) notify_service.RegisterNotifier(NewNotifier())
} }
type webhookNotifier struct { type webhookNotifier struct {

@ -40,6 +40,10 @@ func GetPackagistHook(w *webhook_model.Webhook) *PackagistMeta {
return s return s
} }
type packagistConvertor struct {
PackageURL string
}
// Create implements PayloadConvertor Create method // Create implements PayloadConvertor Create method
func (pc packagistConvertor) Create(_ *api.CreatePayload) (PackagistPayload, error) { func (pc packagistConvertor) Create(_ *api.CreatePayload) (PackagistPayload, error) {
return PackagistPayload{}, nil return PackagistPayload{}, nil
@ -106,18 +110,12 @@ func (pc packagistConvertor) Package(_ *api.PackagePayload) (PackagistPayload, e
return PackagistPayload{}, nil return PackagistPayload{}, nil
} }
type packagistConvertor struct {
PackageURL string
}
var _ payloadConvertor[PackagistPayload] = packagistConvertor{}
func newPackagistRequest(_ context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) { func newPackagistRequest(_ context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) {
meta := &PackagistMeta{} meta := &PackagistMeta{}
if err := json.Unmarshal([]byte(w.Meta), meta); err != nil { if err := json.Unmarshal([]byte(w.Meta), meta); err != nil {
return nil, nil, fmt.Errorf("newpackagistRequest meta json: %w", err) return nil, nil, fmt.Errorf("newpackagistRequest meta json: %w", err)
} }
pc := packagistConvertor{ var pc payloadConvertor[PackagistPayload] = packagistConvertor{
PackageURL: meta.PackageURL, PackageURL: meta.PackageURL,
} }
return newJSONRequest(pc, w, t, true) return newJSONRequest(pc, w, t, true)

@ -30,16 +30,15 @@ type payloadConvertor[T any] interface {
Package(*api.PackagePayload) (T, error) Package(*api.PackagePayload) (T, error)
} }
func convertUnmarshalledJSON[T, P any](convert func(P) (T, error), data []byte) (T, error) { func convertUnmarshalledJSON[T, P any](convert func(P) (T, error), data []byte) (t T, err error) {
var p P var p P
if err := json.Unmarshal(data, &p); err != nil { if err = json.Unmarshal(data, &p); err != nil {
var t T
return t, fmt.Errorf("could not unmarshal payload: %w", err) return t, fmt.Errorf("could not unmarshal payload: %w", err)
} }
return convert(p) return convert(p)
} }
func newPayload[T any](rc payloadConvertor[T], data []byte, event webhook_module.HookEventType) (T, error) { func newPayload[T any](rc payloadConvertor[T], data []byte, event webhook_module.HookEventType) (t T, err error) {
switch event { switch event {
case webhook_module.HookEventCreate: case webhook_module.HookEventCreate:
return convertUnmarshalledJSON(rc.Create, data) return convertUnmarshalledJSON(rc.Create, data)
@ -79,7 +78,6 @@ func newPayload[T any](rc payloadConvertor[T], data []byte, event webhook_module
case webhook_module.HookEventPackage: case webhook_module.HookEventPackage:
return convertUnmarshalledJSON(rc.Package, data) return convertUnmarshalledJSON(rc.Package, data)
} }
var t T
return t, fmt.Errorf("newPayload unsupported event: %s", event) return t, fmt.Errorf("newPayload unsupported event: %s", event)
} }

@ -118,17 +118,17 @@ func (s slackConvertor) Fork(p *api.ForkPayload) (SlackPayload, error) {
// Issue implements payloadConvertor Issue method // Issue implements payloadConvertor Issue method
func (s slackConvertor) Issue(p *api.IssuePayload) (SlackPayload, error) { func (s slackConvertor) Issue(p *api.IssuePayload) (SlackPayload, error) {
text, issueTitle, attachmentText, color := getIssuesPayloadInfo(p, SlackLinkFormatter, true) text, issueTitle, extraMarkdown, color := getIssuesPayloadInfo(p, SlackLinkFormatter, true)
var attachments []SlackAttachment var attachments []SlackAttachment
if attachmentText != "" { if extraMarkdown != "" {
attachmentText = SlackTextFormatter(attachmentText) extraMarkdown = SlackTextFormatter(extraMarkdown)
issueTitle = SlackTextFormatter(issueTitle) issueTitle = SlackTextFormatter(issueTitle)
attachments = append(attachments, SlackAttachment{ attachments = append(attachments, SlackAttachment{
Color: fmt.Sprintf("%x", color), Color: fmt.Sprintf("%x", color),
Title: issueTitle, Title: issueTitle,
TitleLink: p.Issue.HTMLURL, TitleLink: p.Issue.HTMLURL,
Text: attachmentText, Text: extraMarkdown,
}) })
} }
@ -210,17 +210,17 @@ func (s slackConvertor) Push(p *api.PushPayload) (SlackPayload, error) {
// PullRequest implements payloadConvertor PullRequest method // PullRequest implements payloadConvertor PullRequest method
func (s slackConvertor) PullRequest(p *api.PullRequestPayload) (SlackPayload, error) { func (s slackConvertor) PullRequest(p *api.PullRequestPayload) (SlackPayload, error) {
text, issueTitle, attachmentText, color := getPullRequestPayloadInfo(p, SlackLinkFormatter, true) text, issueTitle, extraMarkdown, color := getPullRequestPayloadInfo(p, SlackLinkFormatter, true)
var attachments []SlackAttachment var attachments []SlackAttachment
if attachmentText != "" { if extraMarkdown != "" {
attachmentText = SlackTextFormatter(p.PullRequest.Body) extraMarkdown = SlackTextFormatter(p.PullRequest.Body)
issueTitle = SlackTextFormatter(issueTitle) issueTitle = SlackTextFormatter(issueTitle)
attachments = append(attachments, SlackAttachment{ attachments = append(attachments, SlackAttachment{
Color: fmt.Sprintf("%x", color), Color: fmt.Sprintf("%x", color),
Title: issueTitle, Title: issueTitle,
TitleLink: p.PullRequest.HTMLURL, TitleLink: p.PullRequest.HTMLURL,
Text: attachmentText, Text: extraMarkdown,
}) })
} }
@ -281,20 +281,18 @@ type slackConvertor struct {
Color string Color string
} }
var _ payloadConvertor[SlackPayload] = slackConvertor{}
func newSlackRequest(_ context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) { func newSlackRequest(_ context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) {
meta := &SlackMeta{} meta := &SlackMeta{}
if err := json.Unmarshal([]byte(w.Meta), meta); err != nil { if err := json.Unmarshal([]byte(w.Meta), meta); err != nil {
return nil, nil, fmt.Errorf("newSlackRequest meta json: %w", err) return nil, nil, fmt.Errorf("newSlackRequest meta json: %w", err)
} }
sc := slackConvertor{ var pc payloadConvertor[SlackPayload] = slackConvertor{
Channel: meta.Channel, Channel: meta.Channel,
Username: meta.Username, Username: meta.Username,
IconURL: meta.IconURL, IconURL: meta.IconURL,
Color: meta.Color, Color: meta.Color,
} }
return newJSONRequest(sc, w, t, true) return newJSONRequest(pc, w, t, true)
} }
var slackChannel = regexp.MustCompile(`^#?[a-z0-9_-]{1,80}$`) var slackChannel = regexp.MustCompile(`^#?[a-z0-9_-]{1,80}$`)

@ -6,6 +6,7 @@ package webhook
import ( import (
"context" "context"
"fmt" "fmt"
"html"
"net/http" "net/http"
"strings" "strings"
@ -13,7 +14,9 @@ import (
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
webhook_module "code.gitea.io/gitea/modules/webhook" webhook_module "code.gitea.io/gitea/modules/webhook"
) )
@ -42,41 +45,43 @@ func GetTelegramHook(w *webhook_model.Webhook) *TelegramMeta {
return s return s
} }
type telegramConvertor struct{}
// Create implements PayloadConvertor Create method // Create implements PayloadConvertor Create method
func (t telegramConvertor) Create(p *api.CreatePayload) (TelegramPayload, error) { func (t telegramConvertor) Create(p *api.CreatePayload) (TelegramPayload, error) {
// created tag/branch // created tag/branch
refName := git.RefName(p.Ref).ShortName() refName := git.RefName(p.Ref).ShortName()
title := fmt.Sprintf(`[<a href="%s">%s</a>] %s <a href="%s">%s</a> created`, p.Repo.HTMLURL, p.Repo.FullName, p.RefType, title := fmt.Sprintf(`[%s] %s %s created`,
p.Repo.HTMLURL+"/src/"+refName, refName) htmlLinkFormatter(p.Repo.HTMLURL, p.Repo.FullName),
html.EscapeString(p.RefType),
htmlLinkFormatter(p.Repo.HTMLURL+"/src/"+util.PathEscapeSegments(refName), refName),
)
return createTelegramPayload(title), nil return createTelegramPayloadHTML(title), nil
} }
// Delete implements PayloadConvertor Delete method // Delete implements PayloadConvertor Delete method
func (t telegramConvertor) Delete(p *api.DeletePayload) (TelegramPayload, error) { func (t telegramConvertor) Delete(p *api.DeletePayload) (TelegramPayload, error) {
// created tag/branch // created tag/branch
refName := git.RefName(p.Ref).ShortName() refName := git.RefName(p.Ref).ShortName()
title := fmt.Sprintf(`[<a href="%s">%s</a>] %s <a href="%s">%s</a> deleted`, p.Repo.HTMLURL, p.Repo.FullName, p.RefType, title := fmt.Sprintf(`[%s] %s %s deleted`,
p.Repo.HTMLURL+"/src/"+refName, refName) htmlLinkFormatter(p.Repo.HTMLURL, p.Repo.FullName),
html.EscapeString(p.RefType),
return createTelegramPayload(title), nil htmlLinkFormatter(p.Repo.HTMLURL+"/src/"+util.PathEscapeSegments(refName), refName),
)
return createTelegramPayloadHTML(title), nil
} }
// Fork implements PayloadConvertor Fork method // Fork implements PayloadConvertor Fork method
func (t telegramConvertor) Fork(p *api.ForkPayload) (TelegramPayload, error) { func (t telegramConvertor) Fork(p *api.ForkPayload) (TelegramPayload, error) {
title := fmt.Sprintf(`%s is forked to <a href="%s">%s</a>`, p.Forkee.FullName, p.Repo.HTMLURL, p.Repo.FullName) title := fmt.Sprintf(`%s is forked to %s`, html.EscapeString(p.Forkee.FullName), htmlLinkFormatter(p.Repo.HTMLURL, p.Repo.FullName))
return createTelegramPayloadHTML(title), nil
return createTelegramPayload(title), nil
} }
// Push implements PayloadConvertor Push method // Push implements PayloadConvertor Push method
func (t telegramConvertor) Push(p *api.PushPayload) (TelegramPayload, error) { func (t telegramConvertor) Push(p *api.PushPayload) (TelegramPayload, error) {
var ( branchName := git.RefName(p.Ref).ShortName()
branchName = git.RefName(p.Ref).ShortName() var titleLink, commitDesc string
commitDesc string
)
var titleLink string
if p.TotalCommits == 1 { if p.TotalCommits == 1 {
commitDesc = "1 new commit" commitDesc = "1 new commit"
titleLink = p.Commits[0].URL titleLink = p.Commits[0].URL
@ -85,52 +90,42 @@ func (t telegramConvertor) Push(p *api.PushPayload) (TelegramPayload, error) {
titleLink = p.CompareURL titleLink = p.CompareURL
} }
if titleLink == "" { if titleLink == "" {
titleLink = p.Repo.HTMLURL + "/src/" + branchName titleLink = p.Repo.HTMLURL + "/src/" + util.PathEscapeSegments(branchName)
} }
title := fmt.Sprintf(`[<a href="%s">%s</a>:<a href="%s">%s</a>] %s`, p.Repo.HTMLURL, p.Repo.FullName, titleLink, branchName, commitDesc) title := fmt.Sprintf(`[%s:%s] %s`, htmlLinkFormatter(p.Repo.HTMLURL, p.Repo.FullName), htmlLinkFormatter(titleLink, branchName), html.EscapeString(commitDesc))
var text string var htmlCommits string
// for each commit, generate attachment text for _, commit := range p.Commits {
for i, commit := range p.Commits { htmlCommits += fmt.Sprintf("\n[%s] %s", htmlLinkFormatter(commit.URL, commit.ID[:7]), html.EscapeString(strings.TrimRight(commit.Message, "\r\n")))
var authorName string
if commit.Author != nil { if commit.Author != nil {
authorName = " - " + commit.Author.Name htmlCommits += " - " + html.EscapeString(commit.Author.Name)
}
text += fmt.Sprintf(`[<a href="%s">%s</a>] %s`, commit.URL, commit.ID[:7],
strings.TrimRight(commit.Message, "\r\n")) + authorName
// add linebreak to each commit but the last
if i < len(p.Commits)-1 {
text += "\n"
} }
} }
return createTelegramPayloadHTML(title + htmlCommits), nil
return createTelegramPayload(title + "\n" + text), nil
} }
// Issue implements PayloadConvertor Issue method // Issue implements PayloadConvertor Issue method
func (t telegramConvertor) Issue(p *api.IssuePayload) (TelegramPayload, error) { func (t telegramConvertor) Issue(p *api.IssuePayload) (TelegramPayload, error) {
text, _, attachmentText, _ := getIssuesPayloadInfo(p, htmlLinkFormatter, true) text, _, extraMarkdown, _ := getIssuesPayloadInfo(p, htmlLinkFormatter, true)
// TODO: at the moment the markdown can't be rendered easily because it has context-aware links (eg: attachments)
return createTelegramPayload(text + "\n\n" + attachmentText), nil return createTelegramPayloadHTML(text + "\n\n" + html.EscapeString(extraMarkdown)), nil
} }
// IssueComment implements PayloadConvertor IssueComment method // IssueComment implements PayloadConvertor IssueComment method
func (t telegramConvertor) IssueComment(p *api.IssueCommentPayload) (TelegramPayload, error) { func (t telegramConvertor) IssueComment(p *api.IssueCommentPayload) (TelegramPayload, error) {
text, _, _ := getIssueCommentPayloadInfo(p, htmlLinkFormatter, true) text, _, _ := getIssueCommentPayloadInfo(p, htmlLinkFormatter, true)
return createTelegramPayloadHTML(text + "\n" + html.EscapeString(p.Comment.Body)), nil
return createTelegramPayload(text + "\n" + p.Comment.Body), nil
} }
// PullRequest implements PayloadConvertor PullRequest method // PullRequest implements PayloadConvertor PullRequest method
func (t telegramConvertor) PullRequest(p *api.PullRequestPayload) (TelegramPayload, error) { func (t telegramConvertor) PullRequest(p *api.PullRequestPayload) (TelegramPayload, error) {
text, _, attachmentText, _ := getPullRequestPayloadInfo(p, htmlLinkFormatter, true) text, _, extraMarkdown, _ := getPullRequestPayloadInfo(p, htmlLinkFormatter, true)
return createTelegramPayloadHTML(text + "\n" + html.EscapeString(extraMarkdown)), nil
return createTelegramPayload(text + "\n" + attachmentText), nil
} }
// Review implements PayloadConvertor Review method // Review implements PayloadConvertor Review method
func (t telegramConvertor) Review(p *api.PullRequestPayload, event webhook_module.HookEventType) (TelegramPayload, error) { func (t telegramConvertor) Review(p *api.PullRequestPayload, event webhook_module.HookEventType) (TelegramPayload, error) {
var text, attachmentText string var text, extraMarkdown string
switch p.Action { switch p.Action {
case api.HookIssueReviewed: case api.HookIssueReviewed:
action, err := parseHookPullRequestEventType(event) action, err := parseHookPullRequestEventType(event)
@ -138,11 +133,11 @@ func (t telegramConvertor) Review(p *api.PullRequestPayload, event webhook_modul
return TelegramPayload{}, err return TelegramPayload{}, err
} }
text = fmt.Sprintf("[%s] Pull request review %s: #%d %s", p.Repository.FullName, action, p.Index, p.PullRequest.Title) text = fmt.Sprintf("[%s] Pull request review %s: #%d %s", html.EscapeString(p.Repository.FullName), html.EscapeString(action), p.Index, html.EscapeString(p.PullRequest.Title))
attachmentText = p.Review.Content extraMarkdown = p.Review.Content
} }
return createTelegramPayload(text + "\n" + attachmentText), nil return createTelegramPayloadHTML(text + "\n" + html.EscapeString(extraMarkdown)), nil
} }
// Repository implements PayloadConvertor Repository method // Repository implements PayloadConvertor Repository method
@ -150,11 +145,11 @@ func (t telegramConvertor) Repository(p *api.RepositoryPayload) (TelegramPayload
var title string var title string
switch p.Action { switch p.Action {
case api.HookRepoCreated: case api.HookRepoCreated:
title = fmt.Sprintf(`[<a href="%s">%s</a>] Repository created`, p.Repository.HTMLURL, p.Repository.FullName) title = fmt.Sprintf(`[%s] Repository created`, htmlLinkFormatter(p.Repository.HTMLURL, p.Repository.FullName))
return createTelegramPayload(title), nil return createTelegramPayloadHTML(title), nil
case api.HookRepoDeleted: case api.HookRepoDeleted:
title = fmt.Sprintf("[%s] Repository deleted", p.Repository.FullName) title = fmt.Sprintf("[%s] Repository deleted", html.EscapeString(p.Repository.FullName))
return createTelegramPayload(title), nil return createTelegramPayloadHTML(title), nil
} }
return TelegramPayload{}, nil return TelegramPayload{}, nil
} }
@ -163,34 +158,32 @@ func (t telegramConvertor) Repository(p *api.RepositoryPayload) (TelegramPayload
func (t telegramConvertor) Wiki(p *api.WikiPayload) (TelegramPayload, error) { func (t telegramConvertor) Wiki(p *api.WikiPayload) (TelegramPayload, error) {
text, _, _ := getWikiPayloadInfo(p, htmlLinkFormatter, true) text, _, _ := getWikiPayloadInfo(p, htmlLinkFormatter, true)
return createTelegramPayload(text), nil return createTelegramPayloadHTML(text), nil
} }
// Release implements PayloadConvertor Release method // Release implements PayloadConvertor Release method
func (t telegramConvertor) Release(p *api.ReleasePayload) (TelegramPayload, error) { func (t telegramConvertor) Release(p *api.ReleasePayload) (TelegramPayload, error) {
text, _ := getReleasePayloadInfo(p, htmlLinkFormatter, true) text, _ := getReleasePayloadInfo(p, htmlLinkFormatter, true)
return createTelegramPayload(text), nil return createTelegramPayloadHTML(text), nil
} }
func (t telegramConvertor) Package(p *api.PackagePayload) (TelegramPayload, error) { func (t telegramConvertor) Package(p *api.PackagePayload) (TelegramPayload, error) {
text, _ := getPackagePayloadInfo(p, htmlLinkFormatter, true) text, _ := getPackagePayloadInfo(p, htmlLinkFormatter, true)
return createTelegramPayload(text), nil return createTelegramPayloadHTML(text), nil
} }
func createTelegramPayload(message string) TelegramPayload { func createTelegramPayloadHTML(msgHTML string) TelegramPayload {
// https://core.telegram.org/bots/api#formatting-options
return TelegramPayload{ return TelegramPayload{
Message: strings.TrimSpace(message), Message: strings.TrimSpace(markup.Sanitize(msgHTML)),
ParseMode: "HTML", ParseMode: "HTML",
DisableWebPreview: true, DisableWebPreview: true,
} }
} }
type telegramConvertor struct{}
var _ payloadConvertor[TelegramPayload] = telegramConvertor{}
func newTelegramRequest(_ context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) { func newTelegramRequest(_ context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) {
return newJSONRequest(telegramConvertor{}, w, t, true) var pc payloadConvertor[TelegramPayload] = telegramConvertor{}
return newJSONRequest(pc, w, t, true)
} }

@ -20,11 +20,12 @@ func TestTelegramPayload(t *testing.T) {
tc := telegramConvertor{} tc := telegramConvertor{}
t.Run("Correct webhook params", func(t *testing.T) { t.Run("Correct webhook params", func(t *testing.T) {
p := createTelegramPayload("testMsg ") p := createTelegramPayloadHTML(`<a href=".">testMsg</a> <bad>`)
assert.Equal(t, TelegramPayload{
assert.Equal(t, "HTML", p.ParseMode) Message: `<a href="." rel="nofollow">testMsg</a>`,
assert.Equal(t, true, p.DisableWebPreview) ParseMode: "HTML",
assert.Equal(t, "testMsg", p.Message) DisableWebPreview: true,
}, p)
}) })
t.Run("Create", func(t *testing.T) { t.Run("Create", func(t *testing.T) {
@ -33,7 +34,7 @@ func TestTelegramPayload(t *testing.T) {
pl, err := tc.Create(p) pl, err := tc.Create(p)
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, `[<a href="http://localhost:3000/test/repo">test/repo</a>] branch <a href="http://localhost:3000/test/repo/src/test">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.Message)
}) })
t.Run("Delete", func(t *testing.T) { t.Run("Delete", func(t *testing.T) {
@ -42,7 +43,7 @@ func TestTelegramPayload(t *testing.T) {
pl, err := tc.Delete(p) pl, err := tc.Delete(p)
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, `[<a href="http://localhost:3000/test/repo">test/repo</a>] branch <a href="http://localhost:3000/test/repo/src/test">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.Message)
}) })
t.Run("Fork", func(t *testing.T) { t.Run("Fork", func(t *testing.T) {
@ -51,7 +52,7 @@ func TestTelegramPayload(t *testing.T) {
pl, err := tc.Fork(p) pl, err := tc.Fork(p)
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, `test/repo2 is forked to <a href="http://localhost:3000/test/repo">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.Message)
}) })
t.Run("Push", func(t *testing.T) { t.Run("Push", func(t *testing.T) {
@ -60,7 +61,9 @@ func TestTelegramPayload(t *testing.T) {
pl, err := tc.Push(p) pl, err := tc.Push(p)
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, "[<a href=\"http://localhost:3000/test/repo\">test/repo</a>:<a href=\"http://localhost:3000/test/repo/src/test\">test</a>] 2 new commits\n[<a href=\"http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778\">2020558</a>] commit message - user1\n[<a href=\"http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778\">2020558</a>] commit message - user1", pl.Message) 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)
}) })
t.Run("Issue", func(t *testing.T) { t.Run("Issue", func(t *testing.T) {
@ -70,13 +73,15 @@ func TestTelegramPayload(t *testing.T) {
pl, err := tc.Issue(p) pl, err := tc.Issue(p)
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, "[<a href=\"http://localhost:3000/test/repo\">test/repo</a>] Issue opened: <a href=\"http://localhost:3000/test/repo/issues/2\">#2 crash</a> by <a href=\"https://try.gitea.io/user1\">user1</a>\n\nissue body", pl.Message) 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)
p.Action = api.HookIssueClosed p.Action = api.HookIssueClosed
pl, err = tc.Issue(p) pl, err = tc.Issue(p)
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, `[<a href="http://localhost:3000/test/repo">test/repo</a>] Issue closed: <a href="http://localhost:3000/test/repo/issues/2">#2 crash</a> by <a href="https://try.gitea.io/user1">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.Message)
}) })
t.Run("IssueComment", func(t *testing.T) { t.Run("IssueComment", func(t *testing.T) {
@ -85,7 +90,8 @@ func TestTelegramPayload(t *testing.T) {
pl, err := tc.IssueComment(p) pl, err := tc.IssueComment(p)
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, "[<a href=\"http://localhost:3000/test/repo\">test/repo</a>] New comment on issue <a href=\"http://localhost:3000/test/repo/issues/2\">#2 crash</a> by <a href=\"https://try.gitea.io/user1\">user1</a>\nmore info needed", pl.Message) 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)
}) })
t.Run("PullRequest", func(t *testing.T) { t.Run("PullRequest", func(t *testing.T) {
@ -94,7 +100,8 @@ func TestTelegramPayload(t *testing.T) {
pl, err := tc.PullRequest(p) pl, err := tc.PullRequest(p)
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, "[<a href=\"http://localhost:3000/test/repo\">test/repo</a>] Pull request opened: <a href=\"http://localhost:3000/test/repo/pulls/12\">#12 Fix bug</a> by <a href=\"https://try.gitea.io/user1\">user1</a>\nfixes bug #2", pl.Message) 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)
}) })
t.Run("PullRequestComment", func(t *testing.T) { t.Run("PullRequestComment", func(t *testing.T) {
@ -103,7 +110,8 @@ func TestTelegramPayload(t *testing.T) {
pl, err := tc.IssueComment(p) pl, err := tc.IssueComment(p)
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, "[<a href=\"http://localhost:3000/test/repo\">test/repo</a>] New comment on pull request <a href=\"http://localhost:3000/test/repo/pulls/12\">#12 Fix bug</a> by <a href=\"https://try.gitea.io/user1\">user1</a>\nchanges requested", pl.Message) 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)
}) })
t.Run("Review", func(t *testing.T) { t.Run("Review", func(t *testing.T) {
@ -113,7 +121,8 @@ func TestTelegramPayload(t *testing.T) {
pl, err := tc.Review(p, webhook_module.HookEventPullRequestReviewApproved) pl, err := tc.Review(p, webhook_module.HookEventPullRequestReviewApproved)
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, "[test/repo] Pull request review approved: #12 Fix bug\ngood job", pl.Message) assert.Equal(t, `[test/repo] Pull request review approved: #12 Fix bug
good job`, pl.Message)
}) })
t.Run("Repository", func(t *testing.T) { t.Run("Repository", func(t *testing.T) {
@ -122,7 +131,7 @@ func TestTelegramPayload(t *testing.T) {
pl, err := tc.Repository(p) pl, err := tc.Repository(p)
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, `[<a href="http://localhost:3000/test/repo">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.Message)
}) })
t.Run("Package", func(t *testing.T) { t.Run("Package", func(t *testing.T) {
@ -131,7 +140,7 @@ func TestTelegramPayload(t *testing.T) {
pl, err := tc.Package(p) pl, err := tc.Package(p)
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, `Package created: <a href="http://localhost:3000/user1/-/packages/container/GiteaContainer/latest">GiteaContainer:latest</a> by <a href="https://try.gitea.io/user1">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.Message)
}) })
t.Run("Wiki", func(t *testing.T) { t.Run("Wiki", func(t *testing.T) {
@ -141,19 +150,19 @@ func TestTelegramPayload(t *testing.T) {
pl, err := tc.Wiki(p) pl, err := tc.Wiki(p)
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, `[<a href="http://localhost:3000/test/repo">test/repo</a>] New wiki page '<a href="http://localhost:3000/test/repo/wiki/index">index</a>' (Wiki change comment) by <a href="https://try.gitea.io/user1">user1</a>`, pl.Message) assert.Equal(t, `[<a href="http://localhost:3000/test/repo" rel="nofollow">test/repo</a>] New wiki page &#39;<a href="http://localhost:3000/test/repo/wiki/index" rel="nofollow">index</a>&#39; (Wiki change comment) by <a href="https://try.gitea.io/user1" rel="nofollow">user1</a>`, pl.Message)
p.Action = api.HookWikiEdited p.Action = api.HookWikiEdited
pl, err = tc.Wiki(p) pl, err = tc.Wiki(p)
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, `[<a href="http://localhost:3000/test/repo">test/repo</a>] Wiki page '<a href="http://localhost:3000/test/repo/wiki/index">index</a>' edited (Wiki change comment) by <a href="https://try.gitea.io/user1">user1</a>`, pl.Message) assert.Equal(t, `[<a href="http://localhost:3000/test/repo" rel="nofollow">test/repo</a>] Wiki page &#39;<a href="http://localhost:3000/test/repo/wiki/index" rel="nofollow">index</a>&#39; edited (Wiki change comment) by <a href="https://try.gitea.io/user1" rel="nofollow">user1</a>`, pl.Message)
p.Action = api.HookWikiDeleted p.Action = api.HookWikiDeleted
pl, err = tc.Wiki(p) pl, err = tc.Wiki(p)
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, `[<a href="http://localhost:3000/test/repo">test/repo</a>] Wiki page '<a href="http://localhost:3000/test/repo/wiki/index">index</a>' deleted by <a href="https://try.gitea.io/user1">user1</a>`, pl.Message) assert.Equal(t, `[<a href="http://localhost:3000/test/repo" rel="nofollow">test/repo</a>] Wiki page &#39;<a href="http://localhost:3000/test/repo/wiki/index" rel="nofollow">index</a>&#39; deleted by <a href="https://try.gitea.io/user1" rel="nofollow">user1</a>`, pl.Message)
}) })
t.Run("Release", func(t *testing.T) { t.Run("Release", func(t *testing.T) {
@ -162,7 +171,7 @@ func TestTelegramPayload(t *testing.T) {
pl, err := tc.Release(p) pl, err := tc.Release(p)
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, `[<a href="http://localhost:3000/test/repo">test/repo</a>] Release created: <a href="http://localhost:3000/test/repo/releases/tag/v1.0">v1.0</a> by <a href="https://try.gitea.io/user1">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.Message)
}) })
} }
@ -198,5 +207,7 @@ func TestTelegramJSONPayload(t *testing.T) {
var body TelegramPayload var body TelegramPayload
err = json.NewDecoder(req.Body).Decode(&body) err = json.NewDecoder(req.Body).Decode(&body)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, "[<a href=\"http://localhost:3000/test/repo\">test/repo</a>:<a href=\"http://localhost:3000/test/repo/src/test\">test</a>] 2 new commits\n[<a href=\"http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778\">2020558</a>] commit message - user1\n[<a href=\"http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778\">2020558</a>] commit message - user1", body.Message) 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)
} }

@ -41,6 +41,8 @@ func newWechatworkMarkdownPayload(title string) WechatworkPayload {
} }
} }
type wechatworkConvertor struct{}
// Create implements PayloadConvertor Create method // Create implements PayloadConvertor Create method
func (wc wechatworkConvertor) Create(p *api.CreatePayload) (WechatworkPayload, error) { func (wc wechatworkConvertor) Create(p *api.CreatePayload) (WechatworkPayload, error) {
// created tag/branch // created tag/branch
@ -97,9 +99,9 @@ func (wc wechatworkConvertor) Push(p *api.PushPayload) (WechatworkPayload, error
// Issue implements PayloadConvertor Issue method // Issue implements PayloadConvertor Issue method
func (wc wechatworkConvertor) Issue(p *api.IssuePayload) (WechatworkPayload, error) { func (wc wechatworkConvertor) Issue(p *api.IssuePayload) (WechatworkPayload, error) {
text, issueTitle, attachmentText, _ := getIssuesPayloadInfo(p, noneLinkFormatter, true) text, issueTitle, extraMarkdown, _ := getIssuesPayloadInfo(p, noneLinkFormatter, true)
var content string var content string
content += fmt.Sprintf(" ><font color=\"info\">%s</font>\n >%s \n ><font color=\"warning\"> %s</font> \n [%s](%s)", text, attachmentText, issueTitle, p.Issue.HTMLURL, p.Issue.HTMLURL) content += fmt.Sprintf(" ><font color=\"info\">%s</font>\n >%s \n ><font color=\"warning\"> %s</font> \n [%s](%s)", text, extraMarkdown, issueTitle, p.Issue.HTMLURL, p.Issue.HTMLURL)
return newWechatworkMarkdownPayload(content), nil return newWechatworkMarkdownPayload(content), nil
} }
@ -115,9 +117,9 @@ func (wc wechatworkConvertor) IssueComment(p *api.IssueCommentPayload) (Wechatwo
// PullRequest implements PayloadConvertor PullRequest method // PullRequest implements PayloadConvertor PullRequest method
func (wc wechatworkConvertor) PullRequest(p *api.PullRequestPayload) (WechatworkPayload, error) { func (wc wechatworkConvertor) PullRequest(p *api.PullRequestPayload) (WechatworkPayload, error) {
text, issueTitle, attachmentText, _ := getPullRequestPayloadInfo(p, noneLinkFormatter, true) text, issueTitle, extraMarkdown, _ := getPullRequestPayloadInfo(p, noneLinkFormatter, true)
pr := fmt.Sprintf("> <font color=\"info\"> %s </font> \r\n > <font color=\"comment\">%s </font> \r\n > <font color=\"comment\">%s </font> \r\n", pr := fmt.Sprintf("> <font color=\"info\"> %s </font> \r\n > <font color=\"comment\">%s </font> \r\n > <font color=\"comment\">%s </font> \r\n",
text, issueTitle, attachmentText) text, issueTitle, extraMarkdown)
return newWechatworkMarkdownPayload(pr), nil return newWechatworkMarkdownPayload(pr), nil
} }
@ -173,10 +175,7 @@ func (wc wechatworkConvertor) Package(p *api.PackagePayload) (WechatworkPayload,
return newWechatworkMarkdownPayload(text), nil return newWechatworkMarkdownPayload(text), nil
} }
type wechatworkConvertor struct{}
var _ payloadConvertor[WechatworkPayload] = wechatworkConvertor{}
func newWechatworkRequest(_ context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) { func newWechatworkRequest(_ context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) {
return newJSONRequest(wechatworkConvertor{}, w, t, true) var pc payloadConvertor[WechatworkPayload] = wechatworkConvertor{}
return newJSONRequest(pc, w, t, true)
} }