Modify the content format of the Feishu webhook (#25106)

close https://github.com/go-gitea/gitea/issues/24368

## what my pull request does

Since the official documentation states that custom bots do not support
hyperlink functionality, simply adding it without making some formatting
changes would result in an unappealing output. Therefore, I have
modified the formatting of the output. Currently, it is only used for
Feishu.

--- 


[docs](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/im-v1/message/create_json#%E8%B6%85%E9%93%BE%E6%8E%A5%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E)

<img width="641" alt="image"
src="https://github.com/go-gitea/gitea/assets/75628309/360e1c81-ab64-4ef6-851e-aa450d6e85a4">

- Issue

<img width="423" alt="image"
src="https://github.com/go-gitea/gitea/assets/75628309/519f6fae-81ab-4ec8-89b8-f6a73ff93783">


- Issue Comment


<img width="548" alt="image"
src="https://github.com/go-gitea/gitea/assets/75628309/749c0d1c-3657-431e-b787-8bf4c23cce83">


- Assign

<img width="431" alt="image"
src="https://github.com/go-gitea/gitea/assets/75628309/066f99e5-eabb-455d-91fb-a8359cc26dc7">

<img width="457" alt="image"
src="https://github.com/go-gitea/gitea/assets/75628309/c6c10f99-db83-46ef-a775-4c91979fa68f">

- Merge

<img width="408" alt="image"
src="https://github.com/go-gitea/gitea/assets/75628309/e627bf43-5954-45aa-acf6-261ee046802f">

- PullRequest

<img width="425" alt="image"
src="https://github.com/go-gitea/gitea/assets/75628309/72cfa714-d3fa-4fb9-abdd-e8508d756056">
This commit is contained in:
谈笑风生间 2023-08-24 09:00:11 +08:00 committed by GitHub
parent f67f57a4c2
commit 5104c887d3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 111 additions and 15 deletions

View file

@ -97,23 +97,40 @@ func (f *FeishuPayload) Push(p *api.PushPayload) (api.Payloader, error) {
// Issue implements PayloadConvertor Issue method // Issue implements PayloadConvertor Issue method
func (f *FeishuPayload) Issue(p *api.IssuePayload) (api.Payloader, error) { func (f *FeishuPayload) Issue(p *api.IssuePayload) (api.Payloader, error) {
text, issueTitle, attachmentText, _ := getIssuesPayloadInfo(p, noneLinkFormatter, true) title, link, by, operator, result, assignees := getIssuesInfo(p)
var res api.Payloader
return newFeishuTextPayload(issueTitle + "\r\n" + text + "\r\n\r\n" + attachmentText), nil if assignees != "" {
if p.Action == api.HookIssueAssigned || p.Action == api.HookIssueUnassigned || p.Action == api.HookIssueMilestoned {
res = newFeishuTextPayload(fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s\n\n%s", title, link, by, operator, result, assignees, p.Issue.Body))
} else {
res = newFeishuTextPayload(fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n\n%s", title, link, by, operator, assignees, p.Issue.Body))
}
} else {
res = newFeishuTextPayload(fmt.Sprintf("%s\n%s\n%s\n%s\n\n%s", title, link, by, operator, p.Issue.Body))
}
return res, nil
} }
// IssueComment implements PayloadConvertor IssueComment method // IssueComment implements PayloadConvertor IssueComment method
func (f *FeishuPayload) IssueComment(p *api.IssueCommentPayload) (api.Payloader, error) { func (f *FeishuPayload) IssueComment(p *api.IssueCommentPayload) (api.Payloader, error) {
text, issueTitle, _ := getIssueCommentPayloadInfo(p, noneLinkFormatter, true) title, link, by, operator := getIssuesCommentInfo(p)
return newFeishuTextPayload(fmt.Sprintf("%s\n%s\n%s\n%s\n\n%s", title, link, by, operator, p.Comment.Body)), nil
return newFeishuTextPayload(issueTitle + "\r\n" + text + "\r\n\r\n" + p.Comment.Body), nil
} }
// PullRequest implements PayloadConvertor PullRequest method // PullRequest implements PayloadConvertor PullRequest method
func (f *FeishuPayload) PullRequest(p *api.PullRequestPayload) (api.Payloader, error) { func (f *FeishuPayload) PullRequest(p *api.PullRequestPayload) (api.Payloader, error) {
text, issueTitle, attachmentText, _ := getPullRequestPayloadInfo(p, noneLinkFormatter, true) title, link, by, operator, result, assignees := getPullRequestInfo(p)
var res api.Payloader
return newFeishuTextPayload(issueTitle + "\r\n" + text + "\r\n\r\n" + attachmentText), nil if assignees != "" {
if p.Action == api.HookIssueAssigned || p.Action == api.HookIssueUnassigned || p.Action == api.HookIssueMilestoned {
res = newFeishuTextPayload(fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s\n\n%s", title, link, by, operator, result, assignees, p.PullRequest.Body))
} else {
res = newFeishuTextPayload(fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n\n%s", title, link, by, operator, assignees, p.PullRequest.Body))
}
} else {
res = newFeishuTextPayload(fmt.Sprintf("%s\n%s\n%s\n%s\n\n%s", title, link, by, operator, p.PullRequest.Body))
}
return res, nil
} }
// Review implements PayloadConvertor Review method // Review implements PayloadConvertor Review method

View file

@ -72,7 +72,7 @@ func TestFeishuPayload(t *testing.T) {
require.NotNil(t, pl) require.NotNil(t, pl)
require.IsType(t, &FeishuPayload{}, pl) require.IsType(t, &FeishuPayload{}, pl)
assert.Equal(t, "#2 crash\r\n[test/repo] Issue opened: #2 crash by user1\r\n\r\nissue body", pl.(*FeishuPayload).Content.Text) assert.Equal(t, "[Issue-test/repo #2]: opened\ncrash\nhttp://localhost:3000/test/repo/issues/2\nIssue by user1\nOperator: user1\nAssignees: user1\n\nissue body", pl.(*FeishuPayload).Content.Text)
p.Action = api.HookIssueClosed p.Action = api.HookIssueClosed
pl, err = d.Issue(p) pl, err = d.Issue(p)
@ -80,7 +80,7 @@ func TestFeishuPayload(t *testing.T) {
require.NotNil(t, pl) require.NotNil(t, pl)
require.IsType(t, &FeishuPayload{}, pl) require.IsType(t, &FeishuPayload{}, pl)
assert.Equal(t, "#2 crash\r\n[test/repo] Issue closed: #2 crash by user1", pl.(*FeishuPayload).Content.Text) assert.Equal(t, "[Issue-test/repo #2]: closed\ncrash\nhttp://localhost:3000/test/repo/issues/2\nIssue by user1\nOperator: user1\nAssignees: user1\n\nissue body", pl.(*FeishuPayload).Content.Text)
}) })
t.Run("IssueComment", func(t *testing.T) { t.Run("IssueComment", func(t *testing.T) {
@ -92,7 +92,7 @@ func TestFeishuPayload(t *testing.T) {
require.NotNil(t, pl) require.NotNil(t, pl)
require.IsType(t, &FeishuPayload{}, pl) require.IsType(t, &FeishuPayload{}, pl)
assert.Equal(t, "#2 crash\r\n[test/repo] New comment on issue #2 crash by user1\r\n\r\nmore info needed", pl.(*FeishuPayload).Content.Text) assert.Equal(t, "[Comment-test/repo #2]: created\ncrash\nhttp://localhost:3000/test/repo/issues/2\nIssue by user1\nOperator: user1\n\nmore info needed", pl.(*FeishuPayload).Content.Text)
}) })
t.Run("PullRequest", func(t *testing.T) { t.Run("PullRequest", func(t *testing.T) {
@ -104,7 +104,7 @@ func TestFeishuPayload(t *testing.T) {
require.NotNil(t, pl) require.NotNil(t, pl)
require.IsType(t, &FeishuPayload{}, pl) require.IsType(t, &FeishuPayload{}, pl)
assert.Equal(t, "#12 Fix bug\r\n[test/repo] Pull request opened: #12 Fix bug by user1\r\n\r\nfixes bug #2", pl.(*FeishuPayload).Content.Text) assert.Equal(t, "[PullRequest-test/repo #12]: opened\nFix bug\nhttp://localhost:3000/test/repo/pulls/12\nPullRequest by user1\nOperator: user1\nAssignees: user1\n\nfixes bug #2", pl.(*FeishuPayload).Content.Text)
}) })
t.Run("PullRequestComment", func(t *testing.T) { t.Run("PullRequestComment", func(t *testing.T) {
@ -116,7 +116,7 @@ func TestFeishuPayload(t *testing.T) {
require.NotNil(t, pl) require.NotNil(t, pl)
require.IsType(t, &FeishuPayload{}, pl) require.IsType(t, &FeishuPayload{}, pl)
assert.Equal(t, "#12 Fix bug\r\n[test/repo] New comment on pull request #12 Fix bug by user1\r\n\r\nchanges requested", pl.(*FeishuPayload).Content.Text) assert.Equal(t, "[Comment-test/repo #12]: created\nFix bug\nhttp://localhost:3000/test/repo/pulls/12\nPullRequest by user1\nOperator: user1\n\nchanges requested", pl.(*FeishuPayload).Content.Text)
}) })
t.Run("Review", func(t *testing.T) { t.Run("Review", func(t *testing.T) {

View file

@ -28,6 +28,69 @@ func htmlLinkFormatter(url, text string) string {
return fmt.Sprintf(`<a href="%s">%s</a>`, html.EscapeString(url), html.EscapeString(text)) return fmt.Sprintf(`<a href="%s">%s</a>`, html.EscapeString(url), html.EscapeString(text))
} }
// getPullRequestInfo gets the information for a pull request
func getPullRequestInfo(p *api.PullRequestPayload) (title, link, by, operator, operateResult, assignees string) {
title = fmt.Sprintf("[PullRequest-%s #%d]: %s\n%s", p.Repository.FullName, p.PullRequest.Index, p.Action, p.PullRequest.Title)
assignList := p.PullRequest.Assignees
assignStringList := make([]string, len(assignList))
for i, user := range assignList {
assignStringList[i] = user.UserName
}
if p.Action == api.HookIssueAssigned {
operateResult = fmt.Sprintf("%s assign this to %s", p.Sender.UserName, assignList[len(assignList)-1].UserName)
} else if p.Action == api.HookIssueUnassigned {
operateResult = fmt.Sprintf("%s unassigned this for someone", p.Sender.UserName)
} else if p.Action == api.HookIssueMilestoned {
operateResult = fmt.Sprintf("%s/milestone/%d", p.Repository.HTMLURL, p.PullRequest.Milestone.ID)
}
link = p.PullRequest.HTMLURL
by = fmt.Sprintf("PullRequest by %s", p.PullRequest.Poster.UserName)
if len(assignStringList) > 0 {
assignees = fmt.Sprintf("Assignees: %s", strings.Join(assignStringList, ", "))
}
operator = fmt.Sprintf("Operator: %s", p.Sender.UserName)
return title, link, by, operator, operateResult, assignees
}
// getIssuesInfo gets the information for an issue
func getIssuesInfo(p *api.IssuePayload) (issueTitle, link, by, operator, operateResult, assignees string) {
issueTitle = fmt.Sprintf("[Issue-%s #%d]: %s\n%s", p.Repository.FullName, p.Issue.Index, p.Action, p.Issue.Title)
assignList := p.Issue.Assignees
assignStringList := make([]string, len(assignList))
for i, user := range assignList {
assignStringList[i] = user.UserName
}
if p.Action == api.HookIssueAssigned {
operateResult = fmt.Sprintf("%s assign this to %s", p.Sender.UserName, assignList[len(assignList)-1].UserName)
} else if p.Action == api.HookIssueUnassigned {
operateResult = fmt.Sprintf("%s unassigned this for someone", p.Sender.UserName)
} else if p.Action == api.HookIssueMilestoned {
operateResult = fmt.Sprintf("%s/milestone/%d", p.Repository.HTMLURL, p.Issue.Milestone.ID)
}
link = p.Issue.HTMLURL
by = fmt.Sprintf("Issue by %s", p.Issue.Poster.UserName)
if len(assignStringList) > 0 {
assignees = fmt.Sprintf("Assignees: %s", strings.Join(assignStringList, ", "))
}
operator = fmt.Sprintf("Operator: %s", p.Sender.UserName)
return issueTitle, link, by, operator, operateResult, assignees
}
// getIssuesCommentInfo gets the information for a comment
func getIssuesCommentInfo(p *api.IssueCommentPayload) (title, link, by, operator string) {
title = fmt.Sprintf("[Comment-%s #%d]: %s\n%s", p.Repository.FullName, p.Issue.Index, p.Action, p.Issue.Title)
link = p.Issue.HTMLURL
if p.IsPull {
by = fmt.Sprintf("PullRequest by %s", p.Issue.Poster.UserName)
} else {
by = fmt.Sprintf("Issue by %s", p.Issue.Poster.UserName)
}
operator = fmt.Sprintf("Operator: %s", p.Sender.UserName)
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) (string, string, string, int) {
repoLink := linkFormatter(p.Repository.HTMLURL, p.Repository.FullName) repoLink := linkFormatter(p.Repository.HTMLURL, p.Repository.FullName)
issueTitle := fmt.Sprintf("#%d %s", p.Index, p.Issue.Title) issueTitle := fmt.Sprintf("#%d %s", p.Index, p.Issue.Title)

View file

@ -123,6 +123,10 @@ func issueTestPayload() *api.IssuePayload {
HTMLURL: "http://localhost:3000/test/repo/issues/2", HTMLURL: "http://localhost:3000/test/repo/issues/2",
Title: "crash", Title: "crash",
Body: "issue body", Body: "issue body",
Poster: &api.User{
UserName: "user1",
AvatarURL: "http://localhost:3000/user1/avatar",
},
Assignees: []*api.User{ Assignees: []*api.User{
{ {
UserName: "user1", UserName: "user1",
@ -161,6 +165,10 @@ func issueCommentTestPayload() *api.IssueCommentPayload {
URL: "http://localhost:3000/api/v1/repos/test/repo/issues/2", URL: "http://localhost:3000/api/v1/repos/test/repo/issues/2",
HTMLURL: "http://localhost:3000/test/repo/issues/2", HTMLURL: "http://localhost:3000/test/repo/issues/2",
Title: "crash", Title: "crash",
Poster: &api.User{
UserName: "user1",
AvatarURL: "http://localhost:3000/user1/avatar",
},
Body: "this happened", Body: "this happened",
}, },
} }
@ -190,6 +198,10 @@ func pullRequestCommentTestPayload() *api.IssueCommentPayload {
HTMLURL: "http://localhost:3000/test/repo/pulls/12", HTMLURL: "http://localhost:3000/test/repo/pulls/12",
Title: "Fix bug", Title: "Fix bug",
Body: "fixes bug #2", Body: "fixes bug #2",
Poster: &api.User{
UserName: "user1",
AvatarURL: "http://localhost:3000/user1/avatar",
},
}, },
IsPull: true, IsPull: true,
} }
@ -254,6 +266,10 @@ func pullRequestTestPayload() *api.PullRequestPayload {
Title: "Fix bug", Title: "Fix bug",
Body: "fixes bug #2", Body: "fixes bug #2",
Mergeable: true, Mergeable: true,
Poster: &api.User{
UserName: "user1",
AvatarURL: "http://localhost:3000/user1/avatar",
},
Assignees: []*api.User{ Assignees: []*api.User{
{ {
UserName: "user1", UserName: "user1",