From 078e2b2c39f119b9cbe5678ce12f1cc9eb6ddda9 Mon Sep 17 00:00:00 2001 From: lengyuqu Date: Fri, 23 Jul 2021 12:41:27 +0800 Subject: [PATCH] Add support for corporate WeChat webhooks (#15910) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 企业微信webhook * 企业微信webhook * 企业微信webhook * Update templates/admin/hook_new.tmpl Co-authored-by: a1012112796 <1012112796@qq.com> * Update services/webhook/wechatwork.go Co-authored-by: a1012112796 <1012112796@qq.com> * 修善wechatwork * 修善wechatwork * fix * Update locale_cs-CZ.ini fix * fix build * fix * fix build * make webhooks.zh-cn.md * delet unnecessary blank line * delet unnecessary blank line * 企业微信webhook * 企业微信webhook * 企业微信webhook * Update templates/admin/hook_new.tmpl Co-authored-by: a1012112796 <1012112796@qq.com> * Update services/webhook/wechatwork.go Co-authored-by: a1012112796 <1012112796@qq.com> * 修善wechatwork * 修善wechatwork * fix * fix build * fix * fix build * make webhooks.zh-cn.md * delet unnecessary blank line * delet unnecessary blank line * 企业微信webhook * 企业微信webhook * 企业微信webhook * 企业微信webhook * 企业微信webhook * fix * fix * 企业微信webhook * 企业微信webhook * 企业微信webhook * fix wechat * fix wechat * fix wechat * fix wechat * Fix invalid params and typo of email templates (#16394) Signed-off-by: Meano * Add LRU mem cache implementation (#16226) The current default memory cache implementation is unbounded in size and number of objects cached. This is hardly ideal. This PR proposes creating a TwoQueue LRU cache as the underlying cache for Gitea. The cache is limited by the number of objects stored in the cache (rather than size) for simplicity. The default number of objects is 50000 - which is perhaps too small as most of our objects cached are going to be much less than 1kB. It may be worth considering using a different LRU implementation that actively limits sizes or avoids GC - however, this is just a beginning implementation. Signed-off-by: Andrew Thornton * [skip ci] Updated translations via Crowdin * Replace `plugins/docker` with `techknowlogick/drone-docker`in ci (#16407) * plugins/docker -> techknowlogick/drone-docker * It is multi-arch * docs: rewrite email setup (#16404) * Add intro for both the docs page and mailer methods * Fix numbering level in SMTP section * Recommends implicit TLS Signed-off-by: Bagas Sanjaya * Validate Issue Index before querying DB (#16406) * Fix external renderer (#16401) * fix external renderer * use GBackground context as fallback * no fallback, return error Co-authored-by: Lauris BH * Add checkbox to delete pull branch after successful merge (#16049) * Add checkbox to delete pull branch after successful merge * Omit DeleteBranchAfterMerge field in json * Log a warning instead of error when PR head branch deleted * Add DefaultDeleteBranchAfterMerge to PullRequestConfig * Add support for delete_branch_after_merge via API * Fix for API: the branch should be deleted from the HEAD repo If head and base repo are the same, reuse the already opened ctx.Repo.GitRepo * Don't delegate to CleanupBranch, only reuse branch deletion code CleanupBranch contains too much logic that has already been performed by the Merge * Reuse gitrepo in MergePullRequest Co-authored-by: Andrew Thornton * [skip ci] Updated translations via Crowdin * Detect encoding changes while parsing diff (#16330) * Detect encoding changes while parsing diff * Let branch/tag name be a valid ref to get CI status (#16400) * fix #16384# * refactor: move shared helper func to utils package * extend Tests * use ctx.Repo.GitRepo if not nil * fix * fix * 企业微信webhook * 企业微信webhook * 企业微信webhook * fix build * fix build * Apply suggestions from code review Co-authored-by: a1012112796 <1012112796@qq.com> Co-authored-by: myheavily Co-authored-by: zhaoxin Co-authored-by: Meano Co-authored-by: zeripath Co-authored-by: GiteaBot Co-authored-by: 6543 <6543@obermui.de> Co-authored-by: Bagas Sanjaya Co-authored-by: Norwin Co-authored-by: Lauris BH Co-authored-by: Jimmy Praet Co-authored-by: Lunny Xiao --- docs/content/doc/features/webhooks.en-us.md | 1 + docs/content/doc/features/webhooks.zh-cn.md | 13 ++ docs/content/doc/features/webhooks.zh-tw.md | 1 + models/migrations/v161.go | 20 +- models/migrations/v162.go | 20 +- models/webhook.go | 19 +- modules/setting/webhook.go | 2 +- modules/structs/hook.go | 2 +- options/locale/locale_cs-CZ.ini | 1 + options/locale/locale_en-US.ini | 1 + options/locale/locale_zh-CN.ini | 1 + options/locale/locale_zh-TW.ini | 1 + public/img/wechatwork.png | Bin 0 -> 9341 bytes routers/web/repo/webhook.go | 77 +++++++ routers/web/web.go | 5 + services/forms/repo_form.go | 12 ++ services/webhook/webhook.go | 4 + services/webhook/wechatwork.go | 188 ++++++++++++++++++ templates/admin/hook_new.tmpl | 3 + templates/org/settings/hook_new.tmpl | 3 + .../repo/settings/webhook/base_list.tmpl | 3 + templates/repo/settings/webhook/new.tmpl | 3 + .../repo/settings/webhook/wechatwork.tmpl | 11 + templates/swagger/v1_json.tmpl | 3 +- 24 files changed, 364 insertions(+), 30 deletions(-) create mode 100644 public/img/wechatwork.png create mode 100644 services/webhook/wechatwork.go create mode 100644 templates/repo/settings/webhook/wechatwork.tmpl diff --git a/docs/content/doc/features/webhooks.en-us.md b/docs/content/doc/features/webhooks.en-us.md index 5d792f5f57..7a88f2a5e7 100644 --- a/docs/content/doc/features/webhooks.en-us.md +++ b/docs/content/doc/features/webhooks.en-us.md @@ -27,6 +27,7 @@ All event pushes are POST requests. The methods currently supported are: - Telegram - Microsoft Teams - Feishu +- Wechatwork ### Event information diff --git a/docs/content/doc/features/webhooks.zh-cn.md b/docs/content/doc/features/webhooks.zh-cn.md index fe9008717a..f3a312eee2 100644 --- a/docs/content/doc/features/webhooks.zh-cn.md +++ b/docs/content/doc/features/webhooks.zh-cn.md @@ -15,4 +15,17 @@ menu: # Webhooks +Gitea 的存储 webhook。这可以有存储库管路设定页 `/:username/:reponame/settings/hooks` 中的。Webhook 也可以按照组织调整或全系统调整,所有时间的推送都是POST请求 +。此方法目前被下列服务支援: + +- Gitea (也可以是 GET 請求) +- Gogs +- Slack +- Discord +- Dingtalk +- Telegram +- Microsoft Teams +- Feishu +- Wechatwork + ## TBD diff --git a/docs/content/doc/features/webhooks.zh-tw.md b/docs/content/doc/features/webhooks.zh-tw.md index 0d1c564f6d..697b413916 100644 --- a/docs/content/doc/features/webhooks.zh-tw.md +++ b/docs/content/doc/features/webhooks.zh-tw.md @@ -26,6 +26,7 @@ Gitea 的儲存庫事件支援 web hook。這可以有儲存庫管理員在設 - Telegram - Microsoft Teams - Feishu +- Wechatwork ### 事件資訊 diff --git a/models/migrations/v161.go b/models/migrations/v161.go index 4ca9f01218..7ef06b4c26 100644 --- a/models/migrations/v161.go +++ b/models/migrations/v161.go @@ -19,18 +19,20 @@ func convertTaskTypeToString(x *xorm.Engine) error { MSTEAMS FEISHU MATRIX + WECHATWORK ) hookTaskTypes := map[int]string{ - GITEA: "gitea", - GOGS: "gogs", - SLACK: "slack", - DISCORD: "discord", - DINGTALK: "dingtalk", - TELEGRAM: "telegram", - MSTEAMS: "msteams", - FEISHU: "feishu", - MATRIX: "matrix", + GITEA: "gitea", + GOGS: "gogs", + SLACK: "slack", + DISCORD: "discord", + DINGTALK: "dingtalk", + TELEGRAM: "telegram", + MSTEAMS: "msteams", + FEISHU: "feishu", + MATRIX: "matrix", + WECHATWORK: "wechatwork", } type HookTask struct { diff --git a/models/migrations/v162.go b/models/migrations/v162.go index 9dd175cd38..cf2baadbca 100644 --- a/models/migrations/v162.go +++ b/models/migrations/v162.go @@ -19,18 +19,20 @@ func convertWebhookTaskTypeToString(x *xorm.Engine) error { MSTEAMS FEISHU MATRIX + WECHATWORK ) hookTaskTypes := map[int]string{ - GITEA: "gitea", - GOGS: "gogs", - SLACK: "slack", - DISCORD: "discord", - DINGTALK: "dingtalk", - TELEGRAM: "telegram", - MSTEAMS: "msteams", - FEISHU: "feishu", - MATRIX: "matrix", + GITEA: "gitea", + GOGS: "gogs", + SLACK: "slack", + DISCORD: "discord", + DINGTALK: "dingtalk", + TELEGRAM: "telegram", + MSTEAMS: "msteams", + FEISHU: "feishu", + MATRIX: "matrix", + WECHATWORK: "wechatwork", } type Webhook struct { diff --git a/models/webhook.go b/models/webhook.go index 29cfcf6ed4..9c8c0a568b 100644 --- a/models/webhook.go +++ b/models/webhook.go @@ -114,15 +114,16 @@ type HookType = string // Types of webhooks const ( - GITEA HookType = "gitea" - GOGS HookType = "gogs" - SLACK HookType = "slack" - DISCORD HookType = "discord" - DINGTALK HookType = "dingtalk" - TELEGRAM HookType = "telegram" - MSTEAMS HookType = "msteams" - FEISHU HookType = "feishu" - MATRIX HookType = "matrix" + GITEA HookType = "gitea" + GOGS HookType = "gogs" + SLACK HookType = "slack" + DISCORD HookType = "discord" + DINGTALK HookType = "dingtalk" + TELEGRAM HookType = "telegram" + MSTEAMS HookType = "msteams" + FEISHU HookType = "feishu" + MATRIX HookType = "matrix" + WECHATWORK HookType = "wechatwork" ) // HookStatus is the status of a web hook diff --git a/modules/setting/webhook.go b/modules/setting/webhook.go index 4a0c593c8d..8ef54f5cbe 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", "feishu", "matrix"} + Webhook.Types = []string{"gitea", "gogs", "slack", "discord", "dingtalk", "telegram", "msteams", "feishu", "matrix", "wechatwork"} 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 e4ec99df40..ad86cb35a4 100644 --- a/modules/structs/hook.go +++ b/modules/structs/hook.go @@ -42,7 +42,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,feishu + // enum: dingtalk,discord,gitea,gogs,msteams,slack,telegram,feishu,wechatwork Type string `json:"type" binding:"Required"` // required: true Config CreateHookOptionConfig `json:"config" binding:"Required"` diff --git a/options/locale/locale_cs-CZ.ini b/options/locale/locale_cs-CZ.ini index 2ee17ef36d..373ee94575 100644 --- a/options/locale/locale_cs-CZ.ini +++ b/options/locale/locale_cs-CZ.ini @@ -1758,6 +1758,7 @@ settings.add_telegram_hook_desc=Integrovat Telegram do vašeho settings.add_matrix_hook_desc=Integrovat Matrix do vašeho repozitáře. settings.add_msteams_hook_desc=Integrovat Microsoft Teams do vašeho repozitáře. settings.add_feishu_hook_desc=Integrovat Feishu do vašeho repozitáře. +settings.add_wechatwork_hook_desc=Integrovat Wechatwork do vašeho repozitáře. settings.deploy_keys=Klíče pro nasazení settings.add_deploy_key=Přidat klíč pro nasazení settings.deploy_key_desc=Klíče pro nasazení mají k tomuto repozitáři přístup pouze pro čtení. diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 2479bc5bb4..760c026116 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -1848,6 +1848,7 @@ settings.add_telegram_hook_desc = Integrate Telegram into your settings.add_matrix_hook_desc = Integrate Matrix 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.add_Wechat_hook_desc = Integrate Wechatwork 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/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini index 60a2e5c9fa..9e1374ceca 100644 --- a/options/locale/locale_zh-CN.ini +++ b/options/locale/locale_zh-CN.ini @@ -1848,6 +1848,7 @@ settings.add_telegram_hook_desc=将 Telegram 集成到您的仓 settings.add_matrix_hook_desc=将 Matrix 集成到您的仓库中。 settings.add_msteams_hook_desc=将 Microsoft Teams 集成到您的仓库中。 settings.add_feishu_hook_desc=将 Feishu 集成到您的仓库中。 +settings.add_wechatwork_hook_desc=将 Wechatwork 集成到您的仓库中。 settings.deploy_keys=部署密钥 settings.add_deploy_key=添加部署密钥 settings.deploy_key_desc=部署密钥具有对仓库的只读拉取权限。 diff --git a/options/locale/locale_zh-TW.ini b/options/locale/locale_zh-TW.ini index e8c0e3c92b..39f5b38e42 100644 --- a/options/locale/locale_zh-TW.ini +++ b/options/locale/locale_zh-TW.ini @@ -1833,6 +1833,7 @@ settings.add_telegram_hook_desc=將 Telegram 整合到您的儲 settings.add_matrix_hook_desc=將 Matrix 整合到您的儲存庫。 settings.add_msteams_hook_desc=將 Microsoft Teams 整合到您的儲存庫。 settings.add_feishu_hook_desc=將 Feishu 整合到您的儲存庫。 +settings.add_wechatwork_hook_desc=将 Wechatwork 整合到您的儲存庫。 settings.deploy_keys=部署金鑰 settings.add_deploy_key=新增部署金鑰 settings.deploy_key_desc=部署金鑰具有唯讀權限,可拉取此儲存庫。 diff --git a/public/img/wechatwork.png b/public/img/wechatwork.png new file mode 100644 index 0000000000000000000000000000000000000000..296225fb885144f88c2b02073d7ca642877be9ac GIT binary patch literal 9341 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 WN3cE&mV$$WgQKdbsZa+s5BncsGp+*w literal 0 HcmV?d00001 diff --git a/routers/web/repo/webhook.go b/routers/web/repo/webhook.go index e8d86db51d..30fa98f809 100644 --- a/routers/web/repo/webhook.go +++ b/routers/web/repo/webhook.go @@ -638,6 +638,50 @@ func FeishuHooksNewPost(ctx *context.Context) { ctx.Redirect(orCtx.Link) } +// WechatworkHooksNewPost response for creating wechatwork hook +func WechatworkHooksNewPost(ctx *context.Context) { + form := web.GetForm(ctx).(*forms.NewWechatWorkHookForm) + + ctx.Data["Title"] = ctx.Tr("repo.settings") + ctx.Data["PageIsSettingsHooks"] = true + ctx.Data["PageIsSettingsHooksNew"] = true + ctx.Data["Webhook"] = models.Webhook{HookEvent: &models.HookEvent{}} + ctx.Data["HookType"] = models.WECHATWORK + + orCtx, err := getOrgRepoCtx(ctx) + if err != nil { + ctx.ServerError("getOrgRepoCtx", err) + return + } + + if ctx.HasError() { + ctx.HTML(http.StatusOK, orCtx.NewTemplate) + return + } + + w := &models.Webhook{ + RepoID: orCtx.RepoID, + URL: form.PayloadURL, + ContentType: models.ContentTypeJSON, + HookEvent: ParseHookEvent(form.WebhookForm), + IsActive: form.Active, + Type: models.WECHATWORK, + Meta: "", + OrgID: orCtx.OrgID, + IsSystemWebhook: orCtx.IsSystemWebhook, + } + if err := w.UpdateEvent(); err != nil { + ctx.ServerError("UpdateEvent", err) + return + } else if err := models.CreateWebhook(w); err != nil { + ctx.ServerError("CreateWebhook", err) + return + } + + ctx.Flash.Success(ctx.Tr("repo.settings.add_hook_success")) + ctx.Redirect(orCtx.Link) +} + func checkWebhook(ctx *context.Context) (*orgRepoCtx, *models.Webhook) { ctx.Data["RequireHighlightJS"] = true @@ -1062,6 +1106,39 @@ func FeishuHooksEditPost(ctx *context.Context) { ctx.Redirect(fmt.Sprintf("%s/%d", orCtx.Link, w.ID)) } +// WechatworkHooksEditPost response for editing wechatwork hook +func WechatworkHooksEditPost(ctx *context.Context) { + form := web.GetForm(ctx).(*forms.NewWechatWorkHookForm) + ctx.Data["Title"] = ctx.Tr("repo.settings") + ctx.Data["PageIsSettingsHooks"] = true + ctx.Data["PageIsSettingsHooksEdit"] = true + + orCtx, w := checkWebhook(ctx) + if ctx.Written() { + return + } + ctx.Data["Webhook"] = w + + if ctx.HasError() { + ctx.HTML(http.StatusOK, orCtx.NewTemplate) + return + } + + w.URL = form.PayloadURL + w.HookEvent = ParseHookEvent(form.WebhookForm) + w.IsActive = form.Active + if err := w.UpdateEvent(); err != nil { + ctx.ServerError("UpdateEvent", err) + return + } else if err := models.UpdateWebhook(w); err != nil { + ctx.ServerError("UpdateWebhook", err) + return + } + + ctx.Flash.Success(ctx.Tr("repo.settings.update_hook_success")) + ctx.Redirect(fmt.Sprintf("%s/%d", orCtx.Link, w.ID)) +} + // TestWebhook test if web hook is work fine func TestWebhook(ctx *context.Context) { hookID := ctx.ParamsInt64(":id") diff --git a/routers/web/web.go b/routers/web/web.go index 9baef52962..d06a7fc88e 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -425,6 +425,7 @@ func RegisterRoutes(m *web.Route) { m.Post("/matrix/{id}", bindIgnErr(forms.NewMatrixHookForm{}), repo.MatrixHooksEditPost) m.Post("/msteams/{id}", bindIgnErr(forms.NewMSTeamsHookForm{}), repo.MSTeamsHooksEditPost) m.Post("/feishu/{id}", bindIgnErr(forms.NewFeishuHookForm{}), repo.FeishuHooksEditPost) + m.Post("/wechatwork/{id}", bindIgnErr(forms.NewWechatWorkHookForm{}), repo.WechatworkHooksEditPost) }, webhooksEnabled) m.Group("/{configType:default-hooks|system-hooks}", func() { @@ -438,6 +439,8 @@ func RegisterRoutes(m *web.Route) { m.Post("/matrix/new", bindIgnErr(forms.NewMatrixHookForm{}), repo.MatrixHooksNewPost) m.Post("/msteams/new", bindIgnErr(forms.NewMSTeamsHookForm{}), repo.MSTeamsHooksNewPost) m.Post("/feishu/new", bindIgnErr(forms.NewFeishuHookForm{}), repo.FeishuHooksNewPost) + m.Post("/wechatwork/new", bindIgnErr(forms.NewWechatWorkHookForm{}), repo.WechatworkHooksNewPost) + }) m.Group("/auths", func() { @@ -628,6 +631,7 @@ func RegisterRoutes(m *web.Route) { m.Post("/matrix/new", bindIgnErr(forms.NewMatrixHookForm{}), repo.MatrixHooksNewPost) m.Post("/msteams/new", bindIgnErr(forms.NewMSTeamsHookForm{}), repo.MSTeamsHooksNewPost) m.Post("/feishu/new", bindIgnErr(forms.NewFeishuHookForm{}), repo.FeishuHooksNewPost) + m.Post("/wechatwork/new", bindIgnErr(forms.NewWechatWorkHookForm{}), repo.WechatworkHooksNewPost) m.Get("/{id}", repo.WebHooksEdit) m.Post("/{id}/test", repo.TestWebhook) m.Post("/gitea/{id}", bindIgnErr(forms.NewWebhookForm{}), repo.WebHooksEditPost) @@ -639,6 +643,7 @@ func RegisterRoutes(m *web.Route) { m.Post("/matrix/{id}", bindIgnErr(forms.NewMatrixHookForm{}), repo.MatrixHooksEditPost) m.Post("/msteams/{id}", bindIgnErr(forms.NewMSTeamsHookForm{}), repo.MSTeamsHooksEditPost) m.Post("/feishu/{id}", bindIgnErr(forms.NewFeishuHookForm{}), repo.FeishuHooksEditPost) + m.Post("/wechatwork/{id}", bindIgnErr(forms.NewWechatWorkHookForm{}), repo.WechatworkHooksEditPost) }, webhooksEnabled) m.Group("/keys", func() { diff --git a/services/forms/repo_form.go b/services/forms/repo_form.go index 7c79c4dc21..c1c146f234 100644 --- a/services/forms/repo_form.go +++ b/services/forms/repo_form.go @@ -382,6 +382,18 @@ func (f *NewFeishuHookForm) Validate(req *http.Request, errs binding.Errors) bin return middleware.Validate(errs, ctx.Data, f, ctx.Locale) } +// NewWechatWorkHookForm form for creating wechatwork hook +type NewWechatWorkHookForm struct { + PayloadURL string `binding:"Required;ValidUrl"` + WebhookForm +} + +// Validate validates the fields +func (f *NewWechatWorkHookForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { + ctx := context.GetContext(req) + return middleware.Validate(errs, ctx.Data, f, ctx.Locale) +} + // .___ // | | ______ ________ __ ____ // | |/ ___// ___/ | \_/ __ \ diff --git a/services/webhook/webhook.go b/services/webhook/webhook.go index d094a7754b..46002c895c 100644 --- a/services/webhook/webhook.go +++ b/services/webhook/webhook.go @@ -52,6 +52,10 @@ var ( name: models.MATRIX, payloadCreator: GetMatrixPayload, }, + models.WECHATWORK: { + name: models.WECHATWORK, + payloadCreator: GetWechatworkPayload, + }, } ) diff --git a/services/webhook/wechatwork.go b/services/webhook/wechatwork.go new file mode 100644 index 0000000000..1e2d5f4a10 --- /dev/null +++ b/services/webhook/wechatwork.go @@ -0,0 +1,188 @@ +// Copyright 2021 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 ( + // WechatworkPayload represents + WechatworkPayload struct { + Msgtype string `json:"msgtype"` + Text struct { + Content string `json:"content"` + MentionedList []string `json:"mentioned_list"` + MentionedMobileList []string `json:"mentioned_mobile_list"` + } `json:"text"` + Markdown struct { + Content string `json:"content"` + } `json:"markdown"` + } +) + +// SetSecret sets the Wechatwork secret +func (f *WechatworkPayload) SetSecret(_ string) {} + +// JSONPayload Marshals the WechatworkPayload to json +func (f *WechatworkPayload) JSONPayload() ([]byte, error) { + data, err := json.MarshalIndent(f, "", " ") + if err != nil { + return []byte{}, err + } + return data, nil +} + +func newWechatworkMarkdownPayload(title string) *WechatworkPayload { + return &WechatworkPayload{ + Msgtype: "markdown", + Markdown: struct { + Content string `json:"content"` + }{ + Content: title, + }, + } +} + +var ( + _ PayloadConvertor = &WechatworkPayload{} +) + +// Create implements PayloadConvertor Create method +func (f *WechatworkPayload) Create(p *api.CreatePayload) (api.Payloader, error) { + // created tag/branch + refName := git.RefEndName(p.Ref) + title := fmt.Sprintf("[%s] %s %s created", p.Repo.FullName, p.RefType, refName) + + return newWechatworkMarkdownPayload(title), nil +} + +// Delete implements PayloadConvertor Delete method +func (f *WechatworkPayload) Delete(p *api.DeletePayload) (api.Payloader, error) { + // created tag/branch + refName := git.RefEndName(p.Ref) + title := fmt.Sprintf("[%s] %s %s deleted", p.Repo.FullName, p.RefType, refName) + + return newWechatworkMarkdownPayload(title), nil + +} + +// Fork implements PayloadConvertor Fork method +func (f *WechatworkPayload) Fork(p *api.ForkPayload) (api.Payloader, error) { + + title := fmt.Sprintf("%s is forked to %s", p.Forkee.FullName, p.Repo.FullName) + + return newWechatworkMarkdownPayload(title), nil +} + +// Push implements PayloadConvertor Push method +func (f *WechatworkPayload) Push(p *api.PushPayload) (api.Payloader, 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 = "Author:" + commit.Author.Name + } + + message := strings.ReplaceAll(commit.Message, "\n\n", "\r\n") + text += fmt.Sprintf(" > [%s](%s) \r\n >%s \n >%s", commit.ID[:7], commit.URL, + message, authorName) + + // add linebreak to each commit but the last + if i < len(p.Commits)-1 { + text += "\n" + } + } + return newWechatworkMarkdownPayload(title + "\r\n\r\n" + text), nil + +} + +// Issue implements PayloadConvertor Issue method +func (f *WechatworkPayload) Issue(p *api.IssuePayload) (api.Payloader, error) { + text, issueTitle, attachmentText, _ := getIssuesPayloadInfo(p, noneLinkFormatter, true) + var content string + content += fmt.Sprintf(" >%s\n >%s \n > %s", text, attachmentText, issueTitle) + + return newWechatworkMarkdownPayload(content), nil + +} + +// IssueComment implements PayloadConvertor IssueComment method +func (f *WechatworkPayload) IssueComment(p *api.IssueCommentPayload) (api.Payloader, error) { + text, issueTitle, _ := getIssueCommentPayloadInfo(p, noneLinkFormatter, true) + var content string + content += fmt.Sprintf(" >%s\n >%s \n >%s", text, p.Comment.Body, issueTitle) + + return newWechatworkMarkdownPayload(content), nil + +} + +// PullRequest implements PayloadConvertor PullRequest method +func (f *WechatworkPayload) PullRequest(p *api.PullRequestPayload) (api.Payloader, error) { + text, issueTitle, attachmentText, _ := getPullRequestPayloadInfo(p, noneLinkFormatter, true) + pr := fmt.Sprintf("> %s \r\n > %s \r\n > %s \r\n", + text, issueTitle, attachmentText) + + return newWechatworkMarkdownPayload(pr), nil + +} + +// Review implements PayloadConvertor Review method +func (f *WechatworkPayload) Review(p *api.PullRequestPayload, event models.HookEventType) (api.Payloader, 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 newWechatworkMarkdownPayload("# " + title + "\r\n\r\n >" + text), nil + +} + +// Repository implements PayloadConvertor Repository method +func (f *WechatworkPayload) Repository(p *api.RepositoryPayload) (api.Payloader, error) { + var title string + switch p.Action { + case api.HookRepoCreated: + title = fmt.Sprintf("[%s] Repository created", p.Repository.FullName) + return newWechatworkMarkdownPayload(title), nil + case api.HookRepoDeleted: + title = fmt.Sprintf("[%s] Repository deleted", p.Repository.FullName) + return newWechatworkMarkdownPayload(title), nil + } + + return nil, nil +} + +// Release implements PayloadConvertor Release method +func (f *WechatworkPayload) Release(p *api.ReleasePayload) (api.Payloader, error) { + text, _ := getReleasePayloadInfo(p, noneLinkFormatter, true) + + return newWechatworkMarkdownPayload(text), nil +} + +// GetWechatworkPayload GetWechatworkPayload converts a ding talk webhook into a WechatworkPayload +func GetWechatworkPayload(p api.Payloader, event models.HookEventType, meta string) (api.Payloader, error) { + return convertPayloader(new(WechatworkPayload), p, event) +} diff --git a/templates/admin/hook_new.tmpl b/templates/admin/hook_new.tmpl index 01e9e429a6..4710498b20 100644 --- a/templates/admin/hook_new.tmpl +++ b/templates/admin/hook_new.tmpl @@ -32,6 +32,8 @@ {{else if eq .HookType "matrix"}} + {{else if eq .HookType "wechatwork"}} + {{end}} @@ -45,6 +47,7 @@ {{template "repo/settings/webhook/msteams" .}} {{template "repo/settings/webhook/feishu" .}} {{template "repo/settings/webhook/matrix" .}} + {{template "repo/settings/webhook/wechatwork" .}} {{template "repo/settings/webhook/history" .}} diff --git a/templates/org/settings/hook_new.tmpl b/templates/org/settings/hook_new.tmpl index d070b8bc4b..43351d0ceb 100644 --- a/templates/org/settings/hook_new.tmpl +++ b/templates/org/settings/hook_new.tmpl @@ -27,6 +27,8 @@ {{else if eq .HookType "matrix"}} + {{else if eq .HookType "wechatwork"}} + {{end}} @@ -40,6 +42,7 @@ {{template "repo/settings/webhook/msteams" .}} {{template "repo/settings/webhook/feishu" .}} {{template "repo/settings/webhook/matrix" .}} + {{template "repo/settings/webhook/wechatwork" .}} {{template "repo/settings/webhook/history" .}} diff --git a/templates/repo/settings/webhook/base_list.tmpl b/templates/repo/settings/webhook/base_list.tmpl index e77e747742..e96d086039 100644 --- a/templates/repo/settings/webhook/base_list.tmpl +++ b/templates/repo/settings/webhook/base_list.tmpl @@ -31,6 +31,9 @@ Matrix + + Wechatwork + diff --git a/templates/repo/settings/webhook/new.tmpl b/templates/repo/settings/webhook/new.tmpl index 7c2e5b1a90..6df128f40a 100644 --- a/templates/repo/settings/webhook/new.tmpl +++ b/templates/repo/settings/webhook/new.tmpl @@ -25,6 +25,8 @@ {{else if eq .HookType "matrix"}} + {{else if eq .HookType "wechatwork"}} + {{end}} @@ -38,6 +40,7 @@ {{template "repo/settings/webhook/msteams" .}} {{template "repo/settings/webhook/feishu" .}} {{template "repo/settings/webhook/matrix" .}} + {{template "repo/settings/webhook/wechatwork" .}} {{template "repo/settings/webhook/history" .}} diff --git a/templates/repo/settings/webhook/wechatwork.tmpl b/templates/repo/settings/webhook/wechatwork.tmpl new file mode 100644 index 0000000000..503fab205f --- /dev/null +++ b/templates/repo/settings/webhook/wechatwork.tmpl @@ -0,0 +1,11 @@ +{{if eq .HookType "wechatwork"}} +

{{.i18n.Tr "repo.settings.add_wechatwork_hook_desc" "https://work.weixin.qq.com" | Str2html}}

+
+ {{.CsrfTokenHtml}} +
+ + +
+ {{template "repo/settings/webhook/settings" .}} +
+{{end}} diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index eeb002cb9a..238265fd7e 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -12917,7 +12917,8 @@ "msteams", "slack", "telegram", - "feishu" + "feishu", + "wechatwork" ], "x-go-name": "Type" }