aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--models/webhook.go17
-rw-r--r--models/webhook_dingtalk.go197
-rw-r--r--modules/auth/repo_form.go11
-rw-r--r--modules/setting/setting.go2
-rw-r--r--options/locale/locale_en-US.ini1
-rw-r--r--public/img/dingtalk.icobin0 -> 7886 bytes
-rw-r--r--routers/repo/webhook.go79
-rw-r--r--routers/routes/routes.go4
-rw-r--r--templates/org/settings/hook_new.tmpl3
-rw-r--r--templates/repo/settings/hook_dingtalk.tmpl11
-rw-r--r--templates/repo/settings/hook_list.tmpl3
-rw-r--r--templates/repo/settings/hook_new.tmpl3
-rw-r--r--vendor/github.com/lunny/dingtalk_webhook/LICENSE20
-rw-r--r--vendor/github.com/lunny/dingtalk_webhook/README.md18
-rw-r--r--vendor/github.com/lunny/dingtalk_webhook/webhook.go361
-rw-r--r--vendor/vendor.json6
16 files changed, 725 insertions, 11 deletions
diff --git a/models/webhook.go b/models/webhook.go
index 508fea9977..1b601b4e62 100644
--- a/models/webhook.go
+++ b/models/webhook.go
@@ -332,13 +332,15 @@ const (
SLACK
GITEA
DISCORD
+ DINGTALK
)
var hookTaskTypes = map[string]HookTaskType{
- "gitea": GITEA,
- "gogs": GOGS,
- "slack": SLACK,
- "discord": DISCORD,
+ "gitea": GITEA,
+ "gogs": GOGS,
+ "slack": SLACK,
+ "discord": DISCORD,
+ "dingtalk": DINGTALK,
}
// ToHookTaskType returns HookTaskType by given name.
@@ -357,6 +359,8 @@ func (t HookTaskType) Name() string {
return "slack"
case DISCORD:
return "discord"
+ case DINGTALK:
+ return "dingtalk"
}
return ""
}
@@ -520,6 +524,11 @@ func prepareWebhook(e Engine, w *Webhook, repo *Repository, event HookEventType,
if err != nil {
return fmt.Errorf("GetDiscordPayload: %v", err)
}
+ case DINGTALK:
+ payloader, err = GetDingtalkPayload(p, event, w.Meta)
+ if err != nil {
+ return fmt.Errorf("GetDingtalkPayload: %v", err)
+ }
default:
p.SetSecret(w.Secret)
payloader = p
diff --git a/models/webhook_dingtalk.go b/models/webhook_dingtalk.go
new file mode 100644
index 0000000000..e25e989084
--- /dev/null
+++ b/models/webhook_dingtalk.go
@@ -0,0 +1,197 @@
+// Copyright 2017 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 models
+
+import (
+ "encoding/json"
+ "fmt"
+ "strings"
+
+ "code.gitea.io/git"
+ api "code.gitea.io/sdk/gitea"
+
+ dingtalk "github.com/lunny/dingtalk_webhook"
+)
+
+type (
+ // DingtalkPayload represents
+ DingtalkPayload dingtalk.Payload
+)
+
+// SetSecret sets the dingtalk secret
+func (p *DingtalkPayload) SetSecret(_ string) {}
+
+// JSONPayload Marshals the DingtalkPayload to json
+func (p *DingtalkPayload) JSONPayload() ([]byte, error) {
+ data, err := json.MarshalIndent(p, "", " ")
+ if err != nil {
+ return []byte{}, err
+ }
+ return data, nil
+}
+
+func getDingtalkCreatePayload(p *api.CreatePayload) (*DingtalkPayload, error) {
+ // created tag/branch
+ refName := git.RefEndName(p.Ref)
+ title := fmt.Sprintf("[%s] %s %s created", p.Repo.FullName, p.RefType, refName)
+
+ return &DingtalkPayload{
+ MsgType: "actionCard",
+ ActionCard: dingtalk.ActionCard{
+ Text: title,
+ Title: title,
+ HideAvatar: "0",
+ SingleTitle: fmt.Sprintf("view branch %s", refName),
+ SingleURL: p.Repo.HTMLURL + "/src/" + refName,
+ },
+ }, nil
+}
+
+func getDingtalkPushPayload(p *api.PushPayload) (*DingtalkPayload, error) {
+ var (
+ branchName = git.RefEndName(p.Ref)
+ commitDesc string
+ )
+
+ var titleLink, linkText string
+ if len(p.Commits) == 1 {
+ commitDesc = "1 new commit"
+ titleLink = p.Commits[0].URL
+ linkText = fmt.Sprintf("view commit %s", p.Commits[0].ID[:7])
+ } else {
+ commitDesc = fmt.Sprintf("%d new commits", len(p.Commits))
+ titleLink = p.CompareURL
+ linkText = fmt.Sprintf("view commit %s...%s", p.Commits[0].ID[:7], p.Commits[len(p.Commits)-1].ID[:7])
+ }
+ if titleLink == "" {
+ titleLink = p.Repo.HTMLURL + "/src/" + branchName
+ }
+
+ 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 &DingtalkPayload{
+ MsgType: "actionCard",
+ ActionCard: dingtalk.ActionCard{
+ Text: text,
+ Title: title,
+ HideAvatar: "0",
+ SingleTitle: linkText,
+ SingleURL: titleLink,
+ },
+ }, nil
+}
+
+func getDingtalkPullRequestPayload(p *api.PullRequestPayload) (*DingtalkPayload, error) {
+ var text, title string
+ switch p.Action {
+ case api.HookIssueOpened:
+ title = fmt.Sprintf("[%s] Pull request opened: #%d %s", p.Repository.FullName, p.Index, p.PullRequest.Title)
+ text = p.PullRequest.Body
+ case api.HookIssueClosed:
+ if p.PullRequest.HasMerged {
+ title = fmt.Sprintf("[%s] Pull request merged: #%d %s", p.Repository.FullName, p.Index, p.PullRequest.Title)
+ } else {
+ title = fmt.Sprintf("[%s] Pull request closed: #%d %s", p.Repository.FullName, p.Index, p.PullRequest.Title)
+ }
+ text = p.PullRequest.Body
+ case api.HookIssueReOpened:
+ title = fmt.Sprintf("[%s] Pull request re-opened: #%d %s", p.Repository.FullName, p.Index, p.PullRequest.Title)
+ text = p.PullRequest.Body
+ case api.HookIssueEdited:
+ title = fmt.Sprintf("[%s] Pull request edited: #%d %s", p.Repository.FullName, p.Index, p.PullRequest.Title)
+ text = p.PullRequest.Body
+ case api.HookIssueAssigned:
+ title = fmt.Sprintf("[%s] Pull request assigned to %s: #%d %s", p.Repository.FullName,
+ p.PullRequest.Assignee.UserName, p.Index, p.PullRequest.Title)
+ text = p.PullRequest.Body
+ case api.HookIssueUnassigned:
+ title = fmt.Sprintf("[%s] Pull request unassigned: #%d %s", p.Repository.FullName, p.Index, p.PullRequest.Title)
+ text = p.PullRequest.Body
+ case api.HookIssueLabelUpdated:
+ title = fmt.Sprintf("[%s] Pull request labels updated: #%d %s", p.Repository.FullName, p.Index, p.PullRequest.Title)
+ text = p.PullRequest.Body
+ case api.HookIssueLabelCleared:
+ title = fmt.Sprintf("[%s] Pull request labels cleared: #%d %s", p.Repository.FullName, p.Index, p.PullRequest.Title)
+ text = p.PullRequest.Body
+ case api.HookIssueSynchronized:
+ title = fmt.Sprintf("[%s] Pull request synchronized: #%d %s", p.Repository.FullName, p.Index, p.PullRequest.Title)
+ text = p.PullRequest.Body
+ }
+
+ return &DingtalkPayload{
+ MsgType: "actionCard",
+ ActionCard: dingtalk.ActionCard{
+ Text: text,
+ Title: title,
+ HideAvatar: "0",
+ SingleTitle: "view pull request",
+ SingleURL: p.PullRequest.HTMLURL,
+ },
+ }, nil
+}
+
+func getDingtalkRepositoryPayload(p *api.RepositoryPayload) (*DingtalkPayload, error) {
+ var title, url string
+ switch p.Action {
+ case api.HookRepoCreated:
+ title = fmt.Sprintf("[%s] Repository created", p.Repository.FullName)
+ url = p.Repository.HTMLURL
+ return &DingtalkPayload{
+ MsgType: "actionCard",
+ ActionCard: dingtalk.ActionCard{
+ Text: title,
+ Title: title,
+ HideAvatar: "0",
+ SingleTitle: "view repository",
+ SingleURL: url,
+ },
+ }, nil
+ case api.HookRepoDeleted:
+ title = fmt.Sprintf("[%s] Repository deleted", p.Repository.FullName)
+ return &DingtalkPayload{
+ MsgType: "text",
+ Text: struct {
+ Content string `json:"content"`
+ }{
+ Content: title,
+ },
+ }, nil
+ }
+
+ return nil, nil
+}
+
+// GetDingtalkPayload converts a ding talk webhook into a DingtalkPayload
+func GetDingtalkPayload(p api.Payloader, event HookEventType, meta string) (*DingtalkPayload, error) {
+ s := new(DingtalkPayload)
+
+ switch event {
+ case HookEventCreate:
+ return getDingtalkCreatePayload(p.(*api.CreatePayload))
+ case HookEventPush:
+ return getDingtalkPushPayload(p.(*api.PushPayload))
+ case HookEventPullRequest:
+ return getDingtalkPullRequestPayload(p.(*api.PullRequestPayload))
+ case HookEventRepository:
+ return getDingtalkRepositoryPayload(p.(*api.RepositoryPayload))
+ }
+
+ return s, nil
+}
diff --git a/modules/auth/repo_form.go b/modules/auth/repo_form.go
index 6fe826f574..bb917a9114 100644
--- a/modules/auth/repo_form.go
+++ b/modules/auth/repo_form.go
@@ -222,6 +222,17 @@ func (f *NewDiscordHookForm) Validate(ctx *macaron.Context, errs binding.Errors)
return validate(errs, ctx.Data, f, ctx.Locale)
}
+// NewDingtalkHookForm form for creating dingtalk hook
+type NewDingtalkHookForm struct {
+ PayloadURL string `binding:"Required;ValidUrl"`
+ WebhookForm
+}
+
+// Validate validates the fields
+func (f *NewDingtalkHookForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
+ return validate(errs, ctx.Data, f, ctx.Locale)
+}
+
// .___
// | | ______ ________ __ ____
// | |/ ___// ___/ | \_/ __ \
diff --git a/modules/setting/setting.go b/modules/setting/setting.go
index d15a76f9bf..d4f92dee39 100644
--- a/modules/setting/setting.go
+++ b/modules/setting/setting.go
@@ -1509,7 +1509,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"}
+ Webhook.Types = []string{"gitea", "gogs", "slack", "discord", "dingtalk"}
Webhook.PagingNum = sec.Key("PAGING_NUM").MustInt(10)
}
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index 834ec49e53..4410b63cd0 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -978,6 +978,7 @@ settings.slack_token = Token
settings.slack_domain = Domain
settings.slack_channel = Channel
settings.add_discord_hook_desc = Add <a href="%s">Discord</a> integration to your repository.
+settings.add_dingtalk_hook_desc = Add <a href="%s">Dingtalk</a> integration to your repository.
settings.deploy_keys = Deploy Keys
settings.add_deploy_key = Add Deploy Key
settings.deploy_key_desc = Deploy keys have read-only access. They are not the same as personal account SSH keys.
diff --git a/public/img/dingtalk.ico b/public/img/dingtalk.ico
new file mode 100644
index 0000000000..a2682bbbc4
--- /dev/null
+++ b/public/img/dingtalk.ico
Binary files differ
diff --git a/routers/repo/webhook.go b/routers/repo/webhook.go
index 81f79582de..1bff139528 100644
--- a/routers/repo/webhook.go
+++ b/routers/repo/webhook.go
@@ -269,6 +269,46 @@ func DiscordHooksNewPost(ctx *context.Context, form auth.NewDiscordHookForm) {
ctx.Redirect(orCtx.Link + "/settings/hooks")
}
+// DingtalkHooksNewPost response for creating dingtalk hook
+func DingtalkHooksNewPost(ctx *context.Context, form auth.NewDingtalkHookForm) {
+ ctx.Data["Title"] = ctx.Tr("repo.settings")
+ ctx.Data["PageIsSettingsHooks"] = true
+ ctx.Data["PageIsSettingsHooksNew"] = true
+ ctx.Data["Webhook"] = models.Webhook{HookEvent: &models.HookEvent{}}
+
+ orCtx, err := getOrgRepoCtx(ctx)
+ if err != nil {
+ ctx.Handle(500, "getOrgRepoCtx", err)
+ return
+ }
+
+ if ctx.HasError() {
+ ctx.HTML(200, orCtx.NewTemplate)
+ return
+ }
+
+ w := &models.Webhook{
+ RepoID: orCtx.RepoID,
+ URL: form.PayloadURL,
+ ContentType: models.ContentTypeJSON,
+ HookEvent: ParseHookEvent(form.WebhookForm),
+ IsActive: form.Active,
+ HookTaskType: models.DINGTALK,
+ Meta: "",
+ OrgID: orCtx.OrgID,
+ }
+ if err := w.UpdateEvent(); err != nil {
+ ctx.Handle(500, "UpdateEvent", err)
+ return
+ } else if err := models.CreateWebhook(w); err != nil {
+ ctx.Handle(500, "CreateWebhook", err)
+ return
+ }
+
+ ctx.Flash.Success(ctx.Tr("repo.settings.add_hook_success"))
+ ctx.Redirect(orCtx.Link + "/settings/hooks")
+}
+
// SlackHooksNewPost response for creating slack hook
func SlackHooksNewPost(ctx *context.Context, form auth.NewSlackHookForm) {
ctx.Data["Title"] = ctx.Tr("repo.settings")
@@ -345,17 +385,12 @@ func checkWebhook(ctx *context.Context) (*orgRepoCtx, *models.Webhook) {
return nil, nil
}
+ ctx.Data["HookType"] = w.HookTaskType.Name()
switch w.HookTaskType {
case models.SLACK:
ctx.Data["SlackHook"] = w.GetSlackHook()
- ctx.Data["HookType"] = "slack"
- case models.GOGS:
- ctx.Data["HookType"] = "gogs"
case models.DISCORD:
ctx.Data["DiscordHook"] = w.GetDiscordHook()
- ctx.Data["HookType"] = "discord"
- default:
- ctx.Data["HookType"] = "gitea"
}
ctx.Data["History"], err = w.History(1)
@@ -544,6 +579,38 @@ func DiscordHooksEditPost(ctx *context.Context, form auth.NewDiscordHookForm) {
ctx.Redirect(fmt.Sprintf("%s/settings/hooks/%d", orCtx.Link, w.ID))
}
+// DingtalkHooksEditPost response for editing discord hook
+func DingtalkHooksEditPost(ctx *context.Context, form auth.NewDingtalkHookForm) {
+ 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(200, orCtx.NewTemplate)
+ return
+ }
+
+ w.URL = form.PayloadURL
+ w.HookEvent = ParseHookEvent(form.WebhookForm)
+ w.IsActive = form.Active
+ if err := w.UpdateEvent(); err != nil {
+ ctx.Handle(500, "UpdateEvent", err)
+ return
+ } else if err := models.UpdateWebhook(w); err != nil {
+ ctx.Handle(500, "UpdateWebhook", err)
+ return
+ }
+
+ ctx.Flash.Success(ctx.Tr("repo.settings.update_hook_success"))
+ ctx.Redirect(fmt.Sprintf("%s/settings/hooks/%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/routes/routes.go b/routers/routes/routes.go
index 5a76dddb66..ece2565683 100644
--- a/routers/routes/routes.go
+++ b/routers/routes/routes.go
@@ -396,11 +396,13 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Post("/gogs/new", bindIgnErr(auth.NewGogshookForm{}), repo.GogsHooksNewPost)
m.Post("/slack/new", bindIgnErr(auth.NewSlackHookForm{}), repo.SlackHooksNewPost)
m.Post("/discord/new", bindIgnErr(auth.NewDiscordHookForm{}), repo.DiscordHooksNewPost)
+ m.Post("/dingtalk/new", bindIgnErr(auth.NewDingtalkHookForm{}), repo.DingtalkHooksNewPost)
m.Get("/:id", repo.WebHooksEdit)
m.Post("/gitea/:id", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksEditPost)
m.Post("/gogs/:id", bindIgnErr(auth.NewGogshookForm{}), repo.GogsHooksEditPost)
m.Post("/slack/:id", bindIgnErr(auth.NewSlackHookForm{}), repo.SlackHooksEditPost)
m.Post("/discord/:id", bindIgnErr(auth.NewDiscordHookForm{}), repo.DiscordHooksEditPost)
+ m.Post("/dingtalk/:id", bindIgnErr(auth.NewDingtalkHookForm{}), repo.DingtalkHooksEditPost)
})
m.Route("/delete", "GET,POST", org.SettingsDelete)
@@ -444,12 +446,14 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Post("/gogs/new", bindIgnErr(auth.NewGogshookForm{}), repo.GogsHooksNewPost)
m.Post("/slack/new", bindIgnErr(auth.NewSlackHookForm{}), repo.SlackHooksNewPost)
m.Post("/discord/new", bindIgnErr(auth.NewDiscordHookForm{}), repo.DiscordHooksNewPost)
+ m.Post("/dingtalk/new", bindIgnErr(auth.NewDingtalkHookForm{}), repo.DingtalkHooksNewPost)
m.Get("/:id", repo.WebHooksEdit)
m.Post("/:id/test", repo.TestWebhook)
m.Post("/gitea/:id", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksEditPost)
m.Post("/gogs/:id", bindIgnErr(auth.NewGogshookForm{}), repo.GogsHooksNewPost)
m.Post("/slack/:id", bindIgnErr(auth.NewSlackHookForm{}), repo.SlackHooksEditPost)
m.Post("/discord/:id", bindIgnErr(auth.NewDiscordHookForm{}), repo.DiscordHooksEditPost)
+ m.Post("/dingtalk/:id", bindIgnErr(auth.NewDingtalkHookForm{}), repo.DingtalkHooksEditPost)
m.Group("/git", func() {
m.Get("", repo.GitHooks)
diff --git a/templates/org/settings/hook_new.tmpl b/templates/org/settings/hook_new.tmpl
index 15c6943185..bedb2dc142 100644
--- a/templates/org/settings/hook_new.tmpl
+++ b/templates/org/settings/hook_new.tmpl
@@ -17,6 +17,8 @@
<img class="img-13" src="{{AppSubUrl}}/img/slack.png">
{{else if eq .HookType "discord"}}
<img class="img-13" src="{{AppSubUrl}}/img/discord.png">
+ {{else if eq .HookType "dingtalk"}}
+ <img class="img-13" src="{{AppSubUrl}}/img/dingtalk.png">
{{end}}
</div>
</h4>
@@ -25,6 +27,7 @@
{{template "repo/settings/hook_gogs" .}}
{{template "repo/settings/hook_slack" .}}
{{template "repo/settings/hook_discord" .}}
+ {{template "repo/settings/hook_dingtalk" .}}
</div>
{{template "repo/settings/hook_history" .}}
diff --git a/templates/repo/settings/hook_dingtalk.tmpl b/templates/repo/settings/hook_dingtalk.tmpl
new file mode 100644
index 0000000000..37271a7db5
--- /dev/null
+++ b/templates/repo/settings/hook_dingtalk.tmpl
@@ -0,0 +1,11 @@
+{{if eq .HookType "dingtalk"}}
+ <p>{{.i18n.Tr "repo.settings.add_dingtalk_hook_desc" "https://dingtalk.com" | Str2html}}</p>
+ <form class="ui form" action="{{.BaseLink}}/settings/hooks/dingtalk/{{if .PageIsSettingsHooksNew}}new{{else}}{{.Webhook.ID}}{{end}}" method="post">
+ {{.CsrfTokenHtml}}
+ <div class="required field {{if .Err_PayloadURL}}error{{end}}">
+ <label for="payload_url">{{.i18n.Tr "repo.settings.payload_url"}}</label>
+ <input id="payload_url" name="payload_url" type="url" value="{{.Webhook.URL}}" autofocus required>
+ </div>
+ {{template "repo/settings/hook_settings" .}}
+ </form>
+{{end}}
diff --git a/templates/repo/settings/hook_list.tmpl b/templates/repo/settings/hook_list.tmpl
index dce3439096..4e61ba7a07 100644
--- a/templates/repo/settings/hook_list.tmpl
+++ b/templates/repo/settings/hook_list.tmpl
@@ -17,6 +17,9 @@
<a class="item" href="{{.BaseLink}}/settings/hooks/discord/new">
<img class="img-10" src="{{AppSubUrl}}/img/discord.png">Discord
</a>
+ <a class="item" href="{{.BaseLink}}/settings/hooks/dingtalk/new">
+ <img class="img-10" src="{{AppSubUrl}}/img/dingtalk.ico">Dingtalk
+ </a>
</div>
</div>
</div>
diff --git a/templates/repo/settings/hook_new.tmpl b/templates/repo/settings/hook_new.tmpl
index a40eb9e428..7e3cf3c8cf 100644
--- a/templates/repo/settings/hook_new.tmpl
+++ b/templates/repo/settings/hook_new.tmpl
@@ -15,6 +15,8 @@
<img class="img-13" src="{{AppSubUrl}}/img/slack.png">
{{else if eq .HookType "discord"}}
<img class="img-13" src="{{AppSubUrl}}/img/discord.png">
+ {{else if eq .HookType "dingtalk"}}
+ <img class="img-13" src="{{AppSubUrl}}/img/dingtalk.ico">
{{end}}
</div>
</h4>
@@ -23,6 +25,7 @@
{{template "repo/settings/hook_gogs" .}}
{{template "repo/settings/hook_slack" .}}
{{template "repo/settings/hook_discord" .}}
+ {{template "repo/settings/hook_dingtalk" .}}
</div>
{{template "repo/settings/hook_history" .}}
diff --git a/vendor/github.com/lunny/dingtalk_webhook/LICENSE b/vendor/github.com/lunny/dingtalk_webhook/LICENSE
new file mode 100644
index 0000000000..a8d4b49dd0
--- /dev/null
+++ b/vendor/github.com/lunny/dingtalk_webhook/LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2016 The Gitea Authors
+Copyright (c) 2015 The Gogs Authors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/github.com/lunny/dingtalk_webhook/README.md b/vendor/github.com/lunny/dingtalk_webhook/README.md
new file mode 100644
index 0000000000..5625d36e23
--- /dev/null
+++ b/vendor/github.com/lunny/dingtalk_webhook/README.md
@@ -0,0 +1,18 @@
+# 非官方 Dingtalk webhook Golang SDK
+
+## 此工程仅封装了 Dingtalk 的 webhook 部分的请求
+
+## 使用
+
+首先在dingtalk中创建一个机器人,将accessToken拷贝出来,然后执行下面方法即可
+
+```Go
+webhook := dingtalk.Webhook(accessToken)
+webhook.SendTextMsg("这是一个没有AT的文本消息", false)
+```
+
+## License
+
+This project is licensed under the MIT License.
+See the [LICENSE](https://github.com/lunny/webhook_dingtalk/blob/master/LICENSE) file
+for the full license text. \ No newline at end of file
diff --git a/vendor/github.com/lunny/dingtalk_webhook/webhook.go b/vendor/github.com/lunny/dingtalk_webhook/webhook.go
new file mode 100644
index 0000000000..5d8cbf6c08
--- /dev/null
+++ b/vendor/github.com/lunny/dingtalk_webhook/webhook.go
@@ -0,0 +1,361 @@
+// Copyright 2017 Lunny Xiao. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package dingtalk
+
+import (
+ "bytes"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io/ioutil"
+ "net/http"
+)
+
+/*
+{
+ "msgtype": "text",
+ "text": {
+ "content": "我就是我, 是不一样的烟火"
+ },
+ "at": {
+ "atMobiles": [
+ "156xxxx8827",
+ "189xxxx8325"
+ ],
+ "isAtAll": false
+ }
+}
+
+{
+ "msgtype": "link",
+ "link": {
+ "text": "这个即将发布的新版本,创始人陈航(花名“无招”)称它为“红树林”。
+而在此之前,每当面临重大升级,产品经理们都会取一个应景的代号,这一次,为什么是“红树林”?",
+ "title": "时代的火车向前开",
+ "picUrl": "",
+ "messageUrl": "https://mp.weixin.qq.com/s?__biz=MzA4NjMwMTA2Ng==&mid=2650316842&idx=1&sn=60da3ea2b29f1dcc43a7c8e4a7c97a16&scene=2&srcid=09189AnRJEdIiWVaKltFzNTw&from=timeline&isappinstalled=0&key=&ascene=2&uin=&devicetype=android-23&version=26031933&nettype=WIFI"
+ }
+}
+
+{
+ "msgtype": "markdown",
+ "markdown": {
+ "title":"杭州天气",
+ "text": "#### 杭州天气 @156xxxx8827\n" +
+ "> 9度,西北风1级,空气良89,相对温度73%\n\n" +
+ "> ![screenshot](http://image.jpg)\n" +
+ "> ###### 10点20分发布 [天气](http://www.thinkpage.cn/) \n"
+ },
+ "at": {
+ "atMobiles": [
+ "156xxxx8827",
+ "189xxxx8325"
+ ],
+ "isAtAll": false
+ }
+}
+
+{
+ "actionCard": {
+ "title": "乔布斯 20 年前想打造一间苹果咖啡厅,而它正是 Apple Store 的前身",
+ "text": "![screenshot](@lADOpwk3K80C0M0FoA)
+ ### 乔布斯 20 年前想打造的苹果咖啡厅
+ Apple Store 的设计正从原来满满的科技感走向生活化,而其生活化的走向其实可以追溯到 20 年前苹果一个建立咖啡馆的计划",
+ "hideAvatar": "0",
+ "btnOrientation": "0",
+ "singleTitle" : "阅读全文",
+ "singleURL" : "https://www.dingtalk.com/",
+ "btns": [
+ {
+ "title": "内容不错",
+ "actionURL": "https://www.dingtalk.com/"
+ },
+ {
+ "title": "不感兴趣",
+ "actionURL": "https://www.dingtalk.com/"
+ }
+ ]
+ },
+ "msgtype": "actionCard"
+}
+
+{
+ "feedCard": {
+ "links": [
+ {
+ "title": "时代的火车向前开",
+ "messageURL": "https://mp.weixin.qq.com/s?__biz=MzA4NjMwMTA2Ng==&mid=2650316842&idx=1&sn=60da3ea2b29f1dcc43a7c8e4a7c97a16&scene=2&srcid=09189AnRJEdIiWVaKltFzNTw&from=timeline&isappinstalled=0&key=&ascene=2&uin=&devicetype=android-23&version=26031933&nettype=WIFI",
+ "picURL": "https://www.dingtalk.com/"
+ },
+ {
+ "title": "时代的火车向前开2",
+ "messageURL": "https://mp.weixin.qq.com/s?__biz=MzA4NjMwMTA2Ng==&mid=2650316842&idx=1&sn=60da3ea2b29f1dcc43a7c8e4a7c97a16&scene=2&srcid=09189AnRJEdIiWVaKltFzNTw&from=timeline&isappinstalled=0&key=&ascene=2&uin=&devicetype=android-23&version=26031933&nettype=WIFI",
+ "picURL": "https://www.dingtalk.com/"
+ }
+ ]
+ },
+ "msgtype": "feedCard"
+}
+*/
+
+type LinkMsg struct {
+ Title string `json:"title"`
+ MessageURL string `json:"messageURL"`
+ PicURL string `json:"picURL"`
+}
+
+type ActionCard struct {
+ Text string `json:"text"`
+ Title string `json:"title"`
+ HideAvatar string `json:"hideAvatar"`
+ BtnOrientation string `json:"btnOrientation"`
+ SingleTitle string `json:"singleTitle"`
+ SingleURL string `json:"singleURL"`
+ Buttons []struct {
+ Title string `json:"title"`
+ ActionURL string `json:"actionURL"`
+ } `json:"btns"`
+}
+
+// Payload struct
+type Payload struct {
+ MsgType string `json:"msgtype"`
+ Text struct {
+ Content string `json:"content"`
+ } `json:"text"`
+ Link struct {
+ Text string `json:"text"`
+ Title string `json:"title"`
+ PicURL string `json:"picUrl"`
+ MessageURL string `json:"messageUrl"`
+ } `json:"link"`
+ Markdown struct {
+ Text string `json:"text"`
+ Title string `json:"title"`
+ } `json:"markdown"`
+ ActionCard ActionCard `json:"actionCard"`
+ FeedCard struct {
+ Links []LinkMsg `json:"links"`
+ } `json:"feedCard"`
+ At struct {
+ AtMobiles []string `json:"atMobiles"`
+ IsAtAll bool `json:"isAtAll"`
+ } `json:"at"`
+}
+
+type Webhook struct {
+ accessToken string
+}
+
+func NewWebhook(accessToken string) *Webhook {
+ return &Webhook{accessToken}
+}
+
+type Response struct {
+ ErrorCode int `json:"errcode"`
+ ErrorMessage string `json:"errmsg"`
+}
+
+// SendPayload 发送消息
+func (w *Webhook) SendPayload(payload *Payload) error {
+ bs, err := json.Marshal(payload)
+ if err != nil {
+ return err
+ }
+
+ resp, err := http.Post("https://oapi.dingtalk.com/robot/send?access_token="+w.accessToken, "application/json", bytes.NewReader(bs))
+ if err != nil {
+ return err
+ }
+
+ bs, err = ioutil.ReadAll(resp.Body)
+ if err != nil {
+ return err
+ }
+
+ if resp.StatusCode != 200 {
+ return fmt.Errorf("%d: %s", resp.StatusCode, string(bs))
+ }
+
+ var result Response
+ err = json.Unmarshal(bs, &result)
+ if err != nil {
+ return err
+ }
+ if result.ErrorCode != 0 {
+ return fmt.Errorf("%d: %s", result.ErrorCode, result.ErrorMessage)
+ }
+
+ return nil
+}
+
+// SendTextMsg 发送文本消息
+func (w *Webhook) SendTextMsg(content string, isAtAll bool, mobiles ...string) error {
+ return w.SendPayload(&Payload{
+ MsgType: "text",
+ Text: struct {
+ Content string `json:"content"`
+ }{
+ Content: content,
+ },
+ At: struct {
+ AtMobiles []string `json:"atMobiles"`
+ IsAtAll bool `json:"isAtAll"`
+ }{
+ AtMobiles: mobiles,
+ IsAtAll: isAtAll,
+ },
+ })
+}
+
+// SendLinkMsg 发送链接消息
+func (w *Webhook) SendLinkMsg(title, content, picURL, msgURL string) error {
+ return w.SendPayload(&Payload{
+ MsgType: "link",
+ Link: struct {
+ Text string `json:"text"`
+ Title string `json:"title"`
+ PicURL string `json:"picUrl"`
+ MessageURL string `json:"messageUrl"`
+ }{
+ Text: content,
+ Title: title,
+ PicURL: picURL,
+ MessageURL: msgURL,
+ },
+ })
+}
+
+// SendMarkdownMsg 发送markdown消息,仅支持以下格式
+/*
+标题
+# 一级标题
+## 二级标题
+### 三级标题
+#### 四级标题
+##### 五级标题
+###### 六级标题
+
+引用
+> A man who stands for nothing will fall for anything.
+
+文字加粗、斜体
+**bold**
+*italic*
+
+链接
+[this is a link](http://name.com)
+
+图片
+![](http://name.com/pic.jpg)
+
+无序列表
+- item1
+- item2
+
+有序列表
+1. item1
+2. item2
+*/
+func (w *Webhook) SendMarkdownMsg(title, content string, isAtAll bool, mobiles ...string) error {
+ return w.SendPayload(&Payload{
+ MsgType: "markdown",
+ Markdown: struct {
+ Text string `json:"text"`
+ Title string `json:"title"`
+ }{
+ Text: content,
+ Title: title,
+ },
+ At: struct {
+ AtMobiles []string `json:"atMobiles"`
+ IsAtAll bool `json:"isAtAll"`
+ }{
+ AtMobiles: mobiles,
+ IsAtAll: isAtAll,
+ },
+ })
+}
+
+// SendSingleActionCardMsg 发送整体跳转ActionCard类型消息
+func (w *Webhook) SendSingleActionCardMsg(title, content, linkTitle, linkURL string, hideAvatar, btnOrientation bool) error {
+ var strHideAvatar = "0"
+ if hideAvatar {
+ strHideAvatar = "1"
+ }
+ var strBtnOrientation = "0"
+ if btnOrientation {
+ strBtnOrientation = "1"
+ }
+
+ return w.SendPayload(&Payload{
+ MsgType: "actionCard",
+ ActionCard: ActionCard{
+ Text: content,
+ Title: title,
+ HideAvatar: strHideAvatar,
+ BtnOrientation: strBtnOrientation,
+ SingleTitle: linkTitle,
+ SingleURL: linkURL,
+ },
+ })
+}
+
+// SendActionCardMsg 独立跳转ActionCard类型
+func (w *Webhook) SendActionCardMsg(title, content string, linkTitles, linkURLs []string, hideAvatar, btnOrientation bool) error {
+ if len(linkTitles) == 0 || len(linkURLs) == 0 {
+ return errors.New("链接参数不能为空")
+ }
+ if len(linkTitles) != len(linkURLs) {
+ return errors.New("链接数量不匹配")
+ }
+
+ var strHideAvatar = "0"
+ if hideAvatar {
+ strHideAvatar = "1"
+ }
+ var strBtnOrientation = "0"
+ if btnOrientation {
+ strBtnOrientation = "1"
+ }
+
+ var btns []struct {
+ Title string `json:"title"`
+ ActionURL string `json:"actionURL"`
+ }
+
+ for i := 0; i < len(linkTitles); i++ {
+ btns = append(btns, struct {
+ Title string `json:"title"`
+ ActionURL string `json:"actionURL"`
+ }{
+ Title: linkTitles[i],
+ ActionURL: linkURLs[i],
+ })
+ }
+
+ return w.SendPayload(&Payload{
+ MsgType: "actionCard",
+ ActionCard: ActionCard{
+ Text: content,
+ Title: title,
+ HideAvatar: strHideAvatar,
+ BtnOrientation: strBtnOrientation,
+ Buttons: btns,
+ },
+ })
+}
+
+// SendLinkCardMsg 发送链接消息
+func (w *Webhook) SendLinkCardMsg(msgs []LinkMsg) error {
+ return w.SendPayload(&Payload{
+ MsgType: "feedCard",
+ FeedCard: struct {
+ Links []LinkMsg `json:"links"`
+ }{
+ Links: msgs,
+ },
+ })
+}
diff --git a/vendor/vendor.json b/vendor/vendor.json
index d3cb55ebb6..8e91a66b88 100644
--- a/vendor/vendor.json
+++ b/vendor/vendor.json
@@ -648,6 +648,12 @@
"revisionTime": "2017-10-19T22:30:07Z"
},
{
+ "checksumSHA1": "gVEVVVLsFxLE+ADLuzkmzMxlmMA=",
+ "path": "github.com/lunny/dingtalk_webhook",
+ "revision": "e3534c89ef969912856dfa39e56b09e58c5f5daf",
+ "revisionTime": "2017-10-25T03:15:54Z"
+ },
+ {
"checksumSHA1": "O3KUfEXQPfdQ+tCMpP2RAIRJJqY=",
"path": "github.com/markbates/goth",
"revision": "90362394a367f9d77730911973462a53d69662ba",