diff --git a/models/webhook.go b/models/webhook.go index a764455f5f39a..957de248fec29 100644 --- a/models/webhook.go +++ b/models/webhook.go @@ -381,6 +381,7 @@ const ( GITEA DISCORD DINGTALK + TYPETALK ) var hookTaskTypes = map[string]HookTaskType{ @@ -389,6 +390,7 @@ var hookTaskTypes = map[string]HookTaskType{ "slack": SLACK, "discord": DISCORD, "dingtalk": DINGTALK, + "typetalk": TYPETALK, } // ToHookTaskType returns HookTaskType by given name. @@ -409,6 +411,8 @@ func (t HookTaskType) Name() string { return "discord" case DINGTALK: return "dingtalk" + case TYPETALK: + return "typetalk" } return "" } @@ -581,6 +585,11 @@ func prepareWebhook(e Engine, w *Webhook, repo *Repository, event HookEventType, if err != nil { return fmt.Errorf("GetDingtalkPayload: %v", err) } + case TYPETALK: + payloader, err = GetTypetalkPayload(p, event, w.Meta) + if err != nil { + return fmt.Errorf("GetTypetalkPayload: %v", err) + } default: p.SetSecret(w.Secret) payloader = p diff --git a/models/webhook_typetalk.go b/models/webhook_typetalk.go new file mode 100644 index 0000000000000..276585c3bd197 --- /dev/null +++ b/models/webhook_typetalk.go @@ -0,0 +1,302 @@ +// Copyright 2018 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" + "unicode/utf8" + + "code.gitea.io/git" + api "code.gitea.io/sdk/gitea" +) + +// TypetalkPayload contains information for posting messages on Typetalk +type TypetalkPayload struct { + Message string `json:"message"` +} + +// SetSecret sets the Typetalk secret +func (p *TypetalkPayload) SetSecret(_ string) {} + +// JSONPayload Marshals TypetalkPayload to json +func (p *TypetalkPayload) JSONPayload() ([]byte, error) { + data, err := json.MarshalIndent(p, "", " ") + if err != nil { + return []byte{}, err + } + return data, nil +} + +func formatRepositoryLinkForTypetalk(name, url string) string { + return fmt.Sprintf("[%s](%s)", name, url) +} + +func formatIssueLinkForTypetalk(issueTitle, repositoryURL string, issueNumber int64) string { + return fmt.Sprintf("[#%d %s](%s/issues/%d)", issueNumber, issueTitle, repositoryURL, issueNumber) +} + +func formatIssueCommentLinkForTypetalk(repositoryURL string, issueID, commentID int64) string { + return fmt.Sprintf("[#%d](%s/issues/%d#%s)", commentID, repositoryURL, issueID, CommentHashTag(commentID)) +} + +func formatPullRequestLinkForTypetalk(pullRequestTitle, pullRequestyURL string, pullRequestID int64) string { + return fmt.Sprintf("[#%d %s](%s)", pullRequestID, pullRequestTitle, pullRequestyURL) +} + +func formatTagLinkForTypetalk(name, url, tag string) string { + return fmt.Sprintf("[%s:%s](%s/src/tag/%s)", name, tag, url, tag) +} + +func getTypetalkCreatePayload(p *api.CreatePayload) (*TypetalkPayload, error) { + repoLink := formatRepositoryLinkForTypetalk(p.Repo.FullName, p.Repo.HTMLURL) + refName := git.RefEndName(p.Ref) + message := fmt.Sprintf("[%s] %s %s created", repoLink, p.RefType, refName) + + return &TypetalkPayload{ + Message: message, + }, nil +} + +func getTypetalkDeletePayload(p *api.DeletePayload) (*TypetalkPayload, error) { + repoLink := formatRepositoryLinkForTypetalk(p.Repo.FullName, p.Repo.HTMLURL) + refName := git.RefEndName(p.Ref) + message := fmt.Sprintf("[%s] %s %s deleted", repoLink, p.RefType, refName) + + return &TypetalkPayload{ + Message: message, + }, nil +} + +func getTypetalkForkPayload(p *api.ForkPayload) (*TypetalkPayload, error) { + origin := fmt.Sprintf("[%s](%s)", p.Forkee.FullName, p.Forkee.HTMLURL) + forked := fmt.Sprintf("[%s](%s)", p.Repo.FullName, p.Repo.HTMLURL) + message := fmt.Sprintf("%s is forked to %s", origin, forked) + + return &TypetalkPayload{ + Message: message, + }, nil +} + +func getTypetalkIssuesPayload(p *api.IssuePayload) (*TypetalkPayload, error) { + + repoLink := formatRepositoryLinkForTypetalk(p.Repository.FullName, p.Repository.HTMLURL) + issueLink := formatIssueLinkForTypetalk(p.Issue.Title, p.Repository.HTMLURL, p.Index) + + var title, text string + switch p.Action { + case api.HookIssueOpened: + title = fmt.Sprintf("[%s] %s Issue opened by %s", repoLink, issueLink, p.Sender.UserName) + text = p.Issue.Body + case api.HookIssueClosed: + title = fmt.Sprintf("[%s] %s Issue closed by %s", repoLink, issueLink, p.Sender.UserName) + case api.HookIssueReOpened: + title = fmt.Sprintf("[%s] %s Issue re-opened by %s", repoLink, issueLink, p.Sender.UserName) + case api.HookIssueEdited: + title = fmt.Sprintf("[%s] %s Issue edited by %s", repoLink, issueLink, p.Sender.UserName) + text = p.Issue.Body + case api.HookIssueAssigned: + title = fmt.Sprintf("[%s] %s Issue assigned to %s", repoLink, issueLink, p.Issue.Assignee.UserName) + case api.HookIssueUnassigned: + title = fmt.Sprintf("[%s] %s Issue unassigned by %s", repoLink, issueLink, p.Sender.UserName) + case api.HookIssueLabelUpdated: + title = fmt.Sprintf("[%s] %s Issue labels updated by %s", repoLink, issueLink, p.Sender.UserName) + case api.HookIssueLabelCleared: + title = fmt.Sprintf("[%s] %s Issue labels cleared by %s", repoLink, issueLink, p.Sender.UserName) + case api.HookIssueSynchronized: + title = fmt.Sprintf("[%s] %s Issue synchronized by %s", repoLink, issueLink, p.Sender.UserName) + case api.HookIssueMilestoned: + title = fmt.Sprintf("[%s] %s Issue milestones updated by %s", repoLink, issueLink, p.Sender.UserName) + case api.HookIssueDemilestoned: + title = fmt.Sprintf("[%s] %s Issue milestone cleared by %s", repoLink, issueLink, p.Sender.UserName) + } + message := fmt.Sprintf("%s\n%s", title, text) + + return &TypetalkPayload{ + Message: message, + }, nil +} + +func getTypetalkIssueCommentPayload(p *api.IssueCommentPayload) (*TypetalkPayload, error) { + + repoLink := formatRepositoryLinkForTypetalk(p.Repository.FullName, p.Repository.HTMLURL) + issueLink := formatIssueLinkForTypetalk(p.Issue.Title, p.Repository.HTMLURL, p.Issue.Index) + issueCommentLink := formatIssueCommentLinkForTypetalk(p.Repository.HTMLURL, p.Issue.Index, p.Comment.ID) + + var title, text string + switch p.Action { + case api.HookIssueCommentCreated: + title = fmt.Sprintf("[%s] %s New comment %s created by %s ", repoLink, issueLink, issueCommentLink, p.Sender.UserName) + text = p.Comment.Body + case api.HookIssueCommentEdited: + title = fmt.Sprintf("[%s] %s Comment %s edited by %s", repoLink, issueLink, issueCommentLink, p.Sender.UserName) + text = p.Comment.Body + case api.HookIssueCommentDeleted: + title = fmt.Sprintf("[%s] %s Comment #%d deleted by %s", repoLink, issueLink, p.Comment.ID, p.Sender.UserName) + } + + message := fmt.Sprintf("%s\n%s", title, text) + return &TypetalkPayload{ + Message: message, + }, nil +} + +func getTypetalkPushPayload(p *api.PushPayload) (*TypetalkPayload, error) { + + branchName := git.RefEndName(p.Ref) + + var titleLink, commitDesc string + if len(p.Commits) == 1 { + commitDesc = "1 new commit" + titleLink = p.Commits[0].URL + } else { + commitDesc = fmt.Sprintf("%d new commits", len(p.Commits)) + titleLink = p.CompareURL + } + if titleLink == "" { + titleLink = p.Repo.HTMLURL + "/src/" + branchName + } + + title := fmt.Sprintf("[[%s:%s] %s](%s)", p.Repo.FullName, branchName, commitDesc, titleLink) + + var text string + for i, commit := range p.Commits { + var authorName string + if commit.Author != nil { + authorName = " - " + commit.Author.Name + } + t := fmt.Sprintf("[%s](%s) %s", commit.ID[:7], commit.URL, + strings.TrimRight(commit.Message, "\r\n")) + authorName + + // Typetalk accepts message shorter than 4000 characters. + if utf8.RuneCountInString(text)+utf8.RuneCountInString(t) < 4000 { + text += t + // Add linebreak to each commit but the last + if i < len(p.Commits)-1 { + text += "\n" + } + } else { + text += "..." + break + } + } + + message := fmt.Sprintf("%s\n%s", title, text) + + return &TypetalkPayload{ + Message: message, + }, nil +} + +func getTypetalkPullRequestPayload(p *api.PullRequestPayload) (*TypetalkPayload, error) { + + repoLink := formatRepositoryLinkForTypetalk(p.Repository.FullName, p.Repository.HTMLURL) + pullRequestLink := formatPullRequestLinkForTypetalk(p.PullRequest.Title, p.PullRequest.HTMLURL, p.Index) + + var text, title string + switch p.Action { + case api.HookIssueOpened: + title = fmt.Sprintf("[%s] %s Pull request opened by %s", repoLink, pullRequestLink, p.Sender.UserName) + text = p.PullRequest.Body + case api.HookIssueClosed: + if p.PullRequest.HasMerged { + title = fmt.Sprintf("[%s] %s Pull request merged by %s", repoLink, pullRequestLink, p.Sender.UserName) + } else { + title = fmt.Sprintf("[%s] %s Pull request closed by %s", repoLink, pullRequestLink, p.Sender.UserName) + } + case api.HookIssueReOpened: + title = fmt.Sprintf("[%s] %s Pull request re-opened by %s", repoLink, pullRequestLink, p.Sender.UserName) + case api.HookIssueEdited: + title = fmt.Sprintf("[%s] %s Pull request edited by %s", repoLink, pullRequestLink, p.Sender.UserName) + text = p.PullRequest.Body + case api.HookIssueAssigned: + list, err := MakeAssigneeList(&Issue{ID: p.PullRequest.ID}) + if err != nil { + return &TypetalkPayload{}, err + } + title = fmt.Sprintf("[%s] %s Pull request assigned to %s", repoLink, pullRequestLink, list) + case api.HookIssueUnassigned: + title = fmt.Sprintf("[%s] %s Pull request unassigned by %s", repoLink, pullRequestLink, p.Sender.UserName) + case api.HookIssueLabelUpdated: + title = fmt.Sprintf("[%s] %s Pull request labels updated by %s", repoLink, pullRequestLink, p.Sender.UserName) + case api.HookIssueLabelCleared: + title = fmt.Sprintf("[%s] %s Pull request labels cleared by %s", repoLink, pullRequestLink, p.Sender.UserName) + case api.HookIssueSynchronized: + title = fmt.Sprintf("[%s] %s Pull request synchronized by %s", repoLink, pullRequestLink, p.Sender.UserName) + case api.HookIssueMilestoned: + title = fmt.Sprintf("[%s] %s Pull request milestones updated by %s", repoLink, pullRequestLink, p.Sender.UserName) + case api.HookIssueDemilestoned: + title = fmt.Sprintf("[%s] %s Pull request milestones cleared by %s", repoLink, pullRequestLink, p.Sender.UserName) + } + + message := fmt.Sprintf("%s\n%s", title, text) + + return &TypetalkPayload{ + Message: message, + }, nil +} + +func getTypetalkRepositoryPayload(p *api.RepositoryPayload) (*TypetalkPayload, error) { + + var message string + switch p.Action { + case api.HookRepoCreated: + message = fmt.Sprintf("[%s] Repository created", formatRepositoryLinkForTypetalk(p.Repository.FullName, p.Repository.HTMLURL)) + case api.HookRepoDeleted: + message = fmt.Sprintf("[%s] Repository deleted", p.Repository.FullName) + } + + return &TypetalkPayload{ + Message: message, + }, nil +} + +func getTypetalkReleasePayload(p *api.ReleasePayload) (*TypetalkPayload, error) { + + tagLink := formatTagLinkForTypetalk(p.Repository.FullName, p.Repository.HTMLURL, p.Release.TagName) + + var message string + switch p.Action { + case api.HookReleasePublished: + message = fmt.Sprintf("[%s] Release created", tagLink) + case api.HookReleaseUpdated: + message = fmt.Sprintf("[%s] Release updated", tagLink) + case api.HookReleaseDeleted: + message = fmt.Sprintf("[%s:%s] Release deleted", p.Repository.FullName, p.Release.TagName) + } + + return &TypetalkPayload{ + Message: message, + }, nil +} + +// GetTypetalkPayload converts a Typetalk webhook into a TypetalkPayload +func GetTypetalkPayload(p api.Payloader, event HookEventType, meta string) (*TypetalkPayload, error) { + s := new(TypetalkPayload) + + switch event { + case HookEventCreate: + return getTypetalkCreatePayload(p.(*api.CreatePayload)) + case HookEventDelete: + return getTypetalkDeletePayload(p.(*api.DeletePayload)) + case HookEventFork: + return getTypetalkForkPayload(p.(*api.ForkPayload)) + case HookEventIssues: + return getTypetalkIssuesPayload(p.(*api.IssuePayload)) + case HookEventIssueComment: + return getTypetalkIssueCommentPayload(p.(*api.IssueCommentPayload)) + case HookEventPush: + return getTypetalkPushPayload(p.(*api.PushPayload)) + case HookEventPullRequest: + return getTypetalkPullRequestPayload(p.(*api.PullRequestPayload)) + case HookEventRepository: + return getTypetalkRepositoryPayload(p.(*api.RepositoryPayload)) + case HookEventRelease: + return getTypetalkReleasePayload(p.(*api.ReleasePayload)) + } + + return s, nil +} diff --git a/modules/auth/repo_form.go b/modules/auth/repo_form.go index a600ecc8fe831..e676f874fcab5 100644 --- a/modules/auth/repo_form.go +++ b/modules/auth/repo_form.go @@ -260,6 +260,17 @@ func (f *NewDingtalkHookForm) Validate(ctx *macaron.Context, errs binding.Errors return validate(errs, ctx.Data, f, ctx.Locale) } +// NewTypetalkHookForm form for creating typetalk hook +type NewTypetalkHookForm struct { + PayloadURL string `binding:"Required;ValidUrl"` + WebhookForm +} + +// Validate validates the fields +func (f *NewTypetalkHookForm) 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 2560c091073ee..9a24f9c298bc6 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -1614,7 +1614,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"} + Webhook.Types = []string{"gitea", "gogs", "slack", "discord", "dingtalk", "typetalk"} 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 fd51ca678e192..51c6a73b41ebd 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -1127,6 +1127,7 @@ settings.slack_domain = Domain settings.slack_channel = Channel settings.add_discord_hook_desc = Integrate Discord into your repository. settings.add_dingtalk_hook_desc = Integrate Dingtalk into your repository. +settings.add_typetalk_hook_desc = Integrate Typetalk 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/typetalk.ico b/public/img/typetalk.ico new file mode 100644 index 0000000000000..b9d9624ad2ce5 Binary files /dev/null and b/public/img/typetalk.ico differ diff --git a/routers/repo/webhook.go b/routers/repo/webhook.go index 6c69354c700eb..a7aa9de0640cb 100644 --- a/routers/repo/webhook.go +++ b/routers/repo/webhook.go @@ -314,6 +314,46 @@ func DingtalkHooksNewPost(ctx *context.Context, form auth.NewDingtalkHookForm) { ctx.Redirect(orCtx.Link + "/settings/hooks") } +// TypetalkHooksNewPost handles request for creating typetalk hook +func TypetalkHooksNewPost(ctx *context.Context, form auth.NewTypetalkHookForm) { + 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.ServerError("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.TYPETALK, + Meta: "", + OrgID: orCtx.OrgID, + } + 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 + "/settings/hooks") +} + // SlackHooksNewPost response for creating slack hook func SlackHooksNewPost(ctx *context.Context, form auth.NewSlackHookForm) { ctx.Data["Title"] = ctx.Tr("repo.settings") @@ -628,6 +668,38 @@ func DingtalkHooksEditPost(ctx *context.Context, form auth.NewDingtalkHookForm) ctx.Redirect(fmt.Sprintf("%s/settings/hooks/%d", orCtx.Link, w.ID)) } +// TypetalkHooksEditPost handles request for editing webhook settings for Typetalk. +func TypetalkHooksEditPost(ctx *context.Context, form auth.NewTypetalkHookForm) { + 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.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/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 6011427321aed..78580f8849651 100644 --- a/routers/routes/routes.go +++ b/routers/routes/routes.go @@ -451,12 +451,14 @@ func RegisterRoutes(m *macaron.Macaron) { 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.Post("/typetalk/new", bindIgnErr(auth.NewTypetalkHookForm{}), repo.TypetalkHooksNewPost) 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.Post("/typetalk/:id", bindIgnErr(auth.NewTypetalkHookForm{}), repo.TypetalkHooksEditPost) }) m.Route("/delete", "GET,POST", org.SettingsDelete) @@ -504,6 +506,7 @@ func RegisterRoutes(m *macaron.Macaron) { 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.Post("/typetalk/new", bindIgnErr(auth.NewTypetalkHookForm{}), repo.TypetalkHooksNewPost) m.Get("/:id", repo.WebHooksEdit) m.Post("/:id/test", repo.TestWebhook) m.Post("/gitea/:id", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksEditPost) @@ -511,6 +514,7 @@ func RegisterRoutes(m *macaron.Macaron) { 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.Post("/typetalk/:id", bindIgnErr(auth.NewTypetalkHookForm{}), repo.TypetalkHooksEditPost) 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 809009b66b7b4..368a3393f79d2 100644 --- a/templates/org/settings/hook_new.tmpl +++ b/templates/org/settings/hook_new.tmpl @@ -19,6 +19,8 @@ {{else if eq .HookType "dingtalk"}} + {{else if eq .HookType "typetalk"}} + {{end}} @@ -28,6 +30,7 @@ {{template "repo/settings/webhook/slack" .}} {{template "repo/settings/webhook/discord" .}} {{template "repo/settings/webhook/dingtalk" .}} + {{template "repo/settings/webhook/typetalk" .}} {{template "repo/settings/webhook/history" .}} diff --git a/templates/repo/settings/webhook/list.tmpl b/templates/repo/settings/webhook/list.tmpl index d98976cf5b400..660f573ae51fd 100644 --- a/templates/repo/settings/webhook/list.tmpl +++ b/templates/repo/settings/webhook/list.tmpl @@ -20,6 +20,9 @@ Dingtalk + + Typetalk + diff --git a/templates/repo/settings/webhook/new.tmpl b/templates/repo/settings/webhook/new.tmpl index 1b3d114577a4e..f03393c6751f8 100644 --- a/templates/repo/settings/webhook/new.tmpl +++ b/templates/repo/settings/webhook/new.tmpl @@ -17,6 +17,8 @@ {{else if eq .HookType "dingtalk"}} + {{else if eq .HookType "typetalk"}} + {{end}} @@ -26,6 +28,7 @@ {{template "repo/settings/webhook/slack" .}} {{template "repo/settings/webhook/discord" .}} {{template "repo/settings/webhook/dingtalk" .}} + {{template "repo/settings/webhook/typetalk" .}} {{template "repo/settings/webhook/history" .}} diff --git a/templates/repo/settings/webhook/typetalk.tmpl b/templates/repo/settings/webhook/typetalk.tmpl new file mode 100644 index 0000000000000..c690e73e5bc9d --- /dev/null +++ b/templates/repo/settings/webhook/typetalk.tmpl @@ -0,0 +1,11 @@ +{{if eq .HookType "typetalk"}} +

{{.i18n.Tr "repo.settings.add_typetalk_hook_desc" "https://www.typetalk.com" | Str2html}}

+
+ {{.CsrfTokenHtml}} +
+ + +
+ {{template "repo/settings/webhook/settings" .}} +
+{{end}}