2014-08-24 16:59:47 +04:00
// 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 models
import (
"encoding/json"
"errors"
"fmt"
"strings"
2015-08-28 18:36:13 +03:00
2016-11-10 19:24:48 +03:00
"code.gitea.io/git"
"code.gitea.io/gitea/modules/setting"
2018-12-27 21:04:30 +03:00
api "code.gitea.io/sdk/gitea"
2014-08-24 16:59:47 +04:00
)
2017-01-05 03:50:34 +03:00
// SlackMeta contains the slack metadata
2015-08-28 18:36:13 +03:00
type SlackMeta struct {
2015-08-29 06:49:59 +03:00
Channel string ` json:"channel" `
Username string ` json:"username" `
IconURL string ` json:"icon_url" `
Color string ` json:"color" `
2014-08-24 16:59:47 +04:00
}
2016-11-25 00:35:47 +03:00
// SlackPayload contains the information about the slack channel
2014-08-24 16:59:47 +04:00
type SlackPayload struct {
Channel string ` json:"channel" `
Text string ` json:"text" `
Username string ` json:"username" `
2015-08-29 06:49:59 +03:00
IconURL string ` json:"icon_url" `
2014-08-24 16:59:47 +04:00
UnfurlLinks int ` json:"unfurl_links" `
LinkNames int ` json:"link_names" `
Attachments [ ] SlackAttachment ` json:"attachments" `
}
2016-11-25 00:35:47 +03:00
// SlackAttachment contains the slack message
2014-08-24 16:59:47 +04:00
type SlackAttachment struct {
2015-12-02 05:16:19 +03:00
Fallback string ` json:"fallback" `
Color string ` json:"color" `
2016-08-14 13:32:24 +03:00
Title string ` json:"title" `
2015-12-02 05:16:19 +03:00
Text string ` json:"text" `
2014-08-24 16:59:47 +04:00
}
2016-11-25 00:35:47 +03:00
// SetSecret sets the slack secret
2015-08-28 18:36:13 +03:00
func ( p * SlackPayload ) SetSecret ( _ string ) { }
2016-11-25 00:35:47 +03:00
// JSONPayload Marshals the SlackPayload to json
2015-08-28 18:36:13 +03:00
func ( p * SlackPayload ) JSONPayload ( ) ( [ ] byte , error ) {
2015-08-28 18:38:09 +03:00
data , err := json . MarshalIndent ( p , "" , " " )
2014-08-24 16:59:47 +04:00
if err != nil {
return [ ] byte { } , err
}
return data , nil
}
2016-11-25 00:35:47 +03:00
// SlackTextFormatter replaces &, <, > with HTML characters
2015-08-28 18:36:13 +03:00
// see: https://api.slack.com/docs/formatting
func SlackTextFormatter ( s string ) string {
// replace & < >
2016-08-14 13:32:24 +03:00
s = strings . Replace ( s , "&" , "&" , - 1 )
s = strings . Replace ( s , "<" , "<" , - 1 )
s = strings . Replace ( s , ">" , ">" , - 1 )
return s
}
2016-11-25 00:35:47 +03:00
// SlackShortTextFormatter replaces &, <, > with HTML characters
2016-08-14 13:32:24 +03:00
func SlackShortTextFormatter ( s string ) string {
s = strings . Split ( s , "\n" ) [ 0 ]
// replace & < >
s = strings . Replace ( s , "&" , "&" , - 1 )
s = strings . Replace ( s , "<" , "<" , - 1 )
s = strings . Replace ( s , ">" , ">" , - 1 )
return s
2015-08-28 18:36:13 +03:00
}
2014-08-24 16:59:47 +04:00
2017-01-05 03:50:34 +03:00
// SlackLinkFormatter creates a link compatible with slack
2015-08-28 18:36:13 +03:00
func SlackLinkFormatter ( url string , text string ) string {
return fmt . Sprintf ( "<%s|%s>" , url , SlackTextFormatter ( text ) )
2014-08-24 16:59:47 +04:00
}
2017-10-30 05:04:25 +03:00
// SlackLinkToRef slack-formatter link to a repo ref
func SlackLinkToRef ( repoURL , ref string ) string {
refName := git . RefEndName ( ref )
switch {
case strings . HasPrefix ( ref , git . BranchPrefix ) :
return SlackLinkFormatter ( repoURL + "/src/branch/" + refName , refName )
case strings . HasPrefix ( ref , git . TagPrefix ) :
return SlackLinkFormatter ( repoURL + "/src/tag/" + refName , refName )
default :
return SlackLinkFormatter ( repoURL + "/src/commit/" + refName , refName )
}
}
2015-08-28 18:36:13 +03:00
2017-10-30 05:04:25 +03:00
func getSlackCreatePayload ( p * api . CreatePayload , slack * SlackMeta ) ( * SlackPayload , error ) {
2016-08-14 14:17:26 +03:00
repoLink := SlackLinkFormatter ( p . Repo . HTMLURL , p . Repo . Name )
2017-10-30 05:04:25 +03:00
refLink := SlackLinkToRef ( p . Repo . HTMLURL , p . Ref )
2015-08-28 18:36:13 +03:00
text := fmt . Sprintf ( "[%s:%s] %s created by %s" , repoLink , refLink , p . RefType , p . Sender . UserName )
return & SlackPayload {
Channel : slack . Channel ,
Text : text ,
2015-08-29 06:49:59 +03:00
Username : slack . Username ,
IconURL : slack . IconURL ,
2015-08-28 18:36:13 +03:00
} , nil
}
2018-05-16 17:01:55 +03:00
// getSlackDeletePayload composes Slack payload for delete a branch or tag.
func getSlackDeletePayload ( p * api . DeletePayload , slack * SlackMeta ) ( * SlackPayload , error ) {
refName := git . RefEndName ( p . Ref )
repoLink := SlackLinkFormatter ( p . Repo . HTMLURL , p . Repo . Name )
text := fmt . Sprintf ( "[%s:%s] %s deleted by %s" , repoLink , refName , p . RefType , p . Sender . UserName )
return & SlackPayload {
Channel : slack . Channel ,
Text : text ,
Username : slack . Username ,
IconURL : slack . IconURL ,
} , nil
}
// getSlackForkPayload composes Slack payload for forked by a repository.
func getSlackForkPayload ( p * api . ForkPayload , slack * SlackMeta ) ( * SlackPayload , error ) {
baseLink := SlackLinkFormatter ( p . Repo . HTMLURL , p . Repo . Name )
forkLink := SlackLinkFormatter ( p . Forkee . HTMLURL , p . Forkee . FullName )
text := fmt . Sprintf ( "%s is forked to %s" , baseLink , forkLink )
return & SlackPayload {
Channel : slack . Channel ,
Text : text ,
Username : slack . Username ,
IconURL : slack . IconURL ,
} , nil
}
func getSlackIssuesPayload ( p * api . IssuePayload , slack * SlackMeta ) ( * SlackPayload , error ) {
senderLink := SlackLinkFormatter ( setting . AppURL + p . Sender . UserName , p . Sender . UserName )
titleLink := SlackLinkFormatter ( fmt . Sprintf ( "%s/pulls/%d" , p . Repository . HTMLURL , p . Index ) ,
fmt . Sprintf ( "#%d %s" , p . Index , p . Issue . Title ) )
var text , title , attachmentText string
switch p . Action {
case api . HookIssueOpened :
text = fmt . Sprintf ( "[%s] Issue submitted by %s" , p . Repository . FullName , senderLink )
title = titleLink
attachmentText = SlackTextFormatter ( p . Issue . Body )
case api . HookIssueClosed :
text = fmt . Sprintf ( "[%s] Issue closed: %s by %s" , p . Repository . FullName , titleLink , senderLink )
case api . HookIssueReOpened :
text = fmt . Sprintf ( "[%s] Issue re-opened: %s by %s" , p . Repository . FullName , titleLink , senderLink )
case api . HookIssueEdited :
text = fmt . Sprintf ( "[%s] Issue edited: %s by %s" , p . Repository . FullName , titleLink , senderLink )
attachmentText = SlackTextFormatter ( p . Issue . Body )
case api . HookIssueAssigned :
text = fmt . Sprintf ( "[%s] Issue assigned to %s: %s by %s" , p . Repository . FullName ,
SlackLinkFormatter ( setting . AppURL + p . Issue . Assignee . UserName , p . Issue . Assignee . UserName ) ,
titleLink , senderLink )
case api . HookIssueUnassigned :
text = fmt . Sprintf ( "[%s] Issue unassigned: %s by %s" , p . Repository . FullName , titleLink , senderLink )
case api . HookIssueLabelUpdated :
text = fmt . Sprintf ( "[%s] Issue labels updated: %s by %s" , p . Repository . FullName , titleLink , senderLink )
case api . HookIssueLabelCleared :
text = fmt . Sprintf ( "[%s] Issue labels cleared: %s by %s" , p . Repository . FullName , titleLink , senderLink )
case api . HookIssueSynchronized :
text = fmt . Sprintf ( "[%s] Issue synchronized: %s by %s" , p . Repository . FullName , titleLink , senderLink )
}
return & SlackPayload {
Channel : slack . Channel ,
Text : text ,
Username : slack . Username ,
IconURL : slack . IconURL ,
Attachments : [ ] SlackAttachment { {
Color : slack . Color ,
Title : title ,
Text : attachmentText ,
} } ,
} , nil
}
func getSlackIssueCommentPayload ( p * api . IssueCommentPayload , slack * SlackMeta ) ( * SlackPayload , error ) {
senderLink := SlackLinkFormatter ( setting . AppURL + p . Sender . UserName , p . Sender . UserName )
titleLink := SlackLinkFormatter ( fmt . Sprintf ( "%s/issues/%d#%s" , p . Repository . HTMLURL , p . Issue . Index , CommentHashTag ( p . Comment . ID ) ) ,
fmt . Sprintf ( "#%d %s" , p . Issue . Index , p . Issue . Title ) )
var text , title , attachmentText string
switch p . Action {
case api . HookIssueCommentCreated :
text = fmt . Sprintf ( "[%s] New comment created by %s" , p . Repository . FullName , senderLink )
title = titleLink
attachmentText = SlackTextFormatter ( p . Comment . Body )
case api . HookIssueCommentEdited :
text = fmt . Sprintf ( "[%s] Comment edited by %s" , p . Repository . FullName , senderLink )
title = titleLink
attachmentText = SlackTextFormatter ( p . Comment . Body )
case api . HookIssueCommentDeleted :
text = fmt . Sprintf ( "[%s] Comment deleted by %s" , p . Repository . FullName , senderLink )
title = SlackLinkFormatter ( fmt . Sprintf ( "%s/issues/%d" , p . Repository . HTMLURL , p . Issue . Index ) ,
fmt . Sprintf ( "#%d %s" , p . Issue . Index , p . Issue . Title ) )
attachmentText = SlackTextFormatter ( p . Comment . Body )
}
return & SlackPayload {
Channel : slack . Channel ,
Text : text ,
Username : slack . Username ,
IconURL : slack . IconURL ,
Attachments : [ ] SlackAttachment { {
Color : slack . Color ,
Title : title ,
Text : attachmentText ,
} } ,
} , nil
}
func getSlackReleasePayload ( p * api . ReleasePayload , slack * SlackMeta ) ( * SlackPayload , error ) {
repoLink := SlackLinkFormatter ( p . Repository . HTMLURL , p . Repository . Name )
refLink := SlackLinkFormatter ( p . Repository . HTMLURL + "/src/" + p . Release . TagName , p . Release . TagName )
2018-05-21 05:28:29 +03:00
var text string
switch p . Action {
case api . HookReleasePublished :
text = fmt . Sprintf ( "[%s] new release %s published by %s" , repoLink , refLink , p . Sender . UserName )
case api . HookReleaseUpdated :
text = fmt . Sprintf ( "[%s] new release %s updated by %s" , repoLink , refLink , p . Sender . UserName )
case api . HookReleaseDeleted :
text = fmt . Sprintf ( "[%s] new release %s deleted by %s" , repoLink , refLink , p . Sender . UserName )
}
2018-05-16 17:01:55 +03:00
return & SlackPayload {
Channel : slack . Channel ,
Text : text ,
Username : slack . Username ,
IconURL : slack . IconURL ,
} , nil
}
2015-08-28 18:36:13 +03:00
func getSlackPushPayload ( p * api . PushPayload , slack * SlackMeta ) ( * SlackPayload , error ) {
2014-08-24 16:59:47 +04:00
// n new commits
2015-08-28 18:36:13 +03:00
var (
2015-12-04 23:41:56 +03:00
commitDesc string
2015-08-28 18:36:13 +03:00
commitString string
)
2014-08-24 16:59:47 +04:00
if len ( p . Commits ) == 1 {
2015-12-04 23:41:56 +03:00
commitDesc = "1 new commit"
2014-08-24 16:59:47 +04:00
} else {
2015-12-04 23:41:56 +03:00
commitDesc = fmt . Sprintf ( "%d new commits" , len ( p . Commits ) )
}
2016-08-14 14:17:26 +03:00
if len ( p . CompareURL ) > 0 {
commitString = SlackLinkFormatter ( p . CompareURL , commitDesc )
2015-12-04 23:41:56 +03:00
} else {
commitString = commitDesc
2014-08-24 16:59:47 +04:00
}
2016-08-14 14:17:26 +03:00
repoLink := SlackLinkFormatter ( p . Repo . HTMLURL , p . Repo . Name )
2017-10-30 05:04:25 +03:00
branchLink := SlackLinkToRef ( p . Repo . HTMLURL , p . Ref )
2016-08-14 14:17:26 +03:00
text := fmt . Sprintf ( "[%s:%s] %s pushed by %s" , repoLink , branchLink , commitString , p . Pusher . UserName )
2014-08-24 16:59:47 +04:00
2015-08-28 18:36:13 +03:00
var attachmentText string
2014-08-24 16:59:47 +04:00
// for each commit, generate attachment text
for i , commit := range p . Commits {
2016-08-14 13:32:24 +03:00
attachmentText += fmt . Sprintf ( "%s: %s - %s" , SlackLinkFormatter ( commit . URL , commit . ID [ : 7 ] ) , SlackShortTextFormatter ( commit . Message ) , SlackTextFormatter ( commit . Author . Name ) )
2014-08-24 16:59:47 +04:00
// add linebreak to each commit but the last
if i < len ( p . Commits ) - 1 {
attachmentText += "\n"
}
}
2016-08-14 13:32:24 +03:00
return & SlackPayload {
Channel : slack . Channel ,
Text : text ,
Username : slack . Username ,
IconURL : slack . IconURL ,
Attachments : [ ] SlackAttachment { {
Color : slack . Color ,
Text : attachmentText ,
} } ,
} , nil
}
func getSlackPullRequestPayload ( p * api . PullRequestPayload , slack * SlackMeta ) ( * SlackPayload , error ) {
2016-11-27 13:14:25 +03:00
senderLink := SlackLinkFormatter ( setting . AppURL + p . Sender . UserName , p . Sender . UserName )
2016-08-14 14:17:26 +03:00
titleLink := SlackLinkFormatter ( fmt . Sprintf ( "%s/pulls/%d" , p . Repository . HTMLURL , p . Index ) ,
2016-08-14 13:32:24 +03:00
fmt . Sprintf ( "#%d %s" , p . Index , p . PullRequest . Title ) )
var text , title , attachmentText string
switch p . Action {
2016-11-07 18:37:32 +03:00
case api . HookIssueOpened :
2016-08-14 13:32:24 +03:00
text = fmt . Sprintf ( "[%s] Pull request submitted by %s" , p . Repository . FullName , senderLink )
title = titleLink
attachmentText = SlackTextFormatter ( p . PullRequest . Body )
2016-11-07 18:37:32 +03:00
case api . HookIssueClosed :
2016-08-14 13:32:24 +03:00
if p . PullRequest . HasMerged {
text = fmt . Sprintf ( "[%s] Pull request merged: %s by %s" , p . Repository . FullName , titleLink , senderLink )
} else {
text = fmt . Sprintf ( "[%s] Pull request closed: %s by %s" , p . Repository . FullName , titleLink , senderLink )
}
2016-11-29 11:25:47 +03:00
case api . HookIssueReOpened :
2016-08-14 13:32:24 +03:00
text = fmt . Sprintf ( "[%s] Pull request re-opened: %s by %s" , p . Repository . FullName , titleLink , senderLink )
2016-11-07 18:37:32 +03:00
case api . HookIssueEdited :
2016-08-14 13:32:24 +03:00
text = fmt . Sprintf ( "[%s] Pull request edited: %s by %s" , p . Repository . FullName , titleLink , senderLink )
attachmentText = SlackTextFormatter ( p . PullRequest . Body )
2016-11-07 18:37:32 +03:00
case api . HookIssueAssigned :
2018-05-09 19:29:04 +03:00
list , err := MakeAssigneeList ( & Issue { ID : p . PullRequest . ID } )
if err != nil {
return & SlackPayload { } , err
}
2016-08-14 13:32:24 +03:00
text = fmt . Sprintf ( "[%s] Pull request assigned to %s: %s by %s" , p . Repository . FullName ,
2018-05-09 19:29:04 +03:00
SlackLinkFormatter ( setting . AppURL + list , list ) ,
2016-08-14 13:32:24 +03:00
titleLink , senderLink )
2016-11-07 18:37:32 +03:00
case api . HookIssueUnassigned :
2016-08-14 13:32:24 +03:00
text = fmt . Sprintf ( "[%s] Pull request unassigned: %s by %s" , p . Repository . FullName , titleLink , senderLink )
2016-11-07 18:37:32 +03:00
case api . HookIssueLabelUpdated :
2016-08-14 13:32:24 +03:00
text = fmt . Sprintf ( "[%s] Pull request labels updated: %s by %s" , p . Repository . FullName , titleLink , senderLink )
2016-11-07 18:37:32 +03:00
case api . HookIssueLabelCleared :
2016-08-14 13:32:24 +03:00
text = fmt . Sprintf ( "[%s] Pull request labels cleared: %s by %s" , p . Repository . FullName , titleLink , senderLink )
2016-11-07 18:37:32 +03:00
case api . HookIssueSynchronized :
2016-08-14 13:32:24 +03:00
text = fmt . Sprintf ( "[%s] Pull request synchronized: %s by %s" , p . Repository . FullName , titleLink , senderLink )
}
2014-08-24 16:59:47 +04:00
return & SlackPayload {
2016-08-14 13:32:24 +03:00
Channel : slack . Channel ,
Text : text ,
Username : slack . Username ,
IconURL : slack . IconURL ,
Attachments : [ ] SlackAttachment { {
Color : slack . Color ,
Title : title ,
Text : attachmentText ,
} } ,
2014-08-24 16:59:47 +04:00
} , nil
}
2018-12-27 21:04:30 +03:00
func getSlackPullRequestApprovalPayload ( p * api . PullRequestPayload , slack * SlackMeta , event HookEventType ) ( * SlackPayload , error ) {
senderLink := SlackLinkFormatter ( setting . AppURL + p . Sender . UserName , p . Sender . UserName )
titleLink := SlackLinkFormatter ( fmt . Sprintf ( "%s/pulls/%d" , p . Repository . HTMLURL , p . Index ) ,
fmt . Sprintf ( "#%d %s" , p . Index , p . PullRequest . Title ) )
var text , title , attachmentText string
switch p . Action {
case api . HookIssueSynchronized :
action , err := parseHookPullRequestEventType ( event )
if err != nil {
return nil , err
}
text = fmt . Sprintf ( "[%s] Pull request review %s : %s by %s" , p . Repository . FullName , action , titleLink , senderLink )
}
return & SlackPayload {
Channel : slack . Channel ,
Text : text ,
Username : slack . Username ,
IconURL : slack . IconURL ,
Attachments : [ ] SlackAttachment { {
Color : slack . Color ,
Title : title ,
Text : attachmentText ,
} } ,
} , nil
}
2017-09-03 11:20:24 +03:00
func getSlackRepositoryPayload ( p * api . RepositoryPayload , slack * SlackMeta ) ( * SlackPayload , error ) {
senderLink := SlackLinkFormatter ( setting . AppURL + p . Sender . UserName , p . Sender . UserName )
var text , title , attachmentText string
switch p . Action {
case api . HookRepoCreated :
text = fmt . Sprintf ( "[%s] Repository created by %s" , p . Repository . FullName , senderLink )
title = p . Repository . HTMLURL
case api . HookRepoDeleted :
text = fmt . Sprintf ( "[%s] Repository deleted by %s" , p . Repository . FullName , senderLink )
}
return & SlackPayload {
Channel : slack . Channel ,
Text : text ,
Username : slack . Username ,
IconURL : slack . IconURL ,
Attachments : [ ] SlackAttachment { {
Color : slack . Color ,
Title : title ,
Text : attachmentText ,
} } ,
} , nil
}
2016-11-25 00:35:47 +03:00
// GetSlackPayload converts a slack webhook into a SlackPayload
2015-08-28 18:36:13 +03:00
func GetSlackPayload ( p api . Payloader , event HookEventType , meta string ) ( * SlackPayload , error ) {
s := new ( SlackPayload )
2014-08-26 16:20:18 +04:00
2015-08-28 18:36:13 +03:00
slack := & SlackMeta { }
if err := json . Unmarshal ( [ ] byte ( meta ) , & slack ) ; err != nil {
return s , errors . New ( "GetSlackPayload meta json:" + err . Error ( ) )
}
switch event {
2016-11-07 18:37:32 +03:00
case HookEventCreate :
2015-08-28 18:36:13 +03:00
return getSlackCreatePayload ( p . ( * api . CreatePayload ) , slack )
2018-05-16 17:01:55 +03:00
case HookEventDelete :
return getSlackDeletePayload ( p . ( * api . DeletePayload ) , slack )
case HookEventFork :
return getSlackForkPayload ( p . ( * api . ForkPayload ) , slack )
case HookEventIssues :
return getSlackIssuesPayload ( p . ( * api . IssuePayload ) , slack )
case HookEventIssueComment :
return getSlackIssueCommentPayload ( p . ( * api . IssueCommentPayload ) , slack )
2016-11-07 18:37:32 +03:00
case HookEventPush :
2015-08-28 18:36:13 +03:00
return getSlackPushPayload ( p . ( * api . PushPayload ) , slack )
2016-11-07 18:37:32 +03:00
case HookEventPullRequest :
2016-08-14 13:32:24 +03:00
return getSlackPullRequestPayload ( p . ( * api . PullRequestPayload ) , slack )
2018-12-27 21:04:30 +03:00
case HookEventPullRequestRejected , HookEventPullRequestApproved , HookEventPullRequestComment :
return getSlackPullRequestApprovalPayload ( p . ( * api . PullRequestPayload ) , slack , event )
2017-09-03 11:20:24 +03:00
case HookEventRepository :
return getSlackRepositoryPayload ( p . ( * api . RepositoryPayload ) , slack )
2018-05-16 17:01:55 +03:00
case HookEventRelease :
return getSlackReleasePayload ( p . ( * api . ReleasePayload ) , slack )
2015-08-28 18:36:13 +03:00
}
return s , nil
2014-08-26 16:20:18 +04:00
}