From 83a8944fcf64af9bf5b0a4e233f9a7f917838a4d Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 12 Feb 2020 16:48:28 +0800 Subject: [PATCH] Add feishu webhook support (#10229) Add feishu webhook support --- models/webhook.go | 4 + modules/auth/repo_form.go | 11 ++ modules/setting/webhook.go | 2 +- modules/structs/hook.go | 2 +- modules/webhook/feishu.go | 201 ++++++++++++++++++++ modules/webhook/webhook.go | 5 + options/locale/locale_en-US.ini | 1 + public/img/feishu.png | Bin 0 -> 1982 bytes routers/repo/webhook.go | 72 +++++++ routers/routes/routes.go | 6 + templates/admin/hook_new.tmpl | 3 + templates/org/settings/hook_new.tmpl | 3 + templates/repo/settings/webhook/feishu.tmpl | 11 ++ templates/repo/settings/webhook/list.tmpl | 3 + templates/repo/settings/webhook/new.tmpl | 3 + templates/swagger/v1_json.tmpl | 3 +- 16 files changed, 327 insertions(+), 3 deletions(-) create mode 100644 modules/webhook/feishu.go create mode 100644 public/img/feishu.png create mode 100644 templates/repo/settings/webhook/feishu.tmpl diff --git a/models/webhook.go b/models/webhook.go index 55f58c64a1..626489b342 100644 --- a/models/webhook.go +++ b/models/webhook.go @@ -445,6 +445,7 @@ const ( DINGTALK TELEGRAM MSTEAMS + FEISHU ) var hookTaskTypes = map[string]HookTaskType{ @@ -455,6 +456,7 @@ var hookTaskTypes = map[string]HookTaskType{ "dingtalk": DINGTALK, "telegram": TELEGRAM, "msteams": MSTEAMS, + "feishu": FEISHU, } // ToHookTaskType returns HookTaskType by given name. @@ -479,6 +481,8 @@ func (t HookTaskType) Name() string { return "telegram" case MSTEAMS: return "msteams" + case FEISHU: + return "feishu" } return "" } diff --git a/modules/auth/repo_form.go b/modules/auth/repo_form.go index a5071de47e..932976b6f9 100644 --- a/modules/auth/repo_form.go +++ b/modules/auth/repo_form.go @@ -313,6 +313,17 @@ func (f *NewMSTeamsHookForm) Validate(ctx *macaron.Context, errs binding.Errors) return validate(errs, ctx.Data, f, ctx.Locale) } +// NewFeishuHookForm form for creating feishu hook +type NewFeishuHookForm struct { + PayloadURL string `binding:"Required;ValidUrl"` + WebhookForm +} + +// Validate validates the fields +func (f *NewFeishuHookForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) +} + // .___ // | | ______ ________ __ ____ // | |/ ___// ___/ | \_/ __ \ diff --git a/modules/setting/webhook.go b/modules/setting/webhook.go index 4a953616f1..3e6040a605 100644 --- a/modules/setting/webhook.go +++ b/modules/setting/webhook.go @@ -36,7 +36,7 @@ func newWebhookService() { Webhook.QueueLength = sec.Key("QUEUE_LENGTH").MustInt(1000) Webhook.DeliverTimeout = sec.Key("DELIVER_TIMEOUT").MustInt(5) Webhook.SkipTLSVerify = sec.Key("SKIP_TLS_VERIFY").MustBool() - Webhook.Types = []string{"gitea", "gogs", "slack", "discord", "dingtalk", "telegram", "msteams"} + Webhook.Types = []string{"gitea", "gogs", "slack", "discord", "dingtalk", "telegram", "msteams", "feishu"} Webhook.PagingNum = sec.Key("PAGING_NUM").MustInt(10) Webhook.ProxyURL = sec.Key("PROXY_URL").MustString("") if Webhook.ProxyURL != "" { diff --git a/modules/structs/hook.go b/modules/structs/hook.go index 7ec46254eb..f2bc4f16e1 100644 --- a/modules/structs/hook.go +++ b/modules/structs/hook.go @@ -41,7 +41,7 @@ type CreateHookOptionConfig map[string]string // CreateHookOption options when create a hook type CreateHookOption struct { // required: true - // enum: dingtalk,discord,gitea,gogs,msteams,slack,telegram + // enum: dingtalk,discord,gitea,gogs,msteams,slack,telegram,feishu Type string `json:"type" binding:"Required"` // required: true Config CreateHookOptionConfig `json:"config" binding:"Required"` diff --git a/modules/webhook/feishu.go b/modules/webhook/feishu.go new file mode 100644 index 0000000000..6af78494c9 --- /dev/null +++ b/modules/webhook/feishu.go @@ -0,0 +1,201 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package webhook + +import ( + "encoding/json" + "fmt" + "strings" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/git" + api "code.gitea.io/gitea/modules/structs" +) + +type ( + // FeishuPayload represents + FeishuPayload struct { + Title string `json:"title"` + Text string `json:"text"` + } +) + +// SetSecret sets the Feishu secret +func (p *FeishuPayload) SetSecret(_ string) {} + +// JSONPayload Marshals the FeishuPayload to json +func (p *FeishuPayload) JSONPayload() ([]byte, error) { + data, err := json.MarshalIndent(p, "", " ") + if err != nil { + return []byte{}, err + } + return data, nil +} + +func getFeishuCreatePayload(p *api.CreatePayload) (*FeishuPayload, error) { + // created tag/branch + refName := git.RefEndName(p.Ref) + title := fmt.Sprintf("[%s] %s %s created", p.Repo.FullName, p.RefType, refName) + + return &FeishuPayload{ + Text: title, + Title: title, + }, nil +} + +func getFeishuDeletePayload(p *api.DeletePayload) (*FeishuPayload, error) { + // created tag/branch + refName := git.RefEndName(p.Ref) + title := fmt.Sprintf("[%s] %s %s deleted", p.Repo.FullName, p.RefType, refName) + + return &FeishuPayload{ + Text: title, + Title: title, + }, nil +} + +func getFeishuForkPayload(p *api.ForkPayload) (*FeishuPayload, error) { + title := fmt.Sprintf("%s is forked to %s", p.Forkee.FullName, p.Repo.FullName) + + return &FeishuPayload{ + Text: title, + Title: title, + }, nil +} + +func getFeishuPushPayload(p *api.PushPayload) (*FeishuPayload, error) { + var ( + branchName = git.RefEndName(p.Ref) + commitDesc string + ) + + title := fmt.Sprintf("[%s:%s] %s", p.Repo.FullName, branchName, commitDesc) + + var text string + // for each commit, generate attachment text + for i, commit := range p.Commits { + var authorName string + if commit.Author != nil { + authorName = " - " + commit.Author.Name + } + text += fmt.Sprintf("[%s](%s) %s", commit.ID[:7], commit.URL, + strings.TrimRight(commit.Message, "\r\n")) + authorName + // add linebreak to each commit but the last + if i < len(p.Commits)-1 { + text += "\n" + } + } + + return &FeishuPayload{ + Text: text, + Title: title, + }, nil +} + +func getFeishuIssuesPayload(p *api.IssuePayload) (*FeishuPayload, error) { + text, issueTitle, attachmentText, _ := getIssuesPayloadInfo(p, noneLinkFormatter, true) + + return &FeishuPayload{ + Text: text + "\r\n\r\n" + attachmentText, + Title: issueTitle, + }, nil +} + +func getFeishuIssueCommentPayload(p *api.IssueCommentPayload) (*FeishuPayload, error) { + text, issueTitle, _ := getIssueCommentPayloadInfo(p, noneLinkFormatter, true) + + return &FeishuPayload{ + Text: text + "\r\n\r\n" + p.Comment.Body, + Title: issueTitle, + }, nil +} + +func getFeishuPullRequestPayload(p *api.PullRequestPayload) (*FeishuPayload, error) { + text, issueTitle, attachmentText, _ := getPullRequestPayloadInfo(p, noneLinkFormatter, true) + + return &FeishuPayload{ + Text: text + "\r\n\r\n" + attachmentText, + Title: issueTitle, + }, nil +} + +func getFeishuPullRequestApprovalPayload(p *api.PullRequestPayload, event models.HookEventType) (*FeishuPayload, error) { + var text, title string + switch p.Action { + case api.HookIssueSynchronized: + action, err := parseHookPullRequestEventType(event) + if err != nil { + return nil, err + } + + title = fmt.Sprintf("[%s] Pull request review %s : #%d %s", p.Repository.FullName, action, p.Index, p.PullRequest.Title) + text = p.Review.Content + + } + + return &FeishuPayload{ + Text: title + "\r\n\r\n" + text, + Title: title, + }, nil +} + +func getFeishuRepositoryPayload(p *api.RepositoryPayload) (*FeishuPayload, error) { + var title string + switch p.Action { + case api.HookRepoCreated: + title = fmt.Sprintf("[%s] Repository created", p.Repository.FullName) + return &FeishuPayload{ + Text: title, + Title: title, + }, nil + case api.HookRepoDeleted: + title = fmt.Sprintf("[%s] Repository deleted", p.Repository.FullName) + return &FeishuPayload{ + Title: title, + Text: title, + }, nil + } + + return nil, nil +} + +func getFeishuReleasePayload(p *api.ReleasePayload) (*FeishuPayload, error) { + text, _ := getReleasePayloadInfo(p, noneLinkFormatter, true) + + return &FeishuPayload{ + Text: text, + Title: text, + }, nil +} + +// GetFeishuPayload converts a ding talk webhook into a FeishuPayload +func GetFeishuPayload(p api.Payloader, event models.HookEventType, meta string) (*FeishuPayload, error) { + s := new(FeishuPayload) + + switch event { + case models.HookEventCreate: + return getFeishuCreatePayload(p.(*api.CreatePayload)) + case models.HookEventDelete: + return getFeishuDeletePayload(p.(*api.DeletePayload)) + case models.HookEventFork: + return getFeishuForkPayload(p.(*api.ForkPayload)) + case models.HookEventIssues: + return getFeishuIssuesPayload(p.(*api.IssuePayload)) + case models.HookEventIssueComment: + return getFeishuIssueCommentPayload(p.(*api.IssueCommentPayload)) + case models.HookEventPush: + return getFeishuPushPayload(p.(*api.PushPayload)) + case models.HookEventPullRequest: + return getFeishuPullRequestPayload(p.(*api.PullRequestPayload)) + case models.HookEventPullRequestApproved, models.HookEventPullRequestRejected, models.HookEventPullRequestComment: + return getFeishuPullRequestApprovalPayload(p.(*api.PullRequestPayload), event) + case models.HookEventRepository: + return getFeishuRepositoryPayload(p.(*api.RepositoryPayload)) + case models.HookEventRelease: + return getFeishuReleasePayload(p.(*api.ReleasePayload)) + } + + return s, nil +} diff --git a/modules/webhook/webhook.go b/modules/webhook/webhook.go index 410e47461f..2fab0803bc 100644 --- a/modules/webhook/webhook.go +++ b/modules/webhook/webhook.go @@ -114,6 +114,11 @@ func prepareWebhook(w *models.Webhook, repo *models.Repository, event models.Hoo if err != nil { return fmt.Errorf("GetMSTeamsPayload: %v", err) } + case models.FEISHU: + payloader, err = GetFeishuPayload(p, event, w.Meta) + if err != nil { + return fmt.Errorf("GetFeishuPayload: %v", err) + } default: p.SetSecret(w.Secret) payloader = p diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 5b4d491bd7..dbd1a3680d 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -1398,6 +1398,7 @@ settings.add_discord_hook_desc = Integrate Discord into your re settings.add_dingtalk_hook_desc = Integrate Dingtalk into your repository. settings.add_telegram_hook_desc = Integrate Telegram into your repository. settings.add_msteams_hook_desc = Integrate Microsoft Teams into your repository. +settings.add_feishu_hook_desc = Integrate Feishu into your repository. settings.deploy_keys = Deploy Keys settings.add_deploy_key = Add Deploy Key settings.deploy_key_desc = Deploy keys have read-only pull access to the repository. diff --git a/public/img/feishu.png b/public/img/feishu.png new file mode 100644 index 0000000000000000000000000000000000000000..2c3ab744136a1be5d928be6780489da5ecf19b4c GIT binary patch literal 1982 zcmY+Cdpy(oAIIM(ol*{wB;E8&2$ecXYjHy4Qn{4W=~O7Wm2{CyNo~27kQTXaNhS42 zqJzYesfY-(88ghXVRret%r-vf(VxH9Om zstN$)RS?Di=1sdF@N@uxcfyG@f#l=ZbWxl9Nuv@@jm%3#ox)ji<7{2uY`k#n#LuC6 zfgHLjLBs@KA&fJ&XQ1yqv?>Md%SGv5(R+ocX;2B=rVttDq?E&*m~)e|yT+Bbr@6W# z9gx=schMio2pCmM?;#K~psNb}$3esZKOu-%77x)T5`qikAZFn+5lEQOT>}B){ly3* zz0gAi4lU%30QI=>5`j5M~VW@h(X!|pPS7l2Uc8?fTYi<0NLdudvOm8ecmEr1GC1x z070n8qYy!;)S(ao`@6?8WQ%ct`x!sg4bnakb$dxUx)^8KO-$nf3qmBG*<#;GZbTXK zEkeE}=ooqIhKv_oF}Lgfgws?%u&9<<2*f=w*yJ@a1Tk2gj2}ER2+a4uta6YsK*Byd zJqSUa`UGiMnXC{K0)Y(?Uv5qhtB%ltRsHPCly?!L$OVsOg8lc|8{?#V1>KIrJyi@; z#ZvCLi-i`9->FePn;g4UIT|kJCS$z453sNzY2maG7}a4@+}q_d)@j&Ltj{fHk}F1Z zK5~o3z-JYON7nqq58I%59XtiMV}Tm28!1#@!Q z)TBf_F5qzo2iUzVMtgHzZCUa2=Z_y{UW*LB8X9{3_|e1e?yfE_PEOXlcj2~f*=%ZR zvU=4j9c@ibwK>H!CY4IWLg9#jKm3#1*UMrtnVlW2t>3?WZD^p;>Oa*{Yinz2s^7mY zdHw2T@e2x>oS&PMo&Dhc{nXTygoOCp1Og%K@}-L*=g$QP2Kf8=`TCst``A&h!yX<^ zj<#02cjI^Ac3N)TWNN%YUw^HhPRxOodjM3PINRHL9;oO=BE%IfHKjn!j;9VvxkD`hR>jP8h7h?44vb=1H^=#Nfw1IF^x-g1-`?}mgMiBVPEXEGgxnmO2aT_ zk8wqVMZis?2JYJUE6e4vej)R>C2Esiu~;W^A4HfBMe9^9wc)7R)%l2NB|$YRhj-Up zP3hra>E!|a$Kk2DGyhbXqWxdLB*wkKv%@E>NZ&BMEw=T^q`wSl#+F4{>E3KA?!igt zC)Ka4IMyZ>jRyqQgtQMPAG3aR;Vub0)@)5KYs(Q-@$J7#LxuZgh>@J`+3r{0}andKCp z#^E=4_ZrQ%*^zeG#1aYXg5F#@wY-A5$~e;m(u;pPJ!ha5mvLrId3}3+({@LjxBTr- z2Q=0P=gs#MSUX(0ob^_Eu~kPuaYOnCuQYnfKwLy>lts}Z=;SN%;VfD7dp{I4Z0y&n-m$cddzf+Ieco>FyQ#7Gk7D2F>X{HaVv^ zrbZbNd24vNuJWcmq%TVCPlZ>yX@cU$tK^#on*NXIXSJ{wj*fYmuT>35+p6;^t8w^z z%*}Zjd8FSo$f!!G)*2P=@JPn6eHvr6No3iYk(Q;e8vKx!^KwcB(@(U+1;b7$7W`tX z`AIEh;)S?g*K4Ki`Rb#Yy5INUBU)eUwcm-F$hOb%`#mJweL@SfGua+k#?wCX#*mg; z)Z(gk#C|D>u4{o0d|Vk@6sXq5w+G8xv1cgsndHjYjkasrMlSoGhkIp2ui9?O9m=2n zH4KeJ{0keG&0Di|wt4GBgdH=HWLT$efDptb||2dv| l-8XVwWUHpxfUYtTHJcCdV*lK!7yS$Xz}dmo{ {{else if eq .HookType "msteams"}} + {{else if eq .HookType "feishu"}} + {{end}} @@ -35,6 +37,7 @@ {{template "repo/settings/webhook/dingtalk" .}} {{template "repo/settings/webhook/telegram" .}} {{template "repo/settings/webhook/msteams" .}} + {{template "repo/settings/webhook/feishu" .}} {{template "repo/settings/webhook/history" .}} diff --git a/templates/org/settings/hook_new.tmpl b/templates/org/settings/hook_new.tmpl index c83957dc6a..ce45eed5cd 100644 --- a/templates/org/settings/hook_new.tmpl +++ b/templates/org/settings/hook_new.tmpl @@ -23,6 +23,8 @@ {{else if eq .HookType "msteams"}} + {{else if eq .HookType "feishu"}} + {{end}} @@ -34,6 +36,7 @@ {{template "repo/settings/webhook/dingtalk" .}} {{template "repo/settings/webhook/telegram" .}} {{template "repo/settings/webhook/msteams" .}} + {{template "repo/settings/webhook/feishu" .}} {{template "repo/settings/webhook/history" .}} diff --git a/templates/repo/settings/webhook/feishu.tmpl b/templates/repo/settings/webhook/feishu.tmpl new file mode 100644 index 0000000000..95a5102530 --- /dev/null +++ b/templates/repo/settings/webhook/feishu.tmpl @@ -0,0 +1,11 @@ +{{if eq .HookType "feishu"}} +

{{.i18n.Tr "repo.settings.add_feishu_hook_desc" "https://feishu.cn" | Str2html}}

+
+ {{.CsrfTokenHtml}} +
+ + +
+ {{template "repo/settings/webhook/settings" .}} +
+{{end}} diff --git a/templates/repo/settings/webhook/list.tmpl b/templates/repo/settings/webhook/list.tmpl index 5813b74914..0bdc95fa39 100644 --- a/templates/repo/settings/webhook/list.tmpl +++ b/templates/repo/settings/webhook/list.tmpl @@ -26,6 +26,9 @@ Microsoft Teams + + Feishu + diff --git a/templates/repo/settings/webhook/new.tmpl b/templates/repo/settings/webhook/new.tmpl index 1d5d849738..d02da057f2 100644 --- a/templates/repo/settings/webhook/new.tmpl +++ b/templates/repo/settings/webhook/new.tmpl @@ -21,6 +21,8 @@ {{else if eq .HookType "msteams"}} + {{else if eq .HookType "feishu"}} + {{end}} @@ -32,6 +34,7 @@ {{template "repo/settings/webhook/dingtalk" .}} {{template "repo/settings/webhook/telegram" .}} {{template "repo/settings/webhook/msteams" .}} + {{template "repo/settings/webhook/feishu" .}} {{template "repo/settings/webhook/history" .}} diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index ad2d2cca95..e6a5189928 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -9752,7 +9752,8 @@ "gogs", "msteams", "slack", - "telegram" + "telegram", + "feishu" ], "x-go-name": "Type" }