diff options
author | Lunny Xiao <xiaolunwen@gmail.com> | 2020-12-08 18:41:14 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-12-08 11:41:14 +0100 |
commit | 42354dfe45fa0cabb59674b896c44a55a56cf163 (patch) | |
tree | 86b859881da6ef6bf288183933d7bc519dedc3d4 /modules | |
parent | 4d66ee1f74799196cbdbfd925c3f95e552584b42 (diff) | |
download | gitea-42354dfe45fa0cabb59674b896c44a55a56cf163.tar.gz gitea-42354dfe45fa0cabb59674b896c44a55a56cf163.zip |
Move webhook type from int to string (#13664)
* Move webhook type from int to string
* rename webhook_services
* finish refactor
* Fix merge
* Ignore unnecessary ci
Co-authored-by: 6543 <6543@obermui.de>
Co-authored-by: zeripath <art27@cantab.net>
Diffstat (limited to 'modules')
-rw-r--r-- | modules/convert/convert.go | 4 | ||||
-rw-r--r-- | modules/notification/webhook/webhook.go | 72 | ||||
-rw-r--r-- | modules/webhook/deliver.go | 281 | ||||
-rw-r--r-- | modules/webhook/deliver_test.go | 39 | ||||
-rw-r--r-- | modules/webhook/dingtalk.go | 270 | ||||
-rw-r--r-- | modules/webhook/dingtalk_test.go | 31 | ||||
-rw-r--r-- | modules/webhook/discord.go | 432 | ||||
-rw-r--r-- | modules/webhook/feishu.go | 190 | ||||
-rw-r--r-- | modules/webhook/general.go | 193 | ||||
-rw-r--r-- | modules/webhook/general_test.go | 125 | ||||
-rw-r--r-- | modules/webhook/main_test.go | 16 | ||||
-rw-r--r-- | modules/webhook/matrix.go | 309 | ||||
-rw-r--r-- | modules/webhook/matrix_test.go | 181 | ||||
-rw-r--r-- | modules/webhook/msteams.go | 563 | ||||
-rw-r--r-- | modules/webhook/payloader.go | 56 | ||||
-rw-r--r-- | modules/webhook/slack.go | 333 | ||||
-rw-r--r-- | modules/webhook/slack_test.go | 80 | ||||
-rw-r--r-- | modules/webhook/telegram.go | 212 | ||||
-rw-r--r-- | modules/webhook/telegram_test.go | 24 | ||||
-rw-r--r-- | modules/webhook/webhook.go | 214 | ||||
-rw-r--r-- | modules/webhook/webhook_test.go | 79 |
21 files changed, 38 insertions, 3666 deletions
diff --git a/modules/convert/convert.go b/modules/convert/convert.go index 4d4d9396fe..9c90e6ac51 100644 --- a/modules/convert/convert.go +++ b/modules/convert/convert.go @@ -16,7 +16,7 @@ import ( "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/util" - "code.gitea.io/gitea/modules/webhook" + "code.gitea.io/gitea/services/webhook" "github.com/unknwon/com" ) @@ -237,7 +237,7 @@ func ToHook(repoLink string, w *models.Webhook) *api.Hook { return &api.Hook{ ID: w.ID, - Type: w.HookTaskType.Name(), + Type: string(w.HookTaskType), URL: fmt.Sprintf("%s/settings/hooks/%d", repoLink, w.ID), Active: w.IsActive, Config: config, diff --git a/modules/notification/webhook/webhook.go b/modules/notification/webhook/webhook.go index 4c9c213f18..2a06eba219 100644 --- a/modules/notification/webhook/webhook.go +++ b/modules/notification/webhook/webhook.go @@ -13,7 +13,7 @@ import ( "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" - webhook_module "code.gitea.io/gitea/modules/webhook" + webhook_services "code.gitea.io/gitea/services/webhook" ) type webhookNotifier struct { @@ -48,7 +48,7 @@ func (m *webhookNotifier) NotifyIssueClearLabels(doer *models.User, issue *model return } - err = webhook_module.PrepareWebhooks(issue.Repo, models.HookEventPullRequestLabel, &api.PullRequestPayload{ + err = webhook_services.PrepareWebhooks(issue.Repo, models.HookEventPullRequestLabel, &api.PullRequestPayload{ Action: api.HookIssueLabelCleared, Index: issue.Index, PullRequest: convert.ToAPIPullRequest(issue.PullRequest), @@ -56,7 +56,7 @@ func (m *webhookNotifier) NotifyIssueClearLabels(doer *models.User, issue *model Sender: convert.ToUser(doer, false, false), }) } else { - err = webhook_module.PrepareWebhooks(issue.Repo, models.HookEventIssueLabel, &api.IssuePayload{ + err = webhook_services.PrepareWebhooks(issue.Repo, models.HookEventIssueLabel, &api.IssuePayload{ Action: api.HookIssueLabelCleared, Index: issue.Index, Issue: convert.ToAPIIssue(issue), @@ -74,7 +74,7 @@ func (m *webhookNotifier) NotifyForkRepository(doer *models.User, oldRepo, repo mode, _ := models.AccessLevel(doer, repo) // forked webhook - if err := webhook_module.PrepareWebhooks(oldRepo, models.HookEventFork, &api.ForkPayload{ + if err := webhook_services.PrepareWebhooks(oldRepo, models.HookEventFork, &api.ForkPayload{ Forkee: convert.ToRepo(oldRepo, oldMode), Repo: convert.ToRepo(repo, mode), Sender: convert.ToUser(doer, false, false), @@ -86,7 +86,7 @@ func (m *webhookNotifier) NotifyForkRepository(doer *models.User, oldRepo, repo // Add to hook queue for created repo after session commit. if u.IsOrganization() { - if err := webhook_module.PrepareWebhooks(repo, models.HookEventRepository, &api.RepositoryPayload{ + if err := webhook_services.PrepareWebhooks(repo, models.HookEventRepository, &api.RepositoryPayload{ Action: api.HookRepoCreated, Repository: convert.ToRepo(repo, models.AccessModeOwner), Organization: convert.ToUser(u, false, false), @@ -99,7 +99,7 @@ func (m *webhookNotifier) NotifyForkRepository(doer *models.User, oldRepo, repo func (m *webhookNotifier) NotifyCreateRepository(doer *models.User, u *models.User, repo *models.Repository) { // Add to hook queue for created repo after session commit. - if err := webhook_module.PrepareWebhooks(repo, models.HookEventRepository, &api.RepositoryPayload{ + if err := webhook_services.PrepareWebhooks(repo, models.HookEventRepository, &api.RepositoryPayload{ Action: api.HookRepoCreated, Repository: convert.ToRepo(repo, models.AccessModeOwner), Organization: convert.ToUser(u, false, false), @@ -112,7 +112,7 @@ func (m *webhookNotifier) NotifyCreateRepository(doer *models.User, u *models.Us func (m *webhookNotifier) NotifyDeleteRepository(doer *models.User, repo *models.Repository) { u := repo.MustOwner() - if err := webhook_module.PrepareWebhooks(repo, models.HookEventRepository, &api.RepositoryPayload{ + if err := webhook_services.PrepareWebhooks(repo, models.HookEventRepository, &api.RepositoryPayload{ Action: api.HookRepoDeleted, Repository: convert.ToRepo(repo, models.AccessModeOwner), Organization: convert.ToUser(u, false, false), @@ -143,7 +143,7 @@ func (m *webhookNotifier) NotifyIssueChangeAssignee(doer *models.User, issue *mo apiPullRequest.Action = api.HookIssueAssigned } // Assignee comment triggers a webhook - if err := webhook_module.PrepareWebhooks(issue.Repo, models.HookEventPullRequestAssign, apiPullRequest); err != nil { + if err := webhook_services.PrepareWebhooks(issue.Repo, models.HookEventPullRequestAssign, apiPullRequest); err != nil { log.Error("PrepareWebhooks [is_pull: %v, remove_assignee: %v]: %v", issue.IsPull, removed, err) return } @@ -161,7 +161,7 @@ func (m *webhookNotifier) NotifyIssueChangeAssignee(doer *models.User, issue *mo apiIssue.Action = api.HookIssueAssigned } // Assignee comment triggers a webhook - if err := webhook_module.PrepareWebhooks(issue.Repo, models.HookEventIssueAssign, apiIssue); err != nil { + if err := webhook_services.PrepareWebhooks(issue.Repo, models.HookEventIssueAssign, apiIssue); err != nil { log.Error("PrepareWebhooks [is_pull: %v, remove_assignee: %v]: %v", issue.IsPull, removed, err) return } @@ -177,7 +177,7 @@ func (m *webhookNotifier) NotifyIssueChangeTitle(doer *models.User, issue *model return } issue.PullRequest.Issue = issue - err = webhook_module.PrepareWebhooks(issue.Repo, models.HookEventPullRequest, &api.PullRequestPayload{ + err = webhook_services.PrepareWebhooks(issue.Repo, models.HookEventPullRequest, &api.PullRequestPayload{ Action: api.HookIssueEdited, Index: issue.Index, Changes: &api.ChangesPayload{ @@ -190,7 +190,7 @@ func (m *webhookNotifier) NotifyIssueChangeTitle(doer *models.User, issue *model Sender: convert.ToUser(doer, false, false), }) } else { - err = webhook_module.PrepareWebhooks(issue.Repo, models.HookEventIssues, &api.IssuePayload{ + err = webhook_services.PrepareWebhooks(issue.Repo, models.HookEventIssues, &api.IssuePayload{ Action: api.HookIssueEdited, Index: issue.Index, Changes: &api.ChangesPayload{ @@ -229,7 +229,7 @@ func (m *webhookNotifier) NotifyIssueChangeStatus(doer *models.User, issue *mode } else { apiPullRequest.Action = api.HookIssueReOpened } - err = webhook_module.PrepareWebhooks(issue.Repo, models.HookEventPullRequest, apiPullRequest) + err = webhook_services.PrepareWebhooks(issue.Repo, models.HookEventPullRequest, apiPullRequest) } else { apiIssue := &api.IssuePayload{ Index: issue.Index, @@ -242,7 +242,7 @@ func (m *webhookNotifier) NotifyIssueChangeStatus(doer *models.User, issue *mode } else { apiIssue.Action = api.HookIssueReOpened } - err = webhook_module.PrepareWebhooks(issue.Repo, models.HookEventIssues, apiIssue) + err = webhook_services.PrepareWebhooks(issue.Repo, models.HookEventIssues, apiIssue) } if err != nil { log.Error("PrepareWebhooks [is_pull: %v, is_closed: %v]: %v", issue.IsPull, isClosed, err) @@ -260,7 +260,7 @@ func (m *webhookNotifier) NotifyNewIssue(issue *models.Issue) { } mode, _ := models.AccessLevel(issue.Poster, issue.Repo) - if err := webhook_module.PrepareWebhooks(issue.Repo, models.HookEventIssues, &api.IssuePayload{ + if err := webhook_services.PrepareWebhooks(issue.Repo, models.HookEventIssues, &api.IssuePayload{ Action: api.HookIssueOpened, Index: issue.Index, Issue: convert.ToAPIIssue(issue), @@ -286,7 +286,7 @@ func (m *webhookNotifier) NotifyNewPullRequest(pull *models.PullRequest) { } mode, _ := models.AccessLevel(pull.Issue.Poster, pull.Issue.Repo) - if err := webhook_module.PrepareWebhooks(pull.Issue.Repo, models.HookEventPullRequest, &api.PullRequestPayload{ + if err := webhook_services.PrepareWebhooks(pull.Issue.Repo, models.HookEventPullRequest, &api.PullRequestPayload{ Action: api.HookIssueOpened, Index: pull.Issue.Index, PullRequest: convert.ToAPIPullRequest(pull), @@ -302,7 +302,7 @@ func (m *webhookNotifier) NotifyIssueChangeContent(doer *models.User, issue *mod var err error if issue.IsPull { issue.PullRequest.Issue = issue - err = webhook_module.PrepareWebhooks(issue.Repo, models.HookEventPullRequest, &api.PullRequestPayload{ + err = webhook_services.PrepareWebhooks(issue.Repo, models.HookEventPullRequest, &api.PullRequestPayload{ Action: api.HookIssueEdited, Index: issue.Index, Changes: &api.ChangesPayload{ @@ -315,7 +315,7 @@ func (m *webhookNotifier) NotifyIssueChangeContent(doer *models.User, issue *mod Sender: convert.ToUser(doer, false, false), }) } else { - err = webhook_module.PrepareWebhooks(issue.Repo, models.HookEventIssues, &api.IssuePayload{ + err = webhook_services.PrepareWebhooks(issue.Repo, models.HookEventIssues, &api.IssuePayload{ Action: api.HookIssueEdited, Index: issue.Index, Changes: &api.ChangesPayload{ @@ -352,7 +352,7 @@ func (m *webhookNotifier) NotifyUpdateComment(doer *models.User, c *models.Comme mode, _ := models.AccessLevel(doer, c.Issue.Repo) if c.Issue.IsPull { - err = webhook_module.PrepareWebhooks(c.Issue.Repo, models.HookEventPullRequestComment, &api.IssueCommentPayload{ + err = webhook_services.PrepareWebhooks(c.Issue.Repo, models.HookEventPullRequestComment, &api.IssueCommentPayload{ Action: api.HookIssueCommentEdited, Issue: convert.ToAPIIssue(c.Issue), Comment: convert.ToComment(c), @@ -366,7 +366,7 @@ func (m *webhookNotifier) NotifyUpdateComment(doer *models.User, c *models.Comme IsPull: true, }) } else { - err = webhook_module.PrepareWebhooks(c.Issue.Repo, models.HookEventIssueComment, &api.IssueCommentPayload{ + err = webhook_services.PrepareWebhooks(c.Issue.Repo, models.HookEventIssueComment, &api.IssueCommentPayload{ Action: api.HookIssueCommentEdited, Issue: convert.ToAPIIssue(c.Issue), Comment: convert.ToComment(c), @@ -392,7 +392,7 @@ func (m *webhookNotifier) NotifyCreateIssueComment(doer *models.User, repo *mode var err error if issue.IsPull { - err = webhook_module.PrepareWebhooks(issue.Repo, models.HookEventPullRequestComment, &api.IssueCommentPayload{ + err = webhook_services.PrepareWebhooks(issue.Repo, models.HookEventPullRequestComment, &api.IssueCommentPayload{ Action: api.HookIssueCommentCreated, Issue: convert.ToAPIIssue(issue), Comment: convert.ToComment(comment), @@ -401,7 +401,7 @@ func (m *webhookNotifier) NotifyCreateIssueComment(doer *models.User, repo *mode IsPull: true, }) } else { - err = webhook_module.PrepareWebhooks(issue.Repo, models.HookEventIssueComment, &api.IssueCommentPayload{ + err = webhook_services.PrepareWebhooks(issue.Repo, models.HookEventIssueComment, &api.IssueCommentPayload{ Action: api.HookIssueCommentCreated, Issue: convert.ToAPIIssue(issue), Comment: convert.ToComment(comment), @@ -436,7 +436,7 @@ func (m *webhookNotifier) NotifyDeleteComment(doer *models.User, comment *models mode, _ := models.AccessLevel(doer, comment.Issue.Repo) if comment.Issue.IsPull { - err = webhook_module.PrepareWebhooks(comment.Issue.Repo, models.HookEventPullRequestComment, &api.IssueCommentPayload{ + err = webhook_services.PrepareWebhooks(comment.Issue.Repo, models.HookEventPullRequestComment, &api.IssueCommentPayload{ Action: api.HookIssueCommentDeleted, Issue: convert.ToAPIIssue(comment.Issue), Comment: convert.ToComment(comment), @@ -445,7 +445,7 @@ func (m *webhookNotifier) NotifyDeleteComment(doer *models.User, comment *models IsPull: true, }) } else { - err = webhook_module.PrepareWebhooks(comment.Issue.Repo, models.HookEventIssueComment, &api.IssueCommentPayload{ + err = webhook_services.PrepareWebhooks(comment.Issue.Repo, models.HookEventIssueComment, &api.IssueCommentPayload{ Action: api.HookIssueCommentDeleted, Issue: convert.ToAPIIssue(comment.Issue), Comment: convert.ToComment(comment), @@ -485,7 +485,7 @@ func (m *webhookNotifier) NotifyIssueChangeLabels(doer *models.User, issue *mode log.Error("LoadIssue: %v", err) return } - err = webhook_module.PrepareWebhooks(issue.Repo, models.HookEventPullRequestLabel, &api.PullRequestPayload{ + err = webhook_services.PrepareWebhooks(issue.Repo, models.HookEventPullRequestLabel, &api.PullRequestPayload{ Action: api.HookIssueLabelUpdated, Index: issue.Index, PullRequest: convert.ToAPIPullRequest(issue.PullRequest), @@ -493,7 +493,7 @@ func (m *webhookNotifier) NotifyIssueChangeLabels(doer *models.User, issue *mode Sender: convert.ToUser(doer, false, false), }) } else { - err = webhook_module.PrepareWebhooks(issue.Repo, models.HookEventIssueLabel, &api.IssuePayload{ + err = webhook_services.PrepareWebhooks(issue.Repo, models.HookEventIssueLabel, &api.IssuePayload{ Action: api.HookIssueLabelUpdated, Index: issue.Index, Issue: convert.ToAPIIssue(issue), @@ -527,7 +527,7 @@ func (m *webhookNotifier) NotifyIssueChangeMilestone(doer *models.User, issue *m log.Error("LoadIssue: %v", err) return } - err = webhook_module.PrepareWebhooks(issue.Repo, models.HookEventPullRequestMilestone, &api.PullRequestPayload{ + err = webhook_services.PrepareWebhooks(issue.Repo, models.HookEventPullRequestMilestone, &api.PullRequestPayload{ Action: hookAction, Index: issue.Index, PullRequest: convert.ToAPIPullRequest(issue.PullRequest), @@ -535,7 +535,7 @@ func (m *webhookNotifier) NotifyIssueChangeMilestone(doer *models.User, issue *m Sender: convert.ToUser(doer, false, false), }) } else { - err = webhook_module.PrepareWebhooks(issue.Repo, models.HookEventIssueMilestone, &api.IssuePayload{ + err = webhook_services.PrepareWebhooks(issue.Repo, models.HookEventIssueMilestone, &api.IssuePayload{ Action: hookAction, Index: issue.Index, Issue: convert.ToAPIIssue(issue), @@ -556,7 +556,7 @@ func (m *webhookNotifier) NotifyPushCommits(pusher *models.User, repo *models.Re return } - if err := webhook_module.PrepareWebhooks(repo, models.HookEventPush, &api.PushPayload{ + if err := webhook_services.PrepareWebhooks(repo, models.HookEventPush, &api.PushPayload{ Ref: opts.RefFullName, Before: opts.OldCommitID, After: opts.NewCommitID, @@ -602,7 +602,7 @@ func (*webhookNotifier) NotifyMergePullRequest(pr *models.PullRequest, doer *mod Action: api.HookIssueClosed, } - err = webhook_module.PrepareWebhooks(pr.Issue.Repo, models.HookEventPullRequest, apiPullRequest) + err = webhook_services.PrepareWebhooks(pr.Issue.Repo, models.HookEventPullRequest, apiPullRequest) if err != nil { log.Error("PrepareWebhooks: %v", err) } @@ -621,7 +621,7 @@ func (m *webhookNotifier) NotifyPullRequestChangeTargetBranch(doer *models.User, } issue.PullRequest.Issue = issue mode, _ := models.AccessLevel(issue.Poster, issue.Repo) - err = webhook_module.PrepareWebhooks(issue.Repo, models.HookEventPullRequest, &api.PullRequestPayload{ + err = webhook_services.PrepareWebhooks(issue.Repo, models.HookEventPullRequest, &api.PullRequestPayload{ Action: api.HookIssueEdited, Index: issue.Index, Changes: &api.ChangesPayload{ @@ -665,7 +665,7 @@ func (m *webhookNotifier) NotifyPullRequestReview(pr *models.PullRequest, review log.Error("models.AccessLevel: %v", err) return } - if err := webhook_module.PrepareWebhooks(review.Issue.Repo, reviewHookType, &api.PullRequestPayload{ + if err := webhook_services.PrepareWebhooks(review.Issue.Repo, reviewHookType, &api.PullRequestPayload{ Action: api.HookIssueReviewed, Index: review.Issue.Index, PullRequest: convert.ToAPIPullRequest(pr), @@ -699,7 +699,7 @@ func (m *webhookNotifier) NotifyCreateRef(pusher *models.User, repo *models.Repo } gitRepo.Close() - if err = webhook_module.PrepareWebhooks(repo, models.HookEventCreate, &api.CreatePayload{ + if err = webhook_services.PrepareWebhooks(repo, models.HookEventCreate, &api.CreatePayload{ Ref: refName, Sha: shaSum, RefType: refType, @@ -720,7 +720,7 @@ func (m *webhookNotifier) NotifyPullRequestSynchronized(doer *models.User, pr *m return } - if err := webhook_module.PrepareWebhooks(pr.Issue.Repo, models.HookEventPullRequestSync, &api.PullRequestPayload{ + if err := webhook_services.PrepareWebhooks(pr.Issue.Repo, models.HookEventPullRequestSync, &api.PullRequestPayload{ Action: api.HookIssueSynchronized, Index: pr.Issue.Index, PullRequest: convert.ToAPIPullRequest(pr), @@ -736,7 +736,7 @@ func (m *webhookNotifier) NotifyDeleteRef(pusher *models.User, repo *models.Repo apiRepo := convert.ToRepo(repo, models.AccessModeNone) refName := git.RefEndName(refFullName) - if err := webhook_module.PrepareWebhooks(repo, models.HookEventDelete, &api.DeletePayload{ + if err := webhook_services.PrepareWebhooks(repo, models.HookEventDelete, &api.DeletePayload{ Ref: refName, RefType: refType, PusherType: api.PusherTypeUser, @@ -754,7 +754,7 @@ func sendReleaseHook(doer *models.User, rel *models.Release, action api.HookRele } mode, _ := models.AccessLevel(rel.Publisher, rel.Repo) - if err := webhook_module.PrepareWebhooks(rel.Repo, models.HookEventRelease, &api.ReleasePayload{ + if err := webhook_services.PrepareWebhooks(rel.Repo, models.HookEventRelease, &api.ReleasePayload{ Action: action, Release: convert.ToRelease(rel), Repository: convert.ToRepo(rel.Repo, mode), @@ -784,7 +784,7 @@ func (m *webhookNotifier) NotifySyncPushCommits(pusher *models.User, repo *model return } - if err := webhook_module.PrepareWebhooks(repo, models.HookEventPush, &api.PushPayload{ + if err := webhook_services.PrepareWebhooks(repo, models.HookEventPush, &api.PushPayload{ Ref: opts.RefFullName, Before: opts.OldCommitID, After: opts.NewCommitID, diff --git a/modules/webhook/deliver.go b/modules/webhook/deliver.go deleted file mode 100644 index c29fcb6fa9..0000000000 --- a/modules/webhook/deliver.go +++ /dev/null @@ -1,281 +0,0 @@ -// Copyright 2019 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 ( - "context" - "crypto/tls" - "fmt" - "io/ioutil" - "net" - "net/http" - "net/url" - "strings" - "sync" - "time" - - "code.gitea.io/gitea/models" - "code.gitea.io/gitea/modules/graceful" - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/setting" - "github.com/gobwas/glob" - "github.com/unknwon/com" -) - -// Deliver deliver hook task -func Deliver(t *models.HookTask) error { - defer func() { - err := recover() - if err == nil { - return - } - // There was a panic whilst delivering a hook... - log.Error("PANIC whilst trying to deliver webhook[%d] for repo[%d] to %s Panic: %v\nStacktrace: %s", t.ID, t.RepoID, t.URL, err, log.Stack(2)) - }() - t.IsDelivered = true - - var req *http.Request - var err error - - switch t.HTTPMethod { - case "": - log.Info("HTTP Method for webhook %d empty, setting to POST as default", t.ID) - fallthrough - case http.MethodPost: - switch t.ContentType { - case models.ContentTypeJSON: - req, err = http.NewRequest("POST", t.URL, strings.NewReader(t.PayloadContent)) - if err != nil { - return err - } - - req.Header.Set("Content-Type", "application/json") - case models.ContentTypeForm: - var forms = url.Values{ - "payload": []string{t.PayloadContent}, - } - - req, err = http.NewRequest("POST", t.URL, strings.NewReader(forms.Encode())) - if err != nil { - - return err - } - - req.Header.Set("Content-Type", "application/x-www-form-urlencoded") - } - case http.MethodGet: - u, err := url.Parse(t.URL) - if err != nil { - return err - } - vals := u.Query() - vals["payload"] = []string{t.PayloadContent} - u.RawQuery = vals.Encode() - req, err = http.NewRequest("GET", u.String(), nil) - if err != nil { - return err - } - case http.MethodPut: - switch t.Type { - case models.MATRIX: - req, err = getMatrixHookRequest(t) - if err != nil { - return err - } - default: - return fmt.Errorf("Invalid http method for webhook: [%d] %v", t.ID, t.HTTPMethod) - } - default: - return fmt.Errorf("Invalid http method for webhook: [%d] %v", t.ID, t.HTTPMethod) - } - - req.Header.Add("X-Gitea-Delivery", t.UUID) - req.Header.Add("X-Gitea-Event", t.EventType.Event()) - req.Header.Add("X-Gitea-Signature", t.Signature) - req.Header.Add("X-Gogs-Delivery", t.UUID) - req.Header.Add("X-Gogs-Event", t.EventType.Event()) - req.Header.Add("X-Gogs-Signature", t.Signature) - req.Header["X-GitHub-Delivery"] = []string{t.UUID} - req.Header["X-GitHub-Event"] = []string{t.EventType.Event()} - - // Record delivery information. - t.RequestInfo = &models.HookRequest{ - Headers: map[string]string{}, - } - for k, vals := range req.Header { - t.RequestInfo.Headers[k] = strings.Join(vals, ",") - } - - t.ResponseInfo = &models.HookResponse{ - Headers: map[string]string{}, - } - - defer func() { - t.Delivered = time.Now().UnixNano() - if t.IsSucceed { - log.Trace("Hook delivered: %s", t.UUID) - } else { - log.Trace("Hook delivery failed: %s", t.UUID) - } - - if err := models.UpdateHookTask(t); err != nil { - log.Error("UpdateHookTask [%d]: %v", t.ID, err) - } - - // Update webhook last delivery status. - w, err := models.GetWebhookByID(t.HookID) - if err != nil { - log.Error("GetWebhookByID: %v", err) - return - } - if t.IsSucceed { - w.LastStatus = models.HookStatusSucceed - } else { - w.LastStatus = models.HookStatusFail - } - if err = models.UpdateWebhookLastStatus(w); err != nil { - log.Error("UpdateWebhookLastStatus: %v", err) - return - } - }() - - resp, err := webhookHTTPClient.Do(req) - if err != nil { - t.ResponseInfo.Body = fmt.Sprintf("Delivery: %v", err) - return err - } - defer resp.Body.Close() - - // Status code is 20x can be seen as succeed. - t.IsSucceed = resp.StatusCode/100 == 2 - t.ResponseInfo.Status = resp.StatusCode - for k, vals := range resp.Header { - t.ResponseInfo.Headers[k] = strings.Join(vals, ",") - } - - p, err := ioutil.ReadAll(resp.Body) - if err != nil { - t.ResponseInfo.Body = fmt.Sprintf("read body: %s", err) - return err - } - t.ResponseInfo.Body = string(p) - return nil -} - -// DeliverHooks checks and delivers undelivered hooks. -// FIXME: graceful: This would likely benefit from either a worker pool with dummy queue -// or a full queue. Then more hooks could be sent at same time. -func DeliverHooks(ctx context.Context) { - select { - case <-ctx.Done(): - return - default: - } - tasks, err := models.FindUndeliveredHookTasks() - if err != nil { - log.Error("DeliverHooks: %v", err) - return - } - - // Update hook task status. - for _, t := range tasks { - select { - case <-ctx.Done(): - return - default: - } - if err = Deliver(t); err != nil { - log.Error("deliver: %v", err) - } - } - - // Start listening on new hook requests. - for { - select { - case <-ctx.Done(): - hookQueue.Close() - return - case repoIDStr := <-hookQueue.Queue(): - log.Trace("DeliverHooks [repo_id: %v]", repoIDStr) - hookQueue.Remove(repoIDStr) - - repoID, err := com.StrTo(repoIDStr).Int64() - if err != nil { - log.Error("Invalid repo ID: %s", repoIDStr) - continue - } - - tasks, err := models.FindRepoUndeliveredHookTasks(repoID) - if err != nil { - log.Error("Get repository [%d] hook tasks: %v", repoID, err) - continue - } - for _, t := range tasks { - select { - case <-ctx.Done(): - return - default: - } - if err = Deliver(t); err != nil { - log.Error("deliver: %v", err) - } - } - } - } - -} - -var ( - webhookHTTPClient *http.Client - once sync.Once - hostMatchers []glob.Glob -) - -func webhookProxy() func(req *http.Request) (*url.URL, error) { - if setting.Webhook.ProxyURL == "" { - return http.ProxyFromEnvironment - } - - once.Do(func() { - for _, h := range setting.Webhook.ProxyHosts { - if g, err := glob.Compile(h); err == nil { - hostMatchers = append(hostMatchers, g) - } else { - log.Error("glob.Compile %s failed: %v", h, err) - } - } - }) - - return func(req *http.Request) (*url.URL, error) { - for _, v := range hostMatchers { - if v.Match(req.URL.Host) { - return http.ProxyURL(setting.Webhook.ProxyURLFixed)(req) - } - } - return http.ProxyFromEnvironment(req) - } -} - -// InitDeliverHooks starts the hooks delivery thread -func InitDeliverHooks() { - timeout := time.Duration(setting.Webhook.DeliverTimeout) * time.Second - - webhookHTTPClient = &http.Client{ - Transport: &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: setting.Webhook.SkipTLSVerify}, - Proxy: webhookProxy(), - Dial: func(netw, addr string) (net.Conn, error) { - conn, err := net.DialTimeout(netw, addr, timeout) - if err != nil { - return nil, err - } - - return conn, conn.SetDeadline(time.Now().Add(timeout)) - }, - }, - } - - go graceful.GetManager().RunWithShutdownContext(DeliverHooks) -} diff --git a/modules/webhook/deliver_test.go b/modules/webhook/deliver_test.go deleted file mode 100644 index cfc99d796a..0000000000 --- a/modules/webhook/deliver_test.go +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2019 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 ( - "net/http" - "net/url" - "testing" - - "code.gitea.io/gitea/modules/setting" - "github.com/stretchr/testify/assert" -) - -func TestWebhookProxy(t *testing.T) { - setting.Webhook.ProxyURL = "http://localhost:8080" - setting.Webhook.ProxyURLFixed, _ = url.Parse(setting.Webhook.ProxyURL) - setting.Webhook.ProxyHosts = []string{"*.discordapp.com", "discordapp.com"} - - var kases = map[string]string{ - "https://discordapp.com/api/webhooks/xxxxxxxxx/xxxxxxxxxxxxxxxxxxx": "http://localhost:8080", - "http://s.discordapp.com/assets/xxxxxx": "http://localhost:8080", - "http://github.com/a/b": "", - } - - for reqURL, proxyURL := range kases { - req, err := http.NewRequest("POST", reqURL, nil) - assert.NoError(t, err) - - u, err := webhookProxy()(req) - assert.NoError(t, err) - if proxyURL == "" { - assert.Nil(t, u) - } else { - assert.EqualValues(t, proxyURL, u.String()) - } - } -} diff --git a/modules/webhook/dingtalk.go b/modules/webhook/dingtalk.go deleted file mode 100644 index a9032db046..0000000000 --- a/modules/webhook/dingtalk.go +++ /dev/null @@ -1,270 +0,0 @@ -// 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 webhook - -import ( - "encoding/json" - "fmt" - "strings" - - "code.gitea.io/gitea/models" - "code.gitea.io/gitea/modules/git" - api "code.gitea.io/gitea/modules/structs" - - dingtalk "github.com/lunny/dingtalk_webhook" -) - -type ( - // DingtalkPayload represents - DingtalkPayload dingtalk.Payload -) - -var ( - _ PayloadConvertor = &DingtalkPayload{} -) - -// SetSecret sets the dingtalk secret -func (d *DingtalkPayload) SetSecret(_ string) {} - -// JSONPayload Marshals the DingtalkPayload to json -func (d *DingtalkPayload) JSONPayload() ([]byte, error) { - data, err := json.MarshalIndent(d, "", " ") - if err != nil { - return []byte{}, err - } - return data, nil -} - -// Create implements PayloadConvertor Create method -func (d *DingtalkPayload) 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 &DingtalkPayload{ - MsgType: "actionCard", - ActionCard: dingtalk.ActionCard{ - Text: title, - Title: title, - HideAvatar: "0", - SingleTitle: fmt.Sprintf("view ref %s", refName), - SingleURL: p.Repo.HTMLURL + "/src/" + refName, - }, - }, nil -} - -// Delete implements PayloadConvertor Delete method -func (d *DingtalkPayload) 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 &DingtalkPayload{ - MsgType: "actionCard", - ActionCard: dingtalk.ActionCard{ - Text: title, - Title: title, - HideAvatar: "0", - SingleTitle: fmt.Sprintf("view ref %s", refName), - SingleURL: p.Repo.HTMLURL + "/src/" + refName, - }, - }, nil -} - -// Fork implements PayloadConvertor Fork method -func (d *DingtalkPayload) Fork(p *api.ForkPayload) (api.Payloader, error) { - title := fmt.Sprintf("%s is forked to %s", p.Forkee.FullName, p.Repo.FullName) - - return &DingtalkPayload{ - MsgType: "actionCard", - ActionCard: dingtalk.ActionCard{ - Text: title, - Title: title, - HideAvatar: "0", - SingleTitle: fmt.Sprintf("view forked repo %s", p.Repo.FullName), - SingleURL: p.Repo.HTMLURL, - }, - }, nil -} - -// Push implements PayloadConvertor Push method -func (d *DingtalkPayload) Push(p *api.PushPayload) (api.Payloader, 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 -} - -// Issue implements PayloadConvertor Issue method -func (d *DingtalkPayload) Issue(p *api.IssuePayload) (api.Payloader, error) { - text, issueTitle, attachmentText, _ := getIssuesPayloadInfo(p, noneLinkFormatter, true) - - return &DingtalkPayload{ - MsgType: "actionCard", - ActionCard: dingtalk.ActionCard{ - Text: text + "\r\n\r\n" + attachmentText, - //Markdown: "# " + title + "\n" + text, - Title: issueTitle, - HideAvatar: "0", - SingleTitle: "view issue", - SingleURL: p.Issue.HTMLURL, - }, - }, nil -} - -// IssueComment implements PayloadConvertor IssueComment method -func (d *DingtalkPayload) IssueComment(p *api.IssueCommentPayload) (api.Payloader, error) { - text, issueTitle, _ := getIssueCommentPayloadInfo(p, noneLinkFormatter, true) - - return &DingtalkPayload{ - MsgType: "actionCard", - ActionCard: dingtalk.ActionCard{ - Text: text + "\r\n\r\n" + p.Comment.Body, - Title: issueTitle, - HideAvatar: "0", - SingleTitle: "view issue comment", - SingleURL: p.Comment.HTMLURL, - }, - }, nil -} - -// PullRequest implements PayloadConvertor PullRequest method -func (d *DingtalkPayload) PullRequest(p *api.PullRequestPayload) (api.Payloader, error) { - text, issueTitle, attachmentText, _ := getPullRequestPayloadInfo(p, noneLinkFormatter, true) - - return &DingtalkPayload{ - MsgType: "actionCard", - ActionCard: dingtalk.ActionCard{ - Text: text + "\r\n\r\n" + attachmentText, - //Markdown: "# " + title + "\n" + text, - Title: issueTitle, - HideAvatar: "0", - SingleTitle: "view pull request", - SingleURL: p.PullRequest.HTMLURL, - }, - }, nil -} - -// Review implements PayloadConvertor Review method -func (d *DingtalkPayload) Review(p *api.PullRequestPayload, event models.HookEventType) (api.Payloader, error) { - var text, title string - switch p.Action { - case api.HookIssueReviewed: - 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 &DingtalkPayload{ - MsgType: "actionCard", - ActionCard: dingtalk.ActionCard{ - Text: title + "\r\n\r\n" + text, - Title: title, - HideAvatar: "0", - SingleTitle: "view pull request", - SingleURL: p.PullRequest.HTMLURL, - }, - }, nil -} - -// Repository implements PayloadConvertor Repository method -func (d *DingtalkPayload) Repository(p *api.RepositoryPayload) (api.Payloader, 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 -} - -// Release implements PayloadConvertor Release method -func (d *DingtalkPayload) Release(p *api.ReleasePayload) (api.Payloader, error) { - text, _ := getReleasePayloadInfo(p, noneLinkFormatter, true) - - return &DingtalkPayload{ - MsgType: "actionCard", - ActionCard: dingtalk.ActionCard{ - Text: text, - Title: text, - HideAvatar: "0", - SingleTitle: "view release", - SingleURL: p.Release.URL, - }, - }, nil -} - -// GetDingtalkPayload converts a ding talk webhook into a DingtalkPayload -func GetDingtalkPayload(p api.Payloader, event models.HookEventType, meta string) (api.Payloader, error) { - return convertPayloader(new(DingtalkPayload), p, event) -} diff --git a/modules/webhook/dingtalk_test.go b/modules/webhook/dingtalk_test.go deleted file mode 100644 index e5aa0fca36..0000000000 --- a/modules/webhook/dingtalk_test.go +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright 2019 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 ( - "testing" - - api "code.gitea.io/gitea/modules/structs" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestGetDingTalkIssuesPayload(t *testing.T) { - p := issueTestPayload() - d := new(DingtalkPayload) - p.Action = api.HookIssueOpened - pl, err := d.Issue(p) - require.NoError(t, err) - require.NotNil(t, pl) - assert.Equal(t, "#2 crash", pl.(*DingtalkPayload).ActionCard.Title) - assert.Equal(t, "[test/repo] Issue opened: #2 crash by user1\r\n\r\n", pl.(*DingtalkPayload).ActionCard.Text) - - p.Action = api.HookIssueClosed - pl, err = d.Issue(p) - require.NoError(t, err) - require.NotNil(t, pl) - assert.Equal(t, "#2 crash", pl.(*DingtalkPayload).ActionCard.Title) - assert.Equal(t, "[test/repo] Issue closed: #2 crash by user1\r\n\r\n", pl.(*DingtalkPayload).ActionCard.Text) -} diff --git a/modules/webhook/discord.go b/modules/webhook/discord.go deleted file mode 100644 index 530e7adbda..0000000000 --- a/modules/webhook/discord.go +++ /dev/null @@ -1,432 +0,0 @@ -// 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 webhook - -import ( - "encoding/json" - "errors" - "fmt" - "strconv" - "strings" - - "code.gitea.io/gitea/models" - "code.gitea.io/gitea/modules/git" - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/setting" - api "code.gitea.io/gitea/modules/structs" -) - -type ( - // DiscordEmbedFooter for Embed Footer Structure. - DiscordEmbedFooter struct { - Text string `json:"text"` - } - - // DiscordEmbedAuthor for Embed Author Structure - DiscordEmbedAuthor struct { - Name string `json:"name"` - URL string `json:"url"` - IconURL string `json:"icon_url"` - } - - // DiscordEmbedField for Embed Field Structure - DiscordEmbedField struct { - Name string `json:"name"` - Value string `json:"value"` - } - - // DiscordEmbed is for Embed Structure - DiscordEmbed struct { - Title string `json:"title"` - Description string `json:"description"` - URL string `json:"url"` - Color int `json:"color"` - Footer DiscordEmbedFooter `json:"footer"` - Author DiscordEmbedAuthor `json:"author"` - Fields []DiscordEmbedField `json:"fields"` - } - - // DiscordPayload represents - DiscordPayload struct { - Wait bool `json:"wait"` - Content string `json:"content"` - Username string `json:"username"` - AvatarURL string `json:"avatar_url"` - TTS bool `json:"tts"` - Embeds []DiscordEmbed `json:"embeds"` - } - - // DiscordMeta contains the discord metadata - DiscordMeta struct { - Username string `json:"username"` - IconURL string `json:"icon_url"` - } -) - -// GetDiscordHook returns discord metadata -func GetDiscordHook(w *models.Webhook) *DiscordMeta { - s := &DiscordMeta{} - if err := json.Unmarshal([]byte(w.Meta), s); err != nil { - log.Error("webhook.GetDiscordHook(%d): %v", w.ID, err) - } - return s -} - -func color(clr string) int { - if clr != "" { - clr = strings.TrimLeft(clr, "#") - if s, err := strconv.ParseInt(clr, 16, 32); err == nil { - return int(s) - } - } - - return 0 -} - -var ( - greenColor = color("1ac600") - greenColorLight = color("bfe5bf") - yellowColor = color("ffd930") - greyColor = color("4f545c") - purpleColor = color("7289da") - orangeColor = color("eb6420") - orangeColorLight = color("e68d60") - redColor = color("ff3232") -) - -// SetSecret sets the discord secret -func (d *DiscordPayload) SetSecret(_ string) {} - -// JSONPayload Marshals the DiscordPayload to json -func (d *DiscordPayload) JSONPayload() ([]byte, error) { - data, err := json.MarshalIndent(d, "", " ") - if err != nil { - return []byte{}, err - } - return data, nil -} - -var ( - _ PayloadConvertor = &DiscordPayload{} -) - -// Create implements PayloadConvertor Create method -func (d *DiscordPayload) 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 &DiscordPayload{ - Username: d.Username, - AvatarURL: d.AvatarURL, - Embeds: []DiscordEmbed{ - { - Title: title, - URL: p.Repo.HTMLURL + "/src/" + refName, - Color: greenColor, - Author: DiscordEmbedAuthor{ - Name: p.Sender.UserName, - URL: setting.AppURL + p.Sender.UserName, - IconURL: p.Sender.AvatarURL, - }, - }, - }, - }, nil -} - -// Delete implements PayloadConvertor Delete method -func (d *DiscordPayload) Delete(p *api.DeletePayload) (api.Payloader, error) { - // deleted tag/branch - refName := git.RefEndName(p.Ref) - title := fmt.Sprintf("[%s] %s %s deleted", p.Repo.FullName, p.RefType, refName) - - return &DiscordPayload{ - Username: d.Username, - AvatarURL: d.AvatarURL, - Embeds: []DiscordEmbed{ - { - Title: title, - URL: p.Repo.HTMLURL + "/src/" + refName, - Color: redColor, - Author: DiscordEmbedAuthor{ - Name: p.Sender.UserName, - URL: setting.AppURL + p.Sender.UserName, - IconURL: p.Sender.AvatarURL, - }, - }, - }, - }, nil -} - -// Fork implements PayloadConvertor Fork method -func (d *DiscordPayload) Fork(p *api.ForkPayload) (api.Payloader, error) { - title := fmt.Sprintf("%s is forked to %s", p.Forkee.FullName, p.Repo.FullName) - - return &DiscordPayload{ - Username: d.Username, - AvatarURL: d.AvatarURL, - Embeds: []DiscordEmbed{ - { - Title: title, - URL: p.Repo.HTMLURL, - Color: greenColor, - Author: DiscordEmbedAuthor{ - Name: p.Sender.UserName, - URL: setting.AppURL + p.Sender.UserName, - IconURL: p.Sender.AvatarURL, - }, - }, - }, - }, nil -} - -// Push implements PayloadConvertor Push method -func (d *DiscordPayload) Push(p *api.PushPayload) (api.Payloader, error) { - var ( - branchName = git.RefEndName(p.Ref) - commitDesc string - ) - - var titleLink 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", p.Repo.FullName, branchName, commitDesc) - - var text string - // for each commit, generate attachment text - for i, commit := range p.Commits { - text += fmt.Sprintf("[%s](%s) %s - %s", commit.ID[:7], commit.URL, - strings.TrimRight(commit.Message, "\r\n"), commit.Author.Name) - // add linebreak to each commit but the last - if i < len(p.Commits)-1 { - text += "\n" - } - } - - return &DiscordPayload{ - Username: d.Username, - AvatarURL: d.AvatarURL, - Embeds: []DiscordEmbed{ - { - Title: title, - Description: text, - URL: titleLink, - Color: greenColor, - Author: DiscordEmbedAuthor{ - Name: p.Sender.UserName, - URL: setting.AppURL + p.Sender.UserName, - IconURL: p.Sender.AvatarURL, - }, - }, - }, - }, nil -} - -// Issue implements PayloadConvertor Issue method -func (d *DiscordPayload) Issue(p *api.IssuePayload) (api.Payloader, error) { - text, _, attachmentText, color := getIssuesPayloadInfo(p, noneLinkFormatter, false) - - return &DiscordPayload{ - Username: d.Username, - AvatarURL: d.AvatarURL, - Embeds: []DiscordEmbed{ - { - Title: text, - Description: attachmentText, - URL: p.Issue.HTMLURL, - Color: color, - Author: DiscordEmbedAuthor{ - Name: p.Sender.UserName, - URL: setting.AppURL + p.Sender.UserName, - IconURL: p.Sender.AvatarURL, - }, - }, - }, - }, nil -} - -// IssueComment implements PayloadConvertor IssueComment method -func (d *DiscordPayload) IssueComment(p *api.IssueCommentPayload) (api.Payloader, error) { - text, _, color := getIssueCommentPayloadInfo(p, noneLinkFormatter, false) - - return &DiscordPayload{ - Username: d.Username, - AvatarURL: d.AvatarURL, - Embeds: []DiscordEmbed{ - { - Title: text, - Description: p.Comment.Body, - URL: p.Comment.HTMLURL, - Color: color, - Author: DiscordEmbedAuthor{ - Name: p.Sender.UserName, - URL: setting.AppURL + p.Sender.UserName, - IconURL: p.Sender.AvatarURL, - }, - }, - }, - }, nil -} - -// PullRequest implements PayloadConvertor PullRequest method -func (d *DiscordPayload) PullRequest(p *api.PullRequestPayload) (api.Payloader, error) { - text, _, attachmentText, color := getPullRequestPayloadInfo(p, noneLinkFormatter, false) - - return &DiscordPayload{ - Username: d.Username, - AvatarURL: d.AvatarURL, - Embeds: []DiscordEmbed{ - { - Title: text, - Description: attachmentText, - URL: p.PullRequest.HTMLURL, - Color: color, - Author: DiscordEmbedAuthor{ - Name: p.Sender.UserName, - URL: setting.AppURL + p.Sender.UserName, - IconURL: p.Sender.AvatarURL, - }, - }, - }, - }, nil -} - -// Review implements PayloadConvertor Review method -func (d *DiscordPayload) Review(p *api.PullRequestPayload, event models.HookEventType) (api.Payloader, error) { - var text, title string - var color int - switch p.Action { - case api.HookIssueReviewed: - 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 - - switch event { - case models.HookEventPullRequestReviewApproved: - color = greenColor - case models.HookEventPullRequestReviewRejected: - color = redColor - case models.HookEventPullRequestComment: - color = greyColor - default: - color = yellowColor - } - } - - return &DiscordPayload{ - Username: d.Username, - AvatarURL: d.AvatarURL, - Embeds: []DiscordEmbed{ - { - Title: title, - Description: text, - URL: p.PullRequest.HTMLURL, - Color: color, - Author: DiscordEmbedAuthor{ - Name: p.Sender.UserName, - URL: setting.AppURL + p.Sender.UserName, - IconURL: p.Sender.AvatarURL, - }, - }, - }, - }, nil -} - -// Repository implements PayloadConvertor Repository method -func (d *DiscordPayload) Repository(p *api.RepositoryPayload) (api.Payloader, error) { - var title, url string - var color int - switch p.Action { - case api.HookRepoCreated: - title = fmt.Sprintf("[%s] Repository created", p.Repository.FullName) - url = p.Repository.HTMLURL - color = greenColor - case api.HookRepoDeleted: - title = fmt.Sprintf("[%s] Repository deleted", p.Repository.FullName) - color = redColor - } - - return &DiscordPayload{ - Username: d.Username, - AvatarURL: d.AvatarURL, - Embeds: []DiscordEmbed{ - { - Title: title, - URL: url, - Color: color, - Author: DiscordEmbedAuthor{ - Name: p.Sender.UserName, - URL: setting.AppURL + p.Sender.UserName, - IconURL: p.Sender.AvatarURL, - }, - }, - }, - }, nil -} - -// Release implements PayloadConvertor Release method -func (d *DiscordPayload) Release(p *api.ReleasePayload) (api.Payloader, error) { - text, color := getReleasePayloadInfo(p, noneLinkFormatter, false) - - return &DiscordPayload{ - Username: d.Username, - AvatarURL: d.AvatarURL, - Embeds: []DiscordEmbed{ - { - Title: text, - Description: p.Release.Note, - URL: p.Release.URL, - Color: color, - Author: DiscordEmbedAuthor{ - Name: p.Sender.UserName, - URL: setting.AppURL + p.Sender.UserName, - IconURL: p.Sender.AvatarURL, - }, - }, - }, - }, nil -} - -// GetDiscordPayload converts a discord webhook into a DiscordPayload -func GetDiscordPayload(p api.Payloader, event models.HookEventType, meta string) (api.Payloader, error) { - s := new(DiscordPayload) - - discord := &DiscordMeta{} - if err := json.Unmarshal([]byte(meta), &discord); err != nil { - return s, errors.New("GetDiscordPayload meta json:" + err.Error()) - } - s.Username = discord.Username - s.AvatarURL = discord.IconURL - - return convertPayloader(s, p, event) -} - -func parseHookPullRequestEventType(event models.HookEventType) (string, error) { - switch event { - - case models.HookEventPullRequestReviewApproved: - return "approved", nil - case models.HookEventPullRequestReviewRejected: - return "rejected", nil - case models.HookEventPullRequestComment: - return "comment", nil - - default: - return "", errors.New("unknown event type") - } -} diff --git a/modules/webhook/feishu.go b/modules/webhook/feishu.go deleted file mode 100644 index 8e60dbba13..0000000000 --- a/modules/webhook/feishu.go +++ /dev/null @@ -1,190 +0,0 @@ -// 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 (f *FeishuPayload) SetSecret(_ string) {} - -// JSONPayload Marshals the FeishuPayload to json -func (f *FeishuPayload) JSONPayload() ([]byte, error) { - data, err := json.MarshalIndent(f, "", " ") - if err != nil { - return []byte{}, err - } - return data, nil -} - -var ( - _ PayloadConvertor = &FeishuPayload{} -) - -// Create implements PayloadConvertor Create method -func (f *FeishuPayload) 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 &FeishuPayload{ - Text: title, - Title: title, - }, nil -} - -// Delete implements PayloadConvertor Delete method -func (f *FeishuPayload) 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 &FeishuPayload{ - Text: title, - Title: title, - }, nil -} - -// Fork implements PayloadConvertor Fork method -func (f *FeishuPayload) Fork(p *api.ForkPayload) (api.Payloader, error) { - title := fmt.Sprintf("%s is forked to %s", p.Forkee.FullName, p.Repo.FullName) - - return &FeishuPayload{ - Text: title, - Title: title, - }, nil -} - -// Push implements PayloadConvertor Push method -func (f *FeishuPayload) 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 = " - " + 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 -} - -// Issue implements PayloadConvertor Issue method -func (f *FeishuPayload) Issue(p *api.IssuePayload) (api.Payloader, error) { - text, issueTitle, attachmentText, _ := getIssuesPayloadInfo(p, noneLinkFormatter, true) - - return &FeishuPayload{ - Text: text + "\r\n\r\n" + attachmentText, - Title: issueTitle, - }, nil -} - -// IssueComment implements PayloadConvertor IssueComment method -func (f *FeishuPayload) IssueComment(p *api.IssueCommentPayload) (api.Payloader, error) { - text, issueTitle, _ := getIssueCommentPayloadInfo(p, noneLinkFormatter, true) - - return &FeishuPayload{ - Text: text + "\r\n\r\n" + p.Comment.Body, - Title: issueTitle, - }, nil -} - -// PullRequest implements PayloadConvertor PullRequest method -func (f *FeishuPayload) PullRequest(p *api.PullRequestPayload) (api.Payloader, error) { - text, issueTitle, attachmentText, _ := getPullRequestPayloadInfo(p, noneLinkFormatter, true) - - return &FeishuPayload{ - Text: text + "\r\n\r\n" + attachmentText, - Title: issueTitle, - }, nil -} - -// Review implements PayloadConvertor Review method -func (f *FeishuPayload) 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 &FeishuPayload{ - Text: title + "\r\n\r\n" + text, - Title: title, - }, nil -} - -// Repository implements PayloadConvertor Repository method -func (f *FeishuPayload) 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 &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 -} - -// Release implements PayloadConvertor Release method -func (f *FeishuPayload) Release(p *api.ReleasePayload) (api.Payloader, 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) (api.Payloader, error) { - return convertPayloader(new(FeishuPayload), p, event) -} diff --git a/modules/webhook/general.go b/modules/webhook/general.go deleted file mode 100644 index ec247a2410..0000000000 --- a/modules/webhook/general.go +++ /dev/null @@ -1,193 +0,0 @@ -// Copyright 2019 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 ( - "fmt" - "html" - "strings" - - "code.gitea.io/gitea/modules/setting" - api "code.gitea.io/gitea/modules/structs" -) - -type linkFormatter = func(string, string) string - -// noneLinkFormatter does not create a link but just returns the text -func noneLinkFormatter(url string, text string) string { - return text -} - -// htmlLinkFormatter creates a HTML link -func htmlLinkFormatter(url string, text string) string { - return fmt.Sprintf(`<a href="%s">%s</a>`, url, html.EscapeString(text)) -} - -func getIssuesPayloadInfo(p *api.IssuePayload, linkFormatter linkFormatter, withSender bool) (string, string, string, int) { - repoLink := linkFormatter(p.Repository.HTMLURL, p.Repository.FullName) - issueTitle := fmt.Sprintf("#%d %s", p.Index, p.Issue.Title) - titleLink := linkFormatter(fmt.Sprintf("%s/issues/%d", p.Repository.HTMLURL, p.Index), issueTitle) - var text string - color := yellowColor - - switch p.Action { - case api.HookIssueOpened: - text = fmt.Sprintf("[%s] Issue opened: %s", repoLink, titleLink) - color = orangeColor - case api.HookIssueClosed: - text = fmt.Sprintf("[%s] Issue closed: %s", repoLink, titleLink) - color = redColor - case api.HookIssueReOpened: - text = fmt.Sprintf("[%s] Issue re-opened: %s", repoLink, titleLink) - case api.HookIssueEdited: - text = fmt.Sprintf("[%s] Issue edited: %s", repoLink, titleLink) - case api.HookIssueAssigned: - text = fmt.Sprintf("[%s] Issue assigned to %s: %s", repoLink, - linkFormatter(setting.AppURL+p.Issue.Assignee.UserName, p.Issue.Assignee.UserName), titleLink) - color = greenColor - case api.HookIssueUnassigned: - text = fmt.Sprintf("[%s] Issue unassigned: %s", repoLink, titleLink) - case api.HookIssueLabelUpdated: - text = fmt.Sprintf("[%s] Issue labels updated: %s", repoLink, titleLink) - case api.HookIssueLabelCleared: - text = fmt.Sprintf("[%s] Issue labels cleared: %s", repoLink, titleLink) - case api.HookIssueSynchronized: - text = fmt.Sprintf("[%s] Issue synchronized: %s", repoLink, titleLink) - case api.HookIssueMilestoned: - mileStoneLink := fmt.Sprintf("%s/milestone/%d", p.Repository.HTMLURL, p.Issue.Milestone.ID) - text = fmt.Sprintf("[%s] Issue milestoned to %s: %s", repoLink, - linkFormatter(mileStoneLink, p.Issue.Milestone.Title), titleLink) - case api.HookIssueDemilestoned: - text = fmt.Sprintf("[%s] Issue milestone cleared: %s", repoLink, titleLink) - } - if withSender { - text += fmt.Sprintf(" by %s", linkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName)) - } - - var attachmentText string - if p.Action == api.HookIssueOpened || p.Action == api.HookIssueEdited { - attachmentText = p.Issue.Body - } - - return text, issueTitle, attachmentText, color -} - -func getPullRequestPayloadInfo(p *api.PullRequestPayload, linkFormatter linkFormatter, withSender bool) (string, string, string, int) { - repoLink := linkFormatter(p.Repository.HTMLURL, p.Repository.FullName) - issueTitle := fmt.Sprintf("#%d %s", p.Index, p.PullRequest.Title) - titleLink := linkFormatter(p.PullRequest.URL, issueTitle) - var text string - color := yellowColor - - switch p.Action { - case api.HookIssueOpened: - text = fmt.Sprintf("[%s] Pull request opened: %s", repoLink, titleLink) - color = greenColor - case api.HookIssueClosed: - if p.PullRequest.HasMerged { - text = fmt.Sprintf("[%s] Pull request merged: %s", repoLink, titleLink) - color = purpleColor - } else { - text = fmt.Sprintf("[%s] Pull request closed: %s", repoLink, titleLink) - color = redColor - } - case api.HookIssueReOpened: - text = fmt.Sprintf("[%s] Pull request re-opened: %s", repoLink, titleLink) - case api.HookIssueEdited: - text = fmt.Sprintf("[%s] Pull request edited: %s", repoLink, titleLink) - case api.HookIssueAssigned: - list := make([]string, len(p.PullRequest.Assignees)) - for i, user := range p.PullRequest.Assignees { - list[i] = linkFormatter(setting.AppURL+user.UserName, user.UserName) - } - text = fmt.Sprintf("[%s] Pull request assigned: %s to %s", repoLink, - strings.Join(list, ", "), titleLink) - color = greenColor - case api.HookIssueUnassigned: - text = fmt.Sprintf("[%s] Pull request unassigned: %s", repoLink, titleLink) - case api.HookIssueLabelUpdated: - text = fmt.Sprintf("[%s] Pull request labels updated: %s", repoLink, titleLink) - case api.HookIssueLabelCleared: - text = fmt.Sprintf("[%s] Pull request labels cleared: %s", repoLink, titleLink) - case api.HookIssueSynchronized: - text = fmt.Sprintf("[%s] Pull request synchronized: %s", repoLink, titleLink) - case api.HookIssueMilestoned: - mileStoneLink := fmt.Sprintf("%s/milestone/%d", p.Repository.HTMLURL, p.PullRequest.Milestone.ID) - text = fmt.Sprintf("[%s] Pull request milestoned: %s to %s", repoLink, - linkFormatter(mileStoneLink, p.PullRequest.Milestone.Title), titleLink) - case api.HookIssueDemilestoned: - text = fmt.Sprintf("[%s] Pull request milestone cleared: %s", repoLink, titleLink) - case api.HookIssueReviewed: - text = fmt.Sprintf("[%s] Pull request reviewed: %s", repoLink, titleLink) - } - if withSender { - text += fmt.Sprintf(" by %s", linkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName)) - } - - var attachmentText string - if p.Action == api.HookIssueOpened || p.Action == api.HookIssueEdited { - attachmentText = p.PullRequest.Body - } - - return text, issueTitle, attachmentText, color -} - -func getReleasePayloadInfo(p *api.ReleasePayload, linkFormatter linkFormatter, withSender bool) (text string, color int) { - repoLink := linkFormatter(p.Repository.HTMLURL, p.Repository.FullName) - refLink := linkFormatter(p.Repository.HTMLURL+"/src/"+p.Release.TagName, p.Release.TagName) - - switch p.Action { - case api.HookReleasePublished: - text = fmt.Sprintf("[%s] Release created: %s", repoLink, refLink) - color = greenColor - case api.HookReleaseUpdated: - text = fmt.Sprintf("[%s] Release updated: %s", repoLink, refLink) - color = yellowColor - case api.HookReleaseDeleted: - text = fmt.Sprintf("[%s] Release deleted: %s", repoLink, refLink) - color = redColor - } - if withSender { - text += fmt.Sprintf(" by %s", linkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName)) - } - - return text, color -} - -func getIssueCommentPayloadInfo(p *api.IssueCommentPayload, linkFormatter linkFormatter, withSender bool) (string, string, int) { - repoLink := linkFormatter(p.Repository.HTMLURL, p.Repository.FullName) - issueTitle := fmt.Sprintf("#%d %s", p.Issue.Index, p.Issue.Title) - - var text, typ, titleLink string - color := yellowColor - - if p.IsPull { - typ = "pull request" - titleLink = linkFormatter(p.Comment.PRURL, issueTitle) - } else { - typ = "issue" - titleLink = linkFormatter(p.Comment.IssueURL, issueTitle) - } - - switch p.Action { - case api.HookIssueCommentCreated: - text = fmt.Sprintf("[%s] New comment on %s %s", repoLink, typ, titleLink) - if p.IsPull { - color = greenColorLight - } else { - color = orangeColorLight - } - case api.HookIssueCommentEdited: - text = fmt.Sprintf("[%s] Comment edited on %s %s", repoLink, typ, titleLink) - case api.HookIssueCommentDeleted: - text = fmt.Sprintf("[%s] Comment deleted on %s %s", repoLink, typ, titleLink) - color = redColor - } - if withSender { - text += fmt.Sprintf(" by %s", linkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName)) - } - - return text, issueTitle, color -} diff --git a/modules/webhook/general_test.go b/modules/webhook/general_test.go deleted file mode 100644 index 3033b57880..0000000000 --- a/modules/webhook/general_test.go +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright 2019 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 ( - api "code.gitea.io/gitea/modules/structs" -) - -func issueTestPayload() *api.IssuePayload { - return &api.IssuePayload{ - Index: 2, - Sender: &api.User{ - UserName: "user1", - }, - Repository: &api.Repository{ - HTMLURL: "http://localhost:3000/test/repo", - Name: "repo", - FullName: "test/repo", - }, - Issue: &api.Issue{ - ID: 2, - Index: 2, - URL: "http://localhost:3000/api/v1/repos/test/repo/issues/2", - Title: "crash", - }, - } -} - -func issueCommentTestPayload() *api.IssueCommentPayload { - return &api.IssueCommentPayload{ - Action: api.HookIssueCommentCreated, - Sender: &api.User{ - UserName: "user1", - }, - Repository: &api.Repository{ - HTMLURL: "http://localhost:3000/test/repo", - Name: "repo", - FullName: "test/repo", - }, - Comment: &api.Comment{ - HTMLURL: "http://localhost:3000/test/repo/issues/2#issuecomment-4", - IssueURL: "http://localhost:3000/test/repo/issues/2", - Body: "more info needed", - }, - Issue: &api.Issue{ - ID: 2, - Index: 2, - URL: "http://localhost:3000/api/v1/repos/test/repo/issues/2", - Title: "crash", - Body: "this happened", - }, - } -} - -func pullRequestCommentTestPayload() *api.IssueCommentPayload { - return &api.IssueCommentPayload{ - Action: api.HookIssueCommentCreated, - Sender: &api.User{ - UserName: "user1", - }, - Repository: &api.Repository{ - HTMLURL: "http://localhost:3000/test/repo", - Name: "repo", - FullName: "test/repo", - }, - Comment: &api.Comment{ - HTMLURL: "http://localhost:3000/test/repo/pulls/2#issuecomment-4", - PRURL: "http://localhost:3000/test/repo/pulls/2", - Body: "changes requested", - }, - Issue: &api.Issue{ - ID: 2, - Index: 2, - URL: "http://localhost:3000/api/v1/repos/test/repo/issues/2", - Title: "Fix bug", - Body: "fixes bug #2", - }, - IsPull: true, - } -} - -func pullReleaseTestPayload() *api.ReleasePayload { - return &api.ReleasePayload{ - Action: api.HookReleasePublished, - Sender: &api.User{ - UserName: "user1", - }, - Repository: &api.Repository{ - HTMLURL: "http://localhost:3000/test/repo", - Name: "repo", - FullName: "test/repo", - }, - Release: &api.Release{ - TagName: "v1.0", - Target: "master", - Title: "First stable release", - URL: "http://localhost:3000/api/v1/repos/test/repo/releases/2", - }, - } -} - -func pullRequestTestPayload() *api.PullRequestPayload { - return &api.PullRequestPayload{ - Action: api.HookIssueOpened, - Index: 2, - Sender: &api.User{ - UserName: "user1", - }, - Repository: &api.Repository{ - HTMLURL: "http://localhost:3000/test/repo", - Name: "repo", - FullName: "test/repo", - }, - PullRequest: &api.PullRequest{ - ID: 2, - Index: 2, - URL: "http://localhost:3000/test/repo/pulls/12", - Title: "Fix bug", - Body: "fixes bug #2", - Mergeable: true, - }, - } -} diff --git a/modules/webhook/main_test.go b/modules/webhook/main_test.go deleted file mode 100644 index 6cb0cffe49..0000000000 --- a/modules/webhook/main_test.go +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2019 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 ( - "path/filepath" - "testing" - - "code.gitea.io/gitea/models" -) - -func TestMain(m *testing.M) { - models.MainTest(m, filepath.Join("..", "..")) -} diff --git a/modules/webhook/matrix.go b/modules/webhook/matrix.go deleted file mode 100644 index 063147198a..0000000000 --- a/modules/webhook/matrix.go +++ /dev/null @@ -1,309 +0,0 @@ -// 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 ( - "crypto/sha1" - "encoding/json" - "errors" - "fmt" - "html" - "net/http" - "regexp" - "strings" - - "code.gitea.io/gitea/models" - "code.gitea.io/gitea/modules/git" - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/setting" - api "code.gitea.io/gitea/modules/structs" -) - -const matrixPayloadSizeLimit = 1024 * 64 - -// MatrixMeta contains the Matrix metadata -type MatrixMeta struct { - HomeserverURL string `json:"homeserver_url"` - Room string `json:"room_id"` - AccessToken string `json:"access_token"` - MessageType int `json:"message_type"` -} - -var messageTypeText = map[int]string{ - 1: "m.notice", - 2: "m.text", -} - -// GetMatrixHook returns Matrix metadata -func GetMatrixHook(w *models.Webhook) *MatrixMeta { - s := &MatrixMeta{} - if err := json.Unmarshal([]byte(w.Meta), s); err != nil { - log.Error("webhook.GetMatrixHook(%d): %v", w.ID, err) - } - return s -} - -// MatrixPayloadUnsafe contains the (unsafe) payload for a Matrix room -type MatrixPayloadUnsafe struct { - MatrixPayloadSafe - AccessToken string `json:"access_token"` -} - -var ( - _ PayloadConvertor = &MatrixPayloadUnsafe{} -) - -// safePayload "converts" a unsafe payload to a safe payload -func (m *MatrixPayloadUnsafe) safePayload() *MatrixPayloadSafe { - return &MatrixPayloadSafe{ - Body: m.Body, - MsgType: m.MsgType, - Format: m.Format, - FormattedBody: m.FormattedBody, - Commits: m.Commits, - } -} - -// MatrixPayloadSafe contains (safe) payload for a Matrix room -type MatrixPayloadSafe struct { - Body string `json:"body"` - MsgType string `json:"msgtype"` - Format string `json:"format"` - FormattedBody string `json:"formatted_body"` - Commits []*api.PayloadCommit `json:"io.gitea.commits,omitempty"` -} - -// SetSecret sets the Matrix secret -func (m *MatrixPayloadUnsafe) SetSecret(_ string) {} - -// JSONPayload Marshals the MatrixPayloadUnsafe to json -func (m *MatrixPayloadUnsafe) JSONPayload() ([]byte, error) { - data, err := json.MarshalIndent(m, "", " ") - if err != nil { - return []byte{}, err - } - return data, nil -} - -// MatrixLinkFormatter creates a link compatible with Matrix -func MatrixLinkFormatter(url string, text string) string { - return fmt.Sprintf(`<a href="%s">%s</a>`, html.EscapeString(url), html.EscapeString(text)) -} - -// MatrixLinkToRef Matrix-formatter link to a repo ref -func MatrixLinkToRef(repoURL, ref string) string { - refName := git.RefEndName(ref) - switch { - case strings.HasPrefix(ref, git.BranchPrefix): - return MatrixLinkFormatter(repoURL+"/src/branch/"+refName, refName) - case strings.HasPrefix(ref, git.TagPrefix): - return MatrixLinkFormatter(repoURL+"/src/tag/"+refName, refName) - default: - return MatrixLinkFormatter(repoURL+"/src/commit/"+refName, refName) - } -} - -// Create implements PayloadConvertor Create method -func (m *MatrixPayloadUnsafe) Create(p *api.CreatePayload) (api.Payloader, error) { - repoLink := MatrixLinkFormatter(p.Repo.HTMLURL, p.Repo.FullName) - refLink := MatrixLinkToRef(p.Repo.HTMLURL, p.Ref) - text := fmt.Sprintf("[%s:%s] %s created by %s", repoLink, refLink, p.RefType, p.Sender.UserName) - - return getMatrixPayloadUnsafe(text, nil, m.AccessToken, m.MsgType), nil -} - -// Delete composes Matrix payload for delete a branch or tag. -func (m *MatrixPayloadUnsafe) Delete(p *api.DeletePayload) (api.Payloader, error) { - refName := git.RefEndName(p.Ref) - repoLink := MatrixLinkFormatter(p.Repo.HTMLURL, p.Repo.FullName) - text := fmt.Sprintf("[%s:%s] %s deleted by %s", repoLink, refName, p.RefType, p.Sender.UserName) - - return getMatrixPayloadUnsafe(text, nil, m.AccessToken, m.MsgType), nil -} - -// Fork composes Matrix payload for forked by a repository. -func (m *MatrixPayloadUnsafe) Fork(p *api.ForkPayload) (api.Payloader, error) { - baseLink := MatrixLinkFormatter(p.Forkee.HTMLURL, p.Forkee.FullName) - forkLink := MatrixLinkFormatter(p.Repo.HTMLURL, p.Repo.FullName) - text := fmt.Sprintf("%s is forked to %s", baseLink, forkLink) - - return getMatrixPayloadUnsafe(text, nil, m.AccessToken, m.MsgType), nil -} - -// Issue implements PayloadConvertor Issue method -func (m *MatrixPayloadUnsafe) Issue(p *api.IssuePayload) (api.Payloader, error) { - text, _, _, _ := getIssuesPayloadInfo(p, MatrixLinkFormatter, true) - - return getMatrixPayloadUnsafe(text, nil, m.AccessToken, m.MsgType), nil -} - -// IssueComment implements PayloadConvertor IssueComment method -func (m *MatrixPayloadUnsafe) IssueComment(p *api.IssueCommentPayload) (api.Payloader, error) { - text, _, _ := getIssueCommentPayloadInfo(p, MatrixLinkFormatter, true) - - return getMatrixPayloadUnsafe(text, nil, m.AccessToken, m.MsgType), nil -} - -// Release implements PayloadConvertor Release method -func (m *MatrixPayloadUnsafe) Release(p *api.ReleasePayload) (api.Payloader, error) { - text, _ := getReleasePayloadInfo(p, MatrixLinkFormatter, true) - - return getMatrixPayloadUnsafe(text, nil, m.AccessToken, m.MsgType), nil -} - -// Push implements PayloadConvertor Push method -func (m *MatrixPayloadUnsafe) Push(p *api.PushPayload) (api.Payloader, error) { - var commitDesc string - - if len(p.Commits) == 1 { - commitDesc = "1 commit" - } else { - commitDesc = fmt.Sprintf("%d commits", len(p.Commits)) - } - - repoLink := MatrixLinkFormatter(p.Repo.HTMLURL, p.Repo.FullName) - branchLink := MatrixLinkToRef(p.Repo.HTMLURL, p.Ref) - text := fmt.Sprintf("[%s] %s pushed %s to %s:<br>", repoLink, p.Pusher.UserName, commitDesc, branchLink) - - // for each commit, generate a new line text - for i, commit := range p.Commits { - text += fmt.Sprintf("%s: %s - %s", MatrixLinkFormatter(commit.URL, commit.ID[:7]), commit.Message, commit.Author.Name) - // add linebreak to each commit but the last - if i < len(p.Commits)-1 { - text += "<br>" - } - - } - - return getMatrixPayloadUnsafe(text, p.Commits, m.AccessToken, m.MsgType), nil -} - -// PullRequest implements PayloadConvertor PullRequest method -func (m *MatrixPayloadUnsafe) PullRequest(p *api.PullRequestPayload) (api.Payloader, error) { - text, _, _, _ := getPullRequestPayloadInfo(p, MatrixLinkFormatter, true) - - return getMatrixPayloadUnsafe(text, nil, m.AccessToken, m.MsgType), nil -} - -// Review implements PayloadConvertor Review method -func (m *MatrixPayloadUnsafe) Review(p *api.PullRequestPayload, event models.HookEventType) (api.Payloader, error) { - senderLink := MatrixLinkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName) - title := fmt.Sprintf("#%d %s", p.Index, p.PullRequest.Title) - titleLink := fmt.Sprintf("%s/pulls/%d", p.Repository.HTMLURL, p.Index) - repoLink := MatrixLinkFormatter(p.Repository.HTMLURL, p.Repository.FullName) - var text string - - switch p.Action { - case api.HookIssueReviewed: - action, err := parseHookPullRequestEventType(event) - if err != nil { - return nil, err - } - - text = fmt.Sprintf("[%s] Pull request review %s: [%s](%s) by %s", repoLink, action, title, titleLink, senderLink) - } - - return getMatrixPayloadUnsafe(text, nil, m.AccessToken, m.MsgType), nil -} - -// Repository implements PayloadConvertor Repository method -func (m *MatrixPayloadUnsafe) Repository(p *api.RepositoryPayload) (api.Payloader, error) { - senderLink := MatrixLinkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName) - repoLink := MatrixLinkFormatter(p.Repository.HTMLURL, p.Repository.FullName) - var text string - - switch p.Action { - case api.HookRepoCreated: - text = fmt.Sprintf("[%s] Repository created by %s", repoLink, senderLink) - case api.HookRepoDeleted: - text = fmt.Sprintf("[%s] Repository deleted by %s", repoLink, senderLink) - } - - return getMatrixPayloadUnsafe(text, nil, m.AccessToken, m.MsgType), nil -} - -// GetMatrixPayload converts a Matrix webhook into a MatrixPayloadUnsafe -func GetMatrixPayload(p api.Payloader, event models.HookEventType, meta string) (api.Payloader, error) { - s := new(MatrixPayloadUnsafe) - - matrix := &MatrixMeta{} - if err := json.Unmarshal([]byte(meta), &matrix); err != nil { - return s, errors.New("GetMatrixPayload meta json:" + err.Error()) - } - - s.AccessToken = matrix.AccessToken - s.MsgType = messageTypeText[matrix.MessageType] - - return convertPayloader(s, p, event) -} - -func getMatrixPayloadUnsafe(text string, commits []*api.PayloadCommit, accessToken, msgType string) *MatrixPayloadUnsafe { - p := MatrixPayloadUnsafe{} - p.AccessToken = accessToken - p.FormattedBody = text - p.Body = getMessageBody(text) - p.Format = "org.matrix.custom.html" - p.MsgType = msgType - p.Commits = commits - return &p -} - -var urlRegex = regexp.MustCompile(`<a [^>]*?href="([^">]*?)">(.*?)</a>`) - -func getMessageBody(htmlText string) string { - htmlText = urlRegex.ReplaceAllString(htmlText, "[$2]($1)") - htmlText = strings.ReplaceAll(htmlText, "<br>", "\n") - return htmlText -} - -// getMatrixHookRequest creates a new request which contains an Authorization header. -// The access_token is removed from t.PayloadContent -func getMatrixHookRequest(t *models.HookTask) (*http.Request, error) { - payloadunsafe := MatrixPayloadUnsafe{} - if err := json.Unmarshal([]byte(t.PayloadContent), &payloadunsafe); err != nil { - log.Error("Matrix Hook delivery failed: %v", err) - return nil, err - } - - payloadsafe := payloadunsafe.safePayload() - - var payload []byte - var err error - if payload, err = json.MarshalIndent(payloadsafe, "", " "); err != nil { - return nil, err - } - if len(payload) >= matrixPayloadSizeLimit { - return nil, fmt.Errorf("getMatrixHookRequest: payload size %d > %d", len(payload), matrixPayloadSizeLimit) - } - t.PayloadContent = string(payload) - - txnID, err := getMatrixTxnID(payload) - if err != nil { - return nil, fmt.Errorf("getMatrixHookRequest: unable to hash payload: %+v", err) - } - - t.URL = fmt.Sprintf("%s/%s", t.URL, txnID) - - req, err := http.NewRequest(t.HTTPMethod, t.URL, strings.NewReader(string(payload))) - if err != nil { - return nil, err - } - - req.Header.Set("Content-Type", "application/json") - req.Header.Add("Authorization", "Bearer "+payloadunsafe.AccessToken) - - return req, nil -} - -// getMatrixTxnID creates a txnID based on the payload to ensure idempotency -func getMatrixTxnID(payload []byte) (string, error) { - h := sha1.New() - _, err := h.Write(payload) - if err != nil { - return "", err - } - - return fmt.Sprintf("%x", h.Sum(nil)), nil -} diff --git a/modules/webhook/matrix_test.go b/modules/webhook/matrix_test.go deleted file mode 100644 index 771146f2f3..0000000000 --- a/modules/webhook/matrix_test.go +++ /dev/null @@ -1,181 +0,0 @@ -// 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 ( - "testing" - - "code.gitea.io/gitea/models" - api "code.gitea.io/gitea/modules/structs" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestMatrixIssuesPayloadOpened(t *testing.T) { - p := issueTestPayload() - m := new(MatrixPayloadUnsafe) - - p.Action = api.HookIssueOpened - pl, err := m.Issue(p) - require.NoError(t, err) - require.NotNil(t, pl) - assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] Issue opened: [#2 crash](http://localhost:3000/test/repo/issues/2) by [user1](https://try.gitea.io/user1)", pl.(*MatrixPayloadUnsafe).Body) - assert.Equal(t, "[<a href=\"http://localhost:3000/test/repo\">test/repo</a>] Issue opened: <a href=\"http://localhost:3000/test/repo/issues/2\">#2 crash</a> by <a href=\"https://try.gitea.io/user1\">user1</a>", pl.(*MatrixPayloadUnsafe).FormattedBody) - - p.Action = api.HookIssueClosed - pl, err = m.Issue(p) - require.NoError(t, err) - require.NotNil(t, pl) - assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] Issue closed: [#2 crash](http://localhost:3000/test/repo/issues/2) by [user1](https://try.gitea.io/user1)", pl.(*MatrixPayloadUnsafe).Body) - assert.Equal(t, "[<a href=\"http://localhost:3000/test/repo\">test/repo</a>] Issue closed: <a href=\"http://localhost:3000/test/repo/issues/2\">#2 crash</a> by <a href=\"https://try.gitea.io/user1\">user1</a>", pl.(*MatrixPayloadUnsafe).FormattedBody) -} - -func TestMatrixIssueCommentPayload(t *testing.T) { - p := issueCommentTestPayload() - m := new(MatrixPayloadUnsafe) - - pl, err := m.IssueComment(p) - require.NoError(t, err) - require.NotNil(t, pl) - - assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] New comment on issue [#2 crash](http://localhost:3000/test/repo/issues/2) by [user1](https://try.gitea.io/user1)", pl.(*MatrixPayloadUnsafe).Body) - assert.Equal(t, "[<a href=\"http://localhost:3000/test/repo\">test/repo</a>] New comment on issue <a href=\"http://localhost:3000/test/repo/issues/2\">#2 crash</a> by <a href=\"https://try.gitea.io/user1\">user1</a>", pl.(*MatrixPayloadUnsafe).FormattedBody) -} - -func TestMatrixPullRequestCommentPayload(t *testing.T) { - p := pullRequestCommentTestPayload() - m := new(MatrixPayloadUnsafe) - - pl, err := m.IssueComment(p) - require.NoError(t, err) - require.NotNil(t, pl) - - assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] New comment on pull request [#2 Fix bug](http://localhost:3000/test/repo/pulls/2) by [user1](https://try.gitea.io/user1)", pl.(*MatrixPayloadUnsafe).Body) - assert.Equal(t, "[<a href=\"http://localhost:3000/test/repo\">test/repo</a>] New comment on pull request <a href=\"http://localhost:3000/test/repo/pulls/2\">#2 Fix bug</a> by <a href=\"https://try.gitea.io/user1\">user1</a>", pl.(*MatrixPayloadUnsafe).FormattedBody) -} - -func TestMatrixReleasePayload(t *testing.T) { - p := pullReleaseTestPayload() - m := new(MatrixPayloadUnsafe) - - pl, err := m.Release(p) - require.NoError(t, err) - require.NotNil(t, pl) - - assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] Release created: [v1.0](http://localhost:3000/test/repo/src/v1.0) by [user1](https://try.gitea.io/user1)", pl.(*MatrixPayloadUnsafe).Body) - assert.Equal(t, "[<a href=\"http://localhost:3000/test/repo\">test/repo</a>] Release created: <a href=\"http://localhost:3000/test/repo/src/v1.0\">v1.0</a> by <a href=\"https://try.gitea.io/user1\">user1</a>", pl.(*MatrixPayloadUnsafe).FormattedBody) -} - -func TestMatrixPullRequestPayload(t *testing.T) { - p := pullRequestTestPayload() - m := new(MatrixPayloadUnsafe) - - pl, err := m.PullRequest(p) - require.NoError(t, err) - require.NotNil(t, pl) - - assert.Equal(t, "[[test/repo](http://localhost:3000/test/repo)] Pull request opened: [#2 Fix bug](http://localhost:3000/test/repo/pulls/12) by [user1](https://try.gitea.io/user1)", pl.(*MatrixPayloadUnsafe).Body) - assert.Equal(t, "[<a href=\"http://localhost:3000/test/repo\">test/repo</a>] Pull request opened: <a href=\"http://localhost:3000/test/repo/pulls/12\">#2 Fix bug</a> by <a href=\"https://try.gitea.io/user1\">user1</a>", pl.(*MatrixPayloadUnsafe).FormattedBody) -} - -func TestMatrixHookRequest(t *testing.T) { - h := &models.HookTask{ - PayloadContent: `{ - "body": "[[user1/test](http://localhost:3000/user1/test)] user1 pushed 1 commit to [master](http://localhost:3000/user1/test/src/branch/master):\n[5175ef2](http://localhost:3000/user1/test/commit/5175ef26201c58b035a3404b3fe02b4e8d436eee): Merge pull request 'Change readme.md' (#2) from add-matrix-webhook into master\n\nReviewed-on: http://localhost:3000/user1/test/pulls/2\n - user1", - "msgtype": "m.notice", - "format": "org.matrix.custom.html", - "formatted_body": "[\u003ca href=\"http://localhost:3000/user1/test\"\u003euser1/test\u003c/a\u003e] user1 pushed 1 commit to \u003ca href=\"http://localhost:3000/user1/test/src/branch/master\"\u003emaster\u003c/a\u003e:\u003cbr\u003e\u003ca href=\"http://localhost:3000/user1/test/commit/5175ef26201c58b035a3404b3fe02b4e8d436eee\"\u003e5175ef2\u003c/a\u003e: Merge pull request 'Change readme.md' (#2) from add-matrix-webhook into master\n\nReviewed-on: http://localhost:3000/user1/test/pulls/2\n - user1", - "io.gitea.commits": [ - { - "id": "5175ef26201c58b035a3404b3fe02b4e8d436eee", - "message": "Merge pull request 'Change readme.md' (#2) from add-matrix-webhook into master\n\nReviewed-on: http://localhost:3000/user1/test/pulls/2\n", - "url": "http://localhost:3000/user1/test/commit/5175ef26201c58b035a3404b3fe02b4e8d436eee", - "author": { - "name": "user1", - "email": "user@mail.com", - "username": "" - }, - "committer": { - "name": "user1", - "email": "user@mail.com", - "username": "" - }, - "verification": null, - "timestamp": "0001-01-01T00:00:00Z", - "added": null, - "removed": null, - "modified": null - } - ], - "access_token": "dummy_access_token" -}`, - } - - wantPayloadContent := `{ - "body": "[[user1/test](http://localhost:3000/user1/test)] user1 pushed 1 commit to [master](http://localhost:3000/user1/test/src/branch/master):\n[5175ef2](http://localhost:3000/user1/test/commit/5175ef26201c58b035a3404b3fe02b4e8d436eee): Merge pull request 'Change readme.md' (#2) from add-matrix-webhook into master\n\nReviewed-on: http://localhost:3000/user1/test/pulls/2\n - user1", - "msgtype": "m.notice", - "format": "org.matrix.custom.html", - "formatted_body": "[\u003ca href=\"http://localhost:3000/user1/test\"\u003euser1/test\u003c/a\u003e] user1 pushed 1 commit to \u003ca href=\"http://localhost:3000/user1/test/src/branch/master\"\u003emaster\u003c/a\u003e:\u003cbr\u003e\u003ca href=\"http://localhost:3000/user1/test/commit/5175ef26201c58b035a3404b3fe02b4e8d436eee\"\u003e5175ef2\u003c/a\u003e: Merge pull request 'Change readme.md' (#2) from add-matrix-webhook into master\n\nReviewed-on: http://localhost:3000/user1/test/pulls/2\n - user1", - "io.gitea.commits": [ - { - "id": "5175ef26201c58b035a3404b3fe02b4e8d436eee", - "message": "Merge pull request 'Change readme.md' (#2) from add-matrix-webhook into master\n\nReviewed-on: http://localhost:3000/user1/test/pulls/2\n", - "url": "http://localhost:3000/user1/test/commit/5175ef26201c58b035a3404b3fe02b4e8d436eee", - "author": { - "name": "user1", - "email": "user@mail.com", - "username": "" - }, - "committer": { - "name": "user1", - "email": "user@mail.com", - "username": "" - }, - "verification": null, - "timestamp": "0001-01-01T00:00:00Z", - "added": null, - "removed": null, - "modified": null - } - ] -}` - - req, err := getMatrixHookRequest(h) - require.NoError(t, err) - require.NotNil(t, req) - - assert.Equal(t, "Bearer dummy_access_token", req.Header.Get("Authorization")) - assert.Equal(t, wantPayloadContent, h.PayloadContent) -} - -func Test_getTxnID(t *testing.T) { - type args struct { - payload []byte - } - tests := []struct { - name string - args args - want string - wantErr bool - }{ - { - name: "dummy payload", - args: args{payload: []byte("Hello World")}, - want: "0a4d55a8d778e5022fab701977c5d840bbc486d0", - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := getMatrixTxnID(tt.args.payload) - if (err != nil) != tt.wantErr { - t.Errorf("getMatrixTxnID() error = %v, wantErr %v", err, tt.wantErr) - return - } - assert.Equal(t, tt.want, got) - }) - } -} diff --git a/modules/webhook/msteams.go b/modules/webhook/msteams.go deleted file mode 100644 index a68c97ea37..0000000000 --- a/modules/webhook/msteams.go +++ /dev/null @@ -1,563 +0,0 @@ -// Copyright 2019 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 ( - // MSTeamsFact for Fact Structure - MSTeamsFact struct { - Name string `json:"name"` - Value string `json:"value"` - } - - // MSTeamsSection is a MessageCard section - MSTeamsSection struct { - ActivityTitle string `json:"activityTitle"` - ActivitySubtitle string `json:"activitySubtitle"` - ActivityImage string `json:"activityImage"` - Facts []MSTeamsFact `json:"facts"` - Text string `json:"text"` - } - - // MSTeamsAction is an action (creates buttons, links etc) - MSTeamsAction struct { - Type string `json:"@type"` - Name string `json:"name"` - Targets []MSTeamsActionTarget `json:"targets,omitempty"` - } - - // MSTeamsActionTarget is the actual link to follow, etc - MSTeamsActionTarget struct { - Os string `json:"os"` - URI string `json:"uri"` - } - - // MSTeamsPayload is the parent object - MSTeamsPayload struct { - Type string `json:"@type"` - Context string `json:"@context"` - ThemeColor string `json:"themeColor"` - Title string `json:"title"` - Summary string `json:"summary"` - Sections []MSTeamsSection `json:"sections"` - PotentialAction []MSTeamsAction `json:"potentialAction"` - } -) - -// SetSecret sets the MSTeams secret -func (m *MSTeamsPayload) SetSecret(_ string) {} - -// JSONPayload Marshals the MSTeamsPayload to json -func (m *MSTeamsPayload) JSONPayload() ([]byte, error) { - data, err := json.MarshalIndent(m, "", " ") - if err != nil { - return []byte{}, err - } - return data, nil -} - -var ( - _ PayloadConvertor = &MSTeamsPayload{} -) - -// Create implements PayloadConvertor Create method -func (m *MSTeamsPayload) 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 &MSTeamsPayload{ - Type: "MessageCard", - Context: "https://schema.org/extensions", - ThemeColor: fmt.Sprintf("%x", greenColor), - Title: title, - Summary: title, - Sections: []MSTeamsSection{ - { - ActivityTitle: p.Sender.FullName, - ActivitySubtitle: p.Sender.UserName, - ActivityImage: p.Sender.AvatarURL, - Facts: []MSTeamsFact{ - { - Name: "Repository:", - Value: p.Repo.FullName, - }, - { - Name: fmt.Sprintf("%s:", p.RefType), - Value: refName, - }, - }, - }, - }, - PotentialAction: []MSTeamsAction{ - { - Type: "OpenUri", - Name: "View in Gitea", - Targets: []MSTeamsActionTarget{ - { - Os: "default", - URI: p.Repo.HTMLURL + "/src/" + refName, - }, - }, - }, - }, - }, nil -} - -// Delete implements PayloadConvertor Delete method -func (m *MSTeamsPayload) Delete(p *api.DeletePayload) (api.Payloader, error) { - // deleted tag/branch - refName := git.RefEndName(p.Ref) - title := fmt.Sprintf("[%s] %s %s deleted", p.Repo.FullName, p.RefType, refName) - - return &MSTeamsPayload{ - Type: "MessageCard", - Context: "https://schema.org/extensions", - ThemeColor: fmt.Sprintf("%x", yellowColor), - Title: title, - Summary: title, - Sections: []MSTeamsSection{ - { - ActivityTitle: p.Sender.FullName, - ActivitySubtitle: p.Sender.UserName, - ActivityImage: p.Sender.AvatarURL, - Facts: []MSTeamsFact{ - { - Name: "Repository:", - Value: p.Repo.FullName, - }, - { - Name: fmt.Sprintf("%s:", p.RefType), - Value: refName, - }, - }, - }, - }, - PotentialAction: []MSTeamsAction{ - { - Type: "OpenUri", - Name: "View in Gitea", - Targets: []MSTeamsActionTarget{ - { - Os: "default", - URI: p.Repo.HTMLURL + "/src/" + refName, - }, - }, - }, - }, - }, nil -} - -// Fork implements PayloadConvertor Fork method -func (m *MSTeamsPayload) Fork(p *api.ForkPayload) (api.Payloader, error) { - title := fmt.Sprintf("%s is forked to %s", p.Forkee.FullName, p.Repo.FullName) - - return &MSTeamsPayload{ - Type: "MessageCard", - Context: "https://schema.org/extensions", - ThemeColor: fmt.Sprintf("%x", greenColor), - Title: title, - Summary: title, - Sections: []MSTeamsSection{ - { - ActivityTitle: p.Sender.FullName, - ActivitySubtitle: p.Sender.UserName, - ActivityImage: p.Sender.AvatarURL, - Facts: []MSTeamsFact{ - { - Name: "Forkee:", - Value: p.Forkee.FullName, - }, - { - Name: "Repository:", - Value: p.Repo.FullName, - }, - }, - }, - }, - PotentialAction: []MSTeamsAction{ - { - Type: "OpenUri", - Name: "View in Gitea", - Targets: []MSTeamsActionTarget{ - { - Os: "default", - URI: p.Repo.HTMLURL, - }, - }, - }, - }, - }, nil -} - -// Push implements PayloadConvertor Push method -func (m *MSTeamsPayload) Push(p *api.PushPayload) (api.Payloader, error) { - var ( - branchName = git.RefEndName(p.Ref) - commitDesc string - ) - - var titleLink 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", p.Repo.FullName, branchName, commitDesc) - - var text string - // for each commit, generate attachment text - for i, commit := range p.Commits { - text += fmt.Sprintf("[%s](%s) %s - %s", commit.ID[:7], commit.URL, - strings.TrimRight(commit.Message, "\r\n"), commit.Author.Name) - // add linebreak to each commit but the last - if i < len(p.Commits)-1 { - text += "\n\n" - } - } - - return &MSTeamsPayload{ - Type: "MessageCard", - Context: "https://schema.org/extensions", - ThemeColor: fmt.Sprintf("%x", greenColor), - Title: title, - Summary: title, - Sections: []MSTeamsSection{ - { - ActivityTitle: p.Sender.FullName, - ActivitySubtitle: p.Sender.UserName, - ActivityImage: p.Sender.AvatarURL, - Text: text, - Facts: []MSTeamsFact{ - { - Name: "Repository:", - Value: p.Repo.FullName, - }, - { - Name: "Commit count:", - Value: fmt.Sprintf("%d", len(p.Commits)), - }, - }, - }, - }, - PotentialAction: []MSTeamsAction{ - { - Type: "OpenUri", - Name: "View in Gitea", - Targets: []MSTeamsActionTarget{ - { - Os: "default", - URI: titleLink, - }, - }, - }, - }, - }, nil -} - -// Issue implements PayloadConvertor Issue method -func (m *MSTeamsPayload) Issue(p *api.IssuePayload) (api.Payloader, error) { - text, _, attachmentText, color := getIssuesPayloadInfo(p, noneLinkFormatter, false) - - return &MSTeamsPayload{ - Type: "MessageCard", - Context: "https://schema.org/extensions", - ThemeColor: fmt.Sprintf("%x", color), - Title: text, - Summary: text, - Sections: []MSTeamsSection{ - { - ActivityTitle: p.Sender.FullName, - ActivitySubtitle: p.Sender.UserName, - ActivityImage: p.Sender.AvatarURL, - Text: attachmentText, - Facts: []MSTeamsFact{ - { - Name: "Repository:", - Value: p.Repository.FullName, - }, - { - Name: "Issue #:", - Value: fmt.Sprintf("%d", p.Issue.ID), - }, - }, - }, - }, - PotentialAction: []MSTeamsAction{ - { - Type: "OpenUri", - Name: "View in Gitea", - Targets: []MSTeamsActionTarget{ - { - Os: "default", - URI: p.Issue.HTMLURL, - }, - }, - }, - }, - }, nil -} - -// IssueComment implements PayloadConvertor IssueComment method -func (m *MSTeamsPayload) IssueComment(p *api.IssueCommentPayload) (api.Payloader, error) { - text, _, color := getIssueCommentPayloadInfo(p, noneLinkFormatter, false) - - return &MSTeamsPayload{ - Type: "MessageCard", - Context: "https://schema.org/extensions", - ThemeColor: fmt.Sprintf("%x", color), - Title: text, - Summary: text, - Sections: []MSTeamsSection{ - { - ActivityTitle: p.Sender.FullName, - ActivitySubtitle: p.Sender.UserName, - ActivityImage: p.Sender.AvatarURL, - Text: p.Comment.Body, - Facts: []MSTeamsFact{ - { - Name: "Repository:", - Value: p.Repository.FullName, - }, - { - Name: "Issue #:", - Value: fmt.Sprintf("%d", p.Issue.ID), - }, - }, - }, - }, - PotentialAction: []MSTeamsAction{ - { - Type: "OpenUri", - Name: "View in Gitea", - Targets: []MSTeamsActionTarget{ - { - Os: "default", - URI: p.Comment.HTMLURL, - }, - }, - }, - }, - }, nil -} - -// PullRequest implements PayloadConvertor PullRequest method -func (m *MSTeamsPayload) PullRequest(p *api.PullRequestPayload) (api.Payloader, error) { - text, _, attachmentText, color := getPullRequestPayloadInfo(p, noneLinkFormatter, false) - - return &MSTeamsPayload{ - Type: "MessageCard", - Context: "https://schema.org/extensions", - ThemeColor: fmt.Sprintf("%x", color), - Title: text, - Summary: text, - Sections: []MSTeamsSection{ - { - ActivityTitle: p.Sender.FullName, - ActivitySubtitle: p.Sender.UserName, - ActivityImage: p.Sender.AvatarURL, - Text: attachmentText, - Facts: []MSTeamsFact{ - { - Name: "Repository:", - Value: p.Repository.FullName, - }, - { - Name: "Pull request #:", - Value: fmt.Sprintf("%d", p.PullRequest.ID), - }, - }, - }, - }, - PotentialAction: []MSTeamsAction{ - { - Type: "OpenUri", - Name: "View in Gitea", - Targets: []MSTeamsActionTarget{ - { - Os: "default", - URI: p.PullRequest.HTMLURL, - }, - }, - }, - }, - }, nil -} - -// Review implements PayloadConvertor Review method -func (m *MSTeamsPayload) Review(p *api.PullRequestPayload, event models.HookEventType) (api.Payloader, error) { - var text, title string - var color int - switch p.Action { - case api.HookIssueReviewed: - 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 - - switch event { - case models.HookEventPullRequestReviewApproved: - color = greenColor - case models.HookEventPullRequestReviewRejected: - color = redColor - case models.HookEventPullRequestComment: - color = greyColor - default: - color = yellowColor - } - } - - return &MSTeamsPayload{ - Type: "MessageCard", - Context: "https://schema.org/extensions", - ThemeColor: fmt.Sprintf("%x", color), - Title: title, - Summary: title, - Sections: []MSTeamsSection{ - { - ActivityTitle: p.Sender.FullName, - ActivitySubtitle: p.Sender.UserName, - ActivityImage: p.Sender.AvatarURL, - Text: text, - Facts: []MSTeamsFact{ - { - Name: "Repository:", - Value: p.Repository.FullName, - }, - { - Name: "Pull request #:", - Value: fmt.Sprintf("%d", p.PullRequest.ID), - }, - }, - }, - }, - PotentialAction: []MSTeamsAction{ - { - Type: "OpenUri", - Name: "View in Gitea", - Targets: []MSTeamsActionTarget{ - { - Os: "default", - URI: p.PullRequest.HTMLURL, - }, - }, - }, - }, - }, nil -} - -// Repository implements PayloadConvertor Repository method -func (m *MSTeamsPayload) Repository(p *api.RepositoryPayload) (api.Payloader, error) { - var title, url string - var color int - switch p.Action { - case api.HookRepoCreated: - title = fmt.Sprintf("[%s] Repository created", p.Repository.FullName) - url = p.Repository.HTMLURL - color = greenColor - case api.HookRepoDeleted: - title = fmt.Sprintf("[%s] Repository deleted", p.Repository.FullName) - color = yellowColor - } - - return &MSTeamsPayload{ - Type: "MessageCard", - Context: "https://schema.org/extensions", - ThemeColor: fmt.Sprintf("%x", color), - Title: title, - Summary: title, - Sections: []MSTeamsSection{ - { - ActivityTitle: p.Sender.FullName, - ActivitySubtitle: p.Sender.UserName, - ActivityImage: p.Sender.AvatarURL, - Facts: []MSTeamsFact{ - { - Name: "Repository:", - Value: p.Repository.FullName, - }, - }, - }, - }, - PotentialAction: []MSTeamsAction{ - { - Type: "OpenUri", - Name: "View in Gitea", - Targets: []MSTeamsActionTarget{ - { - Os: "default", - URI: url, - }, - }, - }, - }, - }, nil -} - -// Release implements PayloadConvertor Release method -func (m *MSTeamsPayload) Release(p *api.ReleasePayload) (api.Payloader, error) { - text, color := getReleasePayloadInfo(p, noneLinkFormatter, false) - - return &MSTeamsPayload{ - Type: "MessageCard", - Context: "https://schema.org/extensions", - ThemeColor: fmt.Sprintf("%x", color), - Title: text, - Summary: text, - Sections: []MSTeamsSection{ - { - ActivityTitle: p.Sender.FullName, - ActivitySubtitle: p.Sender.UserName, - ActivityImage: p.Sender.AvatarURL, - Text: p.Release.Note, - Facts: []MSTeamsFact{ - { - Name: "Repository:", - Value: p.Repository.FullName, - }, - { - Name: "Tag:", - Value: p.Release.TagName, - }, - }, - }, - }, - PotentialAction: []MSTeamsAction{ - { - Type: "OpenUri", - Name: "View in Gitea", - Targets: []MSTeamsActionTarget{ - { - Os: "default", - URI: p.Release.URL, - }, - }, - }, - }, - }, nil -} - -// GetMSTeamsPayload converts a MSTeams webhook into a MSTeamsPayload -func GetMSTeamsPayload(p api.Payloader, event models.HookEventType, meta string) (api.Payloader, error) { - return convertPayloader(new(MSTeamsPayload), p, event) -} diff --git a/modules/webhook/payloader.go b/modules/webhook/payloader.go deleted file mode 100644 index f1cdaf6595..0000000000 --- a/modules/webhook/payloader.go +++ /dev/null @@ -1,56 +0,0 @@ -// 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 ( - "code.gitea.io/gitea/models" - api "code.gitea.io/gitea/modules/structs" -) - -// PayloadConvertor defines the interface to convert system webhook payload to external payload -type PayloadConvertor interface { - api.Payloader - Create(*api.CreatePayload) (api.Payloader, error) - Delete(*api.DeletePayload) (api.Payloader, error) - Fork(*api.ForkPayload) (api.Payloader, error) - Issue(*api.IssuePayload) (api.Payloader, error) - IssueComment(*api.IssueCommentPayload) (api.Payloader, error) - Push(*api.PushPayload) (api.Payloader, error) - PullRequest(*api.PullRequestPayload) (api.Payloader, error) - Review(*api.PullRequestPayload, models.HookEventType) (api.Payloader, error) - Repository(*api.RepositoryPayload) (api.Payloader, error) - Release(*api.ReleasePayload) (api.Payloader, error) -} - -func convertPayloader(s PayloadConvertor, p api.Payloader, event models.HookEventType) (api.Payloader, error) { - switch event { - case models.HookEventCreate: - return s.Create(p.(*api.CreatePayload)) - case models.HookEventDelete: - return s.Delete(p.(*api.DeletePayload)) - case models.HookEventFork: - return s.Fork(p.(*api.ForkPayload)) - case models.HookEventIssues, models.HookEventIssueAssign, models.HookEventIssueLabel, models.HookEventIssueMilestone: - return s.Issue(p.(*api.IssuePayload)) - case models.HookEventIssueComment, models.HookEventPullRequestComment: - pl, ok := p.(*api.IssueCommentPayload) - if ok { - return s.IssueComment(pl) - } - return s.PullRequest(p.(*api.PullRequestPayload)) - case models.HookEventPush: - return s.Push(p.(*api.PushPayload)) - case models.HookEventPullRequest, models.HookEventPullRequestAssign, models.HookEventPullRequestLabel, - models.HookEventPullRequestMilestone, models.HookEventPullRequestSync: - return s.PullRequest(p.(*api.PullRequestPayload)) - case models.HookEventPullRequestReviewApproved, models.HookEventPullRequestReviewRejected, models.HookEventPullRequestReviewComment: - return s.Review(p.(*api.PullRequestPayload), event) - case models.HookEventRepository: - return s.Repository(p.(*api.RepositoryPayload)) - case models.HookEventRelease: - return s.Release(p.(*api.ReleasePayload)) - } - return s, nil -} diff --git a/modules/webhook/slack.go b/modules/webhook/slack.go deleted file mode 100644 index aaecad6c67..0000000000 --- a/modules/webhook/slack.go +++ /dev/null @@ -1,333 +0,0 @@ -// Copyright 2014 The Gogs 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" - "errors" - "fmt" - "strings" - - "code.gitea.io/gitea/models" - "code.gitea.io/gitea/modules/git" - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/setting" - api "code.gitea.io/gitea/modules/structs" -) - -// SlackMeta contains the slack metadata -type SlackMeta struct { - Channel string `json:"channel"` - Username string `json:"username"` - IconURL string `json:"icon_url"` - Color string `json:"color"` -} - -// GetSlackHook returns slack metadata -func GetSlackHook(w *models.Webhook) *SlackMeta { - s := &SlackMeta{} - if err := json.Unmarshal([]byte(w.Meta), s); err != nil { - log.Error("webhook.GetSlackHook(%d): %v", w.ID, err) - } - return s -} - -// SlackPayload contains the information about the slack channel -type SlackPayload struct { - Channel string `json:"channel"` - Text string `json:"text"` - Color string `json:"-"` - Username string `json:"username"` - IconURL string `json:"icon_url"` - UnfurlLinks int `json:"unfurl_links"` - LinkNames int `json:"link_names"` - Attachments []SlackAttachment `json:"attachments"` -} - -// SlackAttachment contains the slack message -type SlackAttachment struct { - Fallback string `json:"fallback"` - Color string `json:"color"` - Title string `json:"title"` - TitleLink string `json:"title_link"` - Text string `json:"text"` -} - -// SetSecret sets the slack secret -func (s *SlackPayload) SetSecret(_ string) {} - -// JSONPayload Marshals the SlackPayload to json -func (s *SlackPayload) JSONPayload() ([]byte, error) { - data, err := json.MarshalIndent(s, "", " ") - if err != nil { - return []byte{}, err - } - return data, nil -} - -// SlackTextFormatter replaces &, <, > with HTML characters -// see: https://api.slack.com/docs/formatting -func SlackTextFormatter(s string) string { - // replace & < > - s = strings.ReplaceAll(s, "&", "&") - s = strings.ReplaceAll(s, "<", "<") - s = strings.ReplaceAll(s, ">", ">") - return s -} - -// SlackShortTextFormatter replaces &, <, > with HTML characters -func SlackShortTextFormatter(s string) string { - s = strings.Split(s, "\n")[0] - // replace & < > - s = strings.ReplaceAll(s, "&", "&") - s = strings.ReplaceAll(s, "<", "<") - s = strings.ReplaceAll(s, ">", ">") - return s -} - -// SlackLinkFormatter creates a link compatible with slack -func SlackLinkFormatter(url string, text string) string { - return fmt.Sprintf("<%s|%s>", url, SlackTextFormatter(text)) -} - -// SlackLinkToRef slack-formatter link to a repo ref -func SlackLinkToRef(repoURL, ref string) string { - url := git.RefURL(repoURL, ref) - refName := git.RefEndName(ref) - return SlackLinkFormatter(url, refName) -} - -var ( - _ PayloadConvertor = &SlackPayload{} -) - -// Create implements PayloadConvertor Create method -func (s *SlackPayload) Create(p *api.CreatePayload) (api.Payloader, error) { - repoLink := SlackLinkFormatter(p.Repo.HTMLURL, p.Repo.FullName) - refLink := SlackLinkToRef(p.Repo.HTMLURL, p.Ref) - text := fmt.Sprintf("[%s:%s] %s created by %s", repoLink, refLink, p.RefType, p.Sender.UserName) - - return &SlackPayload{ - Channel: s.Channel, - Text: text, - Username: s.Username, - IconURL: s.IconURL, - }, nil -} - -// Delete composes Slack payload for delete a branch or tag. -func (s *SlackPayload) Delete(p *api.DeletePayload) (api.Payloader, error) { - refName := git.RefEndName(p.Ref) - repoLink := SlackLinkFormatter(p.Repo.HTMLURL, p.Repo.FullName) - text := fmt.Sprintf("[%s:%s] %s deleted by %s", repoLink, refName, p.RefType, p.Sender.UserName) - return &SlackPayload{ - Channel: s.Channel, - Text: text, - Username: s.Username, - IconURL: s.IconURL, - }, nil -} - -// Fork composes Slack payload for forked by a repository. -func (s *SlackPayload) Fork(p *api.ForkPayload) (api.Payloader, error) { - baseLink := SlackLinkFormatter(p.Forkee.HTMLURL, p.Forkee.FullName) - forkLink := SlackLinkFormatter(p.Repo.HTMLURL, p.Repo.FullName) - text := fmt.Sprintf("%s is forked to %s", baseLink, forkLink) - return &SlackPayload{ - Channel: s.Channel, - Text: text, - Username: s.Username, - IconURL: s.IconURL, - }, nil -} - -// Issue implements PayloadConvertor Issue method -func (s *SlackPayload) Issue(p *api.IssuePayload) (api.Payloader, error) { - text, issueTitle, attachmentText, color := getIssuesPayloadInfo(p, SlackLinkFormatter, true) - - pl := &SlackPayload{ - Channel: s.Channel, - Text: text, - Username: s.Username, - IconURL: s.IconURL, - } - if attachmentText != "" { - attachmentText = SlackTextFormatter(attachmentText) - issueTitle = SlackTextFormatter(issueTitle) - pl.Attachments = []SlackAttachment{{ - Color: fmt.Sprintf("%x", color), - Title: issueTitle, - TitleLink: p.Issue.HTMLURL, - Text: attachmentText, - }} - } - - return pl, nil -} - -// IssueComment implements PayloadConvertor IssueComment method -func (s *SlackPayload) IssueComment(p *api.IssueCommentPayload) (api.Payloader, error) { - text, issueTitle, color := getIssueCommentPayloadInfo(p, SlackLinkFormatter, true) - - return &SlackPayload{ - Channel: s.Channel, - Text: text, - Username: s.Username, - IconURL: s.IconURL, - Attachments: []SlackAttachment{{ - Color: fmt.Sprintf("%x", color), - Title: issueTitle, - TitleLink: p.Comment.HTMLURL, - Text: SlackTextFormatter(p.Comment.Body), - }}, - }, nil -} - -// Release implements PayloadConvertor Release method -func (s *SlackPayload) Release(p *api.ReleasePayload) (api.Payloader, error) { - text, _ := getReleasePayloadInfo(p, SlackLinkFormatter, true) - - return &SlackPayload{ - Channel: s.Channel, - Text: text, - Username: s.Username, - IconURL: s.IconURL, - }, nil -} - -// Push implements PayloadConvertor Push method -func (s *SlackPayload) Push(p *api.PushPayload) (api.Payloader, error) { - // n new commits - var ( - commitDesc string - commitString string - ) - - if len(p.Commits) == 1 { - commitDesc = "1 new commit" - } else { - commitDesc = fmt.Sprintf("%d new commits", len(p.Commits)) - } - if len(p.CompareURL) > 0 { - commitString = SlackLinkFormatter(p.CompareURL, commitDesc) - } else { - commitString = commitDesc - } - - repoLink := SlackLinkFormatter(p.Repo.HTMLURL, p.Repo.FullName) - branchLink := SlackLinkToRef(p.Repo.HTMLURL, p.Ref) - text := fmt.Sprintf("[%s:%s] %s pushed by %s", repoLink, branchLink, commitString, p.Pusher.UserName) - - var attachmentText string - // for each commit, generate attachment text - for i, commit := range p.Commits { - attachmentText += fmt.Sprintf("%s: %s - %s", SlackLinkFormatter(commit.URL, commit.ID[:7]), SlackShortTextFormatter(commit.Message), SlackTextFormatter(commit.Author.Name)) - // add linebreak to each commit but the last - if i < len(p.Commits)-1 { - attachmentText += "\n" - } - } - - return &SlackPayload{ - Channel: s.Channel, - Text: text, - Username: s.Username, - IconURL: s.IconURL, - Attachments: []SlackAttachment{{ - Color: s.Color, - Title: p.Repo.HTMLURL, - TitleLink: p.Repo.HTMLURL, - Text: attachmentText, - }}, - }, nil -} - -// PullRequest implements PayloadConvertor PullRequest method -func (s *SlackPayload) PullRequest(p *api.PullRequestPayload) (api.Payloader, error) { - text, issueTitle, attachmentText, color := getPullRequestPayloadInfo(p, SlackLinkFormatter, true) - - pl := &SlackPayload{ - Channel: s.Channel, - Text: text, - Username: s.Username, - IconURL: s.IconURL, - } - if attachmentText != "" { - attachmentText = SlackTextFormatter(p.PullRequest.Body) - issueTitle = SlackTextFormatter(issueTitle) - pl.Attachments = []SlackAttachment{{ - Color: fmt.Sprintf("%x", color), - Title: issueTitle, - TitleLink: p.PullRequest.URL, - Text: attachmentText, - }} - } - - return pl, nil -} - -// Review implements PayloadConvertor Review method -func (s *SlackPayload) Review(p *api.PullRequestPayload, event models.HookEventType) (api.Payloader, error) { - senderLink := SlackLinkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName) - title := fmt.Sprintf("#%d %s", p.Index, p.PullRequest.Title) - titleLink := fmt.Sprintf("%s/pulls/%d", p.Repository.HTMLURL, p.Index) - repoLink := SlackLinkFormatter(p.Repository.HTMLURL, p.Repository.FullName) - var text string - - switch p.Action { - case api.HookIssueReviewed: - action, err := parseHookPullRequestEventType(event) - if err != nil { - return nil, err - } - - text = fmt.Sprintf("[%s] Pull request review %s: [%s](%s) by %s", repoLink, action, title, titleLink, senderLink) - } - - return &SlackPayload{ - Channel: s.Channel, - Text: text, - Username: s.Username, - IconURL: s.IconURL, - }, nil -} - -// Repository implements PayloadConvertor Repository method -func (s *SlackPayload) Repository(p *api.RepositoryPayload) (api.Payloader, error) { - senderLink := SlackLinkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName) - repoLink := SlackLinkFormatter(p.Repository.HTMLURL, p.Repository.FullName) - var text string - - switch p.Action { - case api.HookRepoCreated: - text = fmt.Sprintf("[%s] Repository created by %s", repoLink, senderLink) - case api.HookRepoDeleted: - text = fmt.Sprintf("[%s] Repository deleted by %s", repoLink, senderLink) - } - - return &SlackPayload{ - Channel: s.Channel, - Text: text, - Username: s.Username, - IconURL: s.IconURL, - }, nil -} - -// GetSlackPayload converts a slack webhook into a SlackPayload -func GetSlackPayload(p api.Payloader, event models.HookEventType, meta string) (api.Payloader, error) { - s := new(SlackPayload) - - slack := &SlackMeta{} - if err := json.Unmarshal([]byte(meta), &slack); err != nil { - return s, errors.New("GetSlackPayload meta json:" + err.Error()) - } - - s.Channel = slack.Channel - s.Username = slack.Username - s.IconURL = slack.IconURL - s.Color = slack.Color - - return convertPayloader(s, p, event) -} diff --git a/modules/webhook/slack_test.go b/modules/webhook/slack_test.go deleted file mode 100644 index 20de80bd65..0000000000 --- a/modules/webhook/slack_test.go +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright 2019 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 ( - "testing" - - api "code.gitea.io/gitea/modules/structs" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestSlackIssuesPayloadOpened(t *testing.T) { - p := issueTestPayload() - p.Action = api.HookIssueOpened - - s := new(SlackPayload) - s.Username = p.Sender.UserName - - pl, err := s.Issue(p) - require.NoError(t, err) - require.NotNil(t, pl) - assert.Equal(t, "[<http://localhost:3000/test/repo|test/repo>] Issue opened: <http://localhost:3000/test/repo/issues/2|#2 crash> by <https://try.gitea.io/user1|user1>", pl.(*SlackPayload).Text) - - p.Action = api.HookIssueClosed - pl, err = s.Issue(p) - require.NoError(t, err) - require.NotNil(t, pl) - assert.Equal(t, "[<http://localhost:3000/test/repo|test/repo>] Issue closed: <http://localhost:3000/test/repo/issues/2|#2 crash> by <https://try.gitea.io/user1|user1>", pl.(*SlackPayload).Text) -} - -func TestSlackIssueCommentPayload(t *testing.T) { - p := issueCommentTestPayload() - s := new(SlackPayload) - s.Username = p.Sender.UserName - - pl, err := s.IssueComment(p) - require.NoError(t, err) - require.NotNil(t, pl) - - assert.Equal(t, "[<http://localhost:3000/test/repo|test/repo>] New comment on issue <http://localhost:3000/test/repo/issues/2|#2 crash> by <https://try.gitea.io/user1|user1>", pl.(*SlackPayload).Text) -} - -func TestSlackPullRequestCommentPayload(t *testing.T) { - p := pullRequestCommentTestPayload() - s := new(SlackPayload) - s.Username = p.Sender.UserName - - pl, err := s.IssueComment(p) - require.NoError(t, err) - require.NotNil(t, pl) - - assert.Equal(t, "[<http://localhost:3000/test/repo|test/repo>] New comment on pull request <http://localhost:3000/test/repo/pulls/2|#2 Fix bug> by <https://try.gitea.io/user1|user1>", pl.(*SlackPayload).Text) -} - -func TestSlackReleasePayload(t *testing.T) { - p := pullReleaseTestPayload() - s := new(SlackPayload) - s.Username = p.Sender.UserName - - pl, err := s.Release(p) - require.NoError(t, err) - require.NotNil(t, pl) - - assert.Equal(t, "[<http://localhost:3000/test/repo|test/repo>] Release created: <http://localhost:3000/test/repo/src/v1.0|v1.0> by <https://try.gitea.io/user1|user1>", pl.(*SlackPayload).Text) -} - -func TestSlackPullRequestPayload(t *testing.T) { - p := pullRequestTestPayload() - s := new(SlackPayload) - s.Username = p.Sender.UserName - - pl, err := s.PullRequest(p) - require.NoError(t, err) - require.NotNil(t, pl) - - assert.Equal(t, "[<http://localhost:3000/test/repo|test/repo>] Pull request opened: <http://localhost:3000/test/repo/pulls/12|#2 Fix bug> by <https://try.gitea.io/user1|user1>", pl.(*SlackPayload).Text) -} diff --git a/modules/webhook/telegram.go b/modules/webhook/telegram.go deleted file mode 100644 index 84fc210042..0000000000 --- a/modules/webhook/telegram.go +++ /dev/null @@ -1,212 +0,0 @@ -// Copyright 2019 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" - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/markup" - api "code.gitea.io/gitea/modules/structs" -) - -type ( - // TelegramPayload represents - TelegramPayload struct { - Message string `json:"text"` - ParseMode string `json:"parse_mode"` - DisableWebPreview bool `json:"disable_web_page_preview"` - } - - // TelegramMeta contains the telegram metadata - TelegramMeta struct { - BotToken string `json:"bot_token"` - ChatID string `json:"chat_id"` - } -) - -// GetTelegramHook returns telegram metadata -func GetTelegramHook(w *models.Webhook) *TelegramMeta { - s := &TelegramMeta{} - if err := json.Unmarshal([]byte(w.Meta), s); err != nil { - log.Error("webhook.GetTelegramHook(%d): %v", w.ID, err) - } - return s -} - -var ( - _ PayloadConvertor = &TelegramPayload{} -) - -// SetSecret sets the telegram secret -func (t *TelegramPayload) SetSecret(_ string) {} - -// JSONPayload Marshals the TelegramPayload to json -func (t *TelegramPayload) JSONPayload() ([]byte, error) { - t.ParseMode = "HTML" - t.DisableWebPreview = true - t.Message = markup.Sanitize(t.Message) - data, err := json.MarshalIndent(t, "", " ") - if err != nil { - return []byte{}, err - } - return data, nil -} - -// Create implements PayloadConvertor Create method -func (t *TelegramPayload) Create(p *api.CreatePayload) (api.Payloader, error) { - // created tag/branch - refName := git.RefEndName(p.Ref) - title := fmt.Sprintf(`[<a href="%s">%s</a>] %s <a href="%s">%s</a> created`, p.Repo.HTMLURL, p.Repo.FullName, p.RefType, - p.Repo.HTMLURL+"/src/"+refName, refName) - - return &TelegramPayload{ - Message: title, - }, nil -} - -// Delete implements PayloadConvertor Delete method -func (t *TelegramPayload) Delete(p *api.DeletePayload) (api.Payloader, error) { - // created tag/branch - refName := git.RefEndName(p.Ref) - title := fmt.Sprintf(`[<a href="%s">%s</a>] %s <a href="%s">%s</a> deleted`, p.Repo.HTMLURL, p.Repo.FullName, p.RefType, - p.Repo.HTMLURL+"/src/"+refName, refName) - - return &TelegramPayload{ - Message: title, - }, nil -} - -// Fork implements PayloadConvertor Fork method -func (t *TelegramPayload) Fork(p *api.ForkPayload) (api.Payloader, error) { - title := fmt.Sprintf(`%s is forked to <a href="%s">%s</a>`, p.Forkee.FullName, p.Repo.HTMLURL, p.Repo.FullName) - - return &TelegramPayload{ - Message: title, - }, nil -} - -// Push implements PayloadConvertor Push method -func (t *TelegramPayload) Push(p *api.PushPayload) (api.Payloader, error) { - var ( - branchName = git.RefEndName(p.Ref) - commitDesc string - ) - - var titleLink 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(`[<a href="%s">%s</a>:<a href="%s">%s</a>] %s`, p.Repo.HTMLURL, p.Repo.FullName, titleLink, 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(`[<a href="%s">%s</a>] %s`, commit.URL, commit.ID[:7], - strings.TrimRight(commit.Message, "\r\n")) + authorName - // add linebreak to each commit but the last - if i < len(p.Commits)-1 { - text += "\n" - } - } - - return &TelegramPayload{ - Message: title + "\n" + text, - }, nil -} - -// Issue implements PayloadConvertor Issue method -func (t *TelegramPayload) Issue(p *api.IssuePayload) (api.Payloader, error) { - text, _, attachmentText, _ := getIssuesPayloadInfo(p, htmlLinkFormatter, true) - - return &TelegramPayload{ - Message: text + "\n\n" + attachmentText, - }, nil -} - -// IssueComment implements PayloadConvertor IssueComment method -func (t *TelegramPayload) IssueComment(p *api.IssueCommentPayload) (api.Payloader, error) { - text, _, _ := getIssueCommentPayloadInfo(p, htmlLinkFormatter, true) - - return &TelegramPayload{ - Message: text + "\n" + p.Comment.Body, - }, nil -} - -// PullRequest implements PayloadConvertor PullRequest method -func (t *TelegramPayload) PullRequest(p *api.PullRequestPayload) (api.Payloader, error) { - text, _, attachmentText, _ := getPullRequestPayloadInfo(p, htmlLinkFormatter, true) - - return &TelegramPayload{ - Message: text + "\n" + attachmentText, - }, nil -} - -// Review implements PayloadConvertor Review method -func (t *TelegramPayload) Review(p *api.PullRequestPayload, event models.HookEventType) (api.Payloader, error) { - var text, attachmentText string - switch p.Action { - case api.HookIssueReviewed: - action, err := parseHookPullRequestEventType(event) - if err != nil { - return nil, err - } - - text = fmt.Sprintf("[%s] Pull request review %s: #%d %s", p.Repository.FullName, action, p.Index, p.PullRequest.Title) - attachmentText = p.Review.Content - - } - - return &TelegramPayload{ - Message: text + "\n" + attachmentText, - }, nil -} - -// Repository implements PayloadConvertor Repository method -func (t *TelegramPayload) Repository(p *api.RepositoryPayload) (api.Payloader, error) { - var title string - switch p.Action { - case api.HookRepoCreated: - title = fmt.Sprintf(`[<a href="%s">%s</a>] Repository created`, p.Repository.HTMLURL, p.Repository.FullName) - return &TelegramPayload{ - Message: title, - }, nil - case api.HookRepoDeleted: - title = fmt.Sprintf("[%s] Repository deleted", p.Repository.FullName) - return &TelegramPayload{ - Message: title, - }, nil - } - return nil, nil -} - -// Release implements PayloadConvertor Release method -func (t *TelegramPayload) Release(p *api.ReleasePayload) (api.Payloader, error) { - text, _ := getReleasePayloadInfo(p, htmlLinkFormatter, true) - - return &TelegramPayload{ - Message: text + "\n", - }, nil -} - -// GetTelegramPayload converts a telegram webhook into a TelegramPayload -func GetTelegramPayload(p api.Payloader, event models.HookEventType, meta string) (api.Payloader, error) { - return convertPayloader(new(TelegramPayload), p, event) -} diff --git a/modules/webhook/telegram_test.go b/modules/webhook/telegram_test.go deleted file mode 100644 index 0e909343a8..0000000000 --- a/modules/webhook/telegram_test.go +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2019 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 ( - "testing" - - api "code.gitea.io/gitea/modules/structs" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestGetTelegramIssuesPayload(t *testing.T) { - p := issueTestPayload() - p.Action = api.HookIssueClosed - - pl, err := new(TelegramPayload).Issue(p) - require.NoError(t, err) - require.NotNil(t, pl) - - assert.Equal(t, "[<a href=\"http://localhost:3000/test/repo\">test/repo</a>] Issue closed: <a href=\"http://localhost:3000/test/repo/issues/2\">#2 crash</a> by <a href=\"https://try.gitea.io/user1\">user1</a>\n\n", pl.(*TelegramPayload).Message) -} diff --git a/modules/webhook/webhook.go b/modules/webhook/webhook.go deleted file mode 100644 index 2ef150210e..0000000000 --- a/modules/webhook/webhook.go +++ /dev/null @@ -1,214 +0,0 @@ -// Copyright 2019 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 ( - "crypto/hmac" - "crypto/sha256" - "encoding/hex" - "fmt" - "strings" - - "code.gitea.io/gitea/models" - "code.gitea.io/gitea/modules/git" - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/setting" - api "code.gitea.io/gitea/modules/structs" - "code.gitea.io/gitea/modules/sync" - "github.com/gobwas/glob" -) - -// hookQueue is a global queue of web hooks -var hookQueue = sync.NewUniqueQueue(setting.Webhook.QueueLength) - -// getPayloadBranch returns branch for hook event, if applicable. -func getPayloadBranch(p api.Payloader) string { - switch pp := p.(type) { - case *api.CreatePayload: - if pp.RefType == "branch" { - return pp.Ref - } - case *api.DeletePayload: - if pp.RefType == "branch" { - return pp.Ref - } - case *api.PushPayload: - if strings.HasPrefix(pp.Ref, git.BranchPrefix) { - return pp.Ref[len(git.BranchPrefix):] - } - } - return "" -} - -// PrepareWebhook adds special webhook to task queue for given payload. -func PrepareWebhook(w *models.Webhook, repo *models.Repository, event models.HookEventType, p api.Payloader) error { - if err := prepareWebhook(w, repo, event, p); err != nil { - return err - } - - go hookQueue.Add(repo.ID) - return nil -} - -func checkBranch(w *models.Webhook, branch string) bool { - if w.BranchFilter == "" || w.BranchFilter == "*" { - return true - } - - g, err := glob.Compile(w.BranchFilter) - if err != nil { - // should not really happen as BranchFilter is validated - log.Error("CheckBranch failed: %s", err) - return false - } - - return g.Match(branch) -} - -func prepareWebhook(w *models.Webhook, repo *models.Repository, event models.HookEventType, p api.Payloader) error { - for _, e := range w.EventCheckers() { - if event == e.Type { - if !e.Has() { - return nil - } - } - } - - // Avoid sending "0 new commits" to non-integration relevant webhooks (e.g. slack, discord, etc.). - // Integration webhooks (e.g. drone) still receive the required data. - if pushEvent, ok := p.(*api.PushPayload); ok && - w.HookTaskType != models.GITEA && w.HookTaskType != models.GOGS && - len(pushEvent.Commits) == 0 { - return nil - } - - // If payload has no associated branch (e.g. it's a new tag, issue, etc.), - // branch filter has no effect. - if branch := getPayloadBranch(p); branch != "" { - if !checkBranch(w, branch) { - log.Info("Branch %q doesn't match branch filter %q, skipping", branch, w.BranchFilter) - return nil - } - } - - var payloader api.Payloader - var err error - // Use separate objects so modifications won't be made on payload on non-Gogs/Gitea type hooks. - switch w.HookTaskType { - case models.SLACK: - payloader, err = GetSlackPayload(p, event, w.Meta) - if err != nil { - return fmt.Errorf("GetSlackPayload: %v", err) - } - case models.DISCORD: - payloader, err = GetDiscordPayload(p, event, w.Meta) - if err != nil { - return fmt.Errorf("GetDiscordPayload: %v", err) - } - case models.DINGTALK: - payloader, err = GetDingtalkPayload(p, event, w.Meta) - if err != nil { - return fmt.Errorf("GetDingtalkPayload: %v", err) - } - case models.TELEGRAM: - payloader, err = GetTelegramPayload(p, event, w.Meta) - if err != nil { - return fmt.Errorf("GetTelegramPayload: %v", err) - } - case models.MSTEAMS: - payloader, err = GetMSTeamsPayload(p, event, w.Meta) - 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) - } - case models.MATRIX: - payloader, err = GetMatrixPayload(p, event, w.Meta) - if err != nil { - return fmt.Errorf("GetMatrixPayload: %v", err) - } - default: - p.SetSecret(w.Secret) - payloader = p - } - - var signature string - if len(w.Secret) > 0 { - data, err := payloader.JSONPayload() - if err != nil { - log.Error("prepareWebhooks.JSONPayload: %v", err) - } - sig := hmac.New(sha256.New, []byte(w.Secret)) - _, err = sig.Write(data) - if err != nil { - log.Error("prepareWebhooks.sigWrite: %v", err) - } - signature = hex.EncodeToString(sig.Sum(nil)) - } - - if err = models.CreateHookTask(&models.HookTask{ - RepoID: repo.ID, - HookID: w.ID, - Type: w.HookTaskType, - URL: w.URL, - Signature: signature, - Payloader: payloader, - HTTPMethod: w.HTTPMethod, - ContentType: w.ContentType, - EventType: event, - IsSSL: w.IsSSL, - }); err != nil { - return fmt.Errorf("CreateHookTask: %v", err) - } - return nil -} - -// PrepareWebhooks adds new webhooks to task queue for given payload. -func PrepareWebhooks(repo *models.Repository, event models.HookEventType, p api.Payloader) error { - if err := prepareWebhooks(repo, event, p); err != nil { - return err - } - - go hookQueue.Add(repo.ID) - return nil -} - -func prepareWebhooks(repo *models.Repository, event models.HookEventType, p api.Payloader) error { - ws, err := models.GetActiveWebhooksByRepoID(repo.ID) - if err != nil { - return fmt.Errorf("GetActiveWebhooksByRepoID: %v", err) - } - - // check if repo belongs to org and append additional webhooks - if repo.MustOwner().IsOrganization() { - // get hooks for org - orgHooks, err := models.GetActiveWebhooksByOrgID(repo.OwnerID) - if err != nil { - return fmt.Errorf("GetActiveWebhooksByOrgID: %v", err) - } - ws = append(ws, orgHooks...) - } - - // Add any admin-defined system webhooks - systemHooks, err := models.GetSystemWebhooks() - if err != nil { - return fmt.Errorf("GetSystemWebhooks: %v", err) - } - ws = append(ws, systemHooks...) - - if len(ws) == 0 { - return nil - } - - for _, w := range ws { - if err = prepareWebhook(w, repo, event, p); err != nil { - return err - } - } - return nil -} diff --git a/modules/webhook/webhook_test.go b/modules/webhook/webhook_test.go deleted file mode 100644 index 10c32a9485..0000000000 --- a/modules/webhook/webhook_test.go +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright 2019 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 ( - "testing" - - "code.gitea.io/gitea/models" - api "code.gitea.io/gitea/modules/structs" - "github.com/stretchr/testify/assert" -) - -func TestWebhook_GetSlackHook(t *testing.T) { - w := &models.Webhook{ - Meta: `{"channel": "foo", "username": "username", "color": "blue"}`, - } - slackHook := GetSlackHook(w) - assert.Equal(t, *slackHook, SlackMeta{ - Channel: "foo", - Username: "username", - Color: "blue", - }) -} - -func TestPrepareWebhooks(t *testing.T) { - assert.NoError(t, models.PrepareTestDatabase()) - - repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) - hookTasks := []*models.HookTask{ - {RepoID: repo.ID, HookID: 1, EventType: models.HookEventPush}, - } - for _, hookTask := range hookTasks { - models.AssertNotExistsBean(t, hookTask) - } - assert.NoError(t, PrepareWebhooks(repo, models.HookEventPush, &api.PushPayload{Commits: []*api.PayloadCommit{{}}})) - for _, hookTask := range hookTasks { - models.AssertExistsAndLoadBean(t, hookTask) - } -} - -func TestPrepareWebhooksBranchFilterMatch(t *testing.T) { - assert.NoError(t, models.PrepareTestDatabase()) - - repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 2}).(*models.Repository) - hookTasks := []*models.HookTask{ - {RepoID: repo.ID, HookID: 4, EventType: models.HookEventPush}, - } - for _, hookTask := range hookTasks { - models.AssertNotExistsBean(t, hookTask) - } - // this test also ensures that * doesn't handle / in any special way (like shell would) - assert.NoError(t, PrepareWebhooks(repo, models.HookEventPush, &api.PushPayload{Ref: "refs/heads/feature/7791", Commits: []*api.PayloadCommit{{}}})) - for _, hookTask := range hookTasks { - models.AssertExistsAndLoadBean(t, hookTask) - } -} - -func TestPrepareWebhooksBranchFilterNoMatch(t *testing.T) { - assert.NoError(t, models.PrepareTestDatabase()) - - repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 2}).(*models.Repository) - hookTasks := []*models.HookTask{ - {RepoID: repo.ID, HookID: 4, EventType: models.HookEventPush}, - } - for _, hookTask := range hookTasks { - models.AssertNotExistsBean(t, hookTask) - } - assert.NoError(t, PrepareWebhooks(repo, models.HookEventPush, &api.PushPayload{Ref: "refs/heads/fix_weird_bug"})) - - for _, hookTask := range hookTasks { - models.AssertNotExistsBean(t, hookTask) - } -} - -// TODO TestHookTask_deliver - -// TODO TestDeliverHooks |