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 55f58c64a..626489b34 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 a5071de47..932976b6f 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 4a953616f..3e6040a60 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 7ec46254e..f2bc4f16e 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 000000000..6af78494c --- /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 410e47461..2fab0803b 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 5b4d491bd..dbd1a3680 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*ZdoNb*Kb7AieNr!nQzLU?QM+JXd!eTqShRpQGPsUGAI%FN3VeqSj`acO$3dzz>cdQS14^3WrZ>#&6ZRC*5oMbH zq7?-fPQ%c*_YCAN94P9Pf0W^$`)AT>XUp28v;mYO<-?XBix|-H@4ANw zx++1?`Aa2675E5%QMWVTBCBjRV`<{7r8jZ42Mh=hyQYggC)gpy z$TJ^#7NTQR{tXEyymWTg{c*d=eqdCarYXMH1A`52<3kXEM@hK;(}U3U9=ghHBy_-=sbS@j4q#L~`!eaCuh5G3Si;+XpSdv#+so^=9quWo zqjH9P$6Y)yckE83{MiKmR@rE!9i$B(Kd)g;DdvQ2zoxk$uZzID^>}EUlslV zGJ=p4B!F2>)3T@lrXA>|#LBJH8n}p+|;L1=_Jno8t(+C{p zKt?OnRd2>uY*1LgE$EQrVVI+&JkNA?MqhiW+QPXq>Et9+UECJ&*JkZi%a*AAp*(L^ zDw&)Ri^g~y_TT`smqBlBtf?w4c>etHqtt7m!B+zV&mTW}*xA|9!NJbXeD^NG)-9V2 z4GmVWQp~HXtIW!#a10ZR1cDJBclal}ub08->S}LmZvOu5Yh4|UR{N=@x~i(Ovf};Q z!q=}}7QD!zQnNEN($gQ@zn_?x5EB!9n?xc7UA}ZN;QTp%UmtHTFHetCe;+&QcG$(m z&eqaw_io}Y!cNnzn+)|gXlt+4S{89&P+P z;f;wUO+U2N{5}}DnC*n zt{(l;IuoCpdUfoz(N~sBBfSFVZi`()dBtFy$b1lDJQTjHY_SDP*{a4vL@V^GR64x7 z@@hg43r{Zb=|2unjqSOoie;_;dd1Q2`JNpyjOVc;aPfK=ZRudka zw0~0Z%8X@hU{Zg8XHH7`VDK^RM5(~WIGK*phCP%o@+F`0u5{CQ1@%{{H+9s#A9bA7z+c$fW~IJX)}d^x$j(_! zAZFuE_DiWF{f2&e<#Ltguwa`<61wHn2(wK>)8>?<~dHRQ1uqIw;#6}4uoj;3mU-$M*(ey!DdCu}_3I>YPtfMDlw4cy9LyKgaP$&oj@ zw8Z=-N0lSiizywNCPd%IWs&*5DlJ@VFufIdCZ}UMzASR1<(ig}%iibVUNPCNs#|;~ z=TGk&i{8vjayr=ZYTy>vhbiDwh?|b2 zsNX9htLajV$PQW-F##^BIqmN(65kib-IeULSj1j9c$&0e++bjDrt7@Q$)|eNe$=@^ zFCRtcEFk5u>gP% {{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 c83957dc6..ce45eed5c 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 000000000..95a510253 --- /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 5813b7491..0bdc95fa3 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 1d5d84973..d02da057f 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 ad2d2cca9..e6a518992 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" }