2017-10-25 08:13:45 +03:00
// 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.
2017-08-28 08:06:45 +03:00
package models
import (
"encoding/json"
"errors"
"fmt"
"strconv"
"strings"
2019-03-27 12:33:00 +03:00
"code.gitea.io/gitea/modules/git"
2017-08-28 08:06:45 +03:00
"code.gitea.io/gitea/modules/setting"
2019-05-11 13:21:34 +03:00
api "code.gitea.io/gitea/modules/structs"
2017-08-28 08:06:45 +03:00
)
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" `
}
)
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 (
successColor = color ( "1ac600" )
warnColor = color ( "ffd930" )
failedColor = color ( "ff3232" )
)
// SetSecret sets the discord secret
func ( p * DiscordPayload ) SetSecret ( _ string ) { }
// JSONPayload Marshals the DiscordPayload to json
func ( p * DiscordPayload ) JSONPayload ( ) ( [ ] byte , error ) {
data , err := json . MarshalIndent ( p , "" , " " )
if err != nil {
return [ ] byte { } , err
}
return data , nil
}
func getDiscordCreatePayload ( p * api . CreatePayload , meta * DiscordMeta ) ( * DiscordPayload , 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 : meta . Username ,
AvatarURL : meta . IconURL ,
Embeds : [ ] DiscordEmbed {
{
Title : title ,
URL : p . Repo . HTMLURL + "/src/" + refName ,
Color : successColor ,
Author : DiscordEmbedAuthor {
Name : p . Sender . UserName ,
URL : setting . AppURL + p . Sender . UserName ,
IconURL : p . Sender . AvatarURL ,
} ,
} ,
} ,
} , nil
}
2018-05-16 17:01:55 +03:00
func getDiscordDeletePayload ( p * api . DeletePayload , meta * DiscordMeta ) ( * DiscordPayload , 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 : meta . Username ,
AvatarURL : meta . IconURL ,
Embeds : [ ] DiscordEmbed {
{
Title : title ,
URL : p . Repo . HTMLURL + "/src/" + refName ,
Color : warnColor ,
Author : DiscordEmbedAuthor {
Name : p . Sender . UserName ,
URL : setting . AppURL + p . Sender . UserName ,
IconURL : p . Sender . AvatarURL ,
} ,
} ,
} ,
} , nil
}
func getDiscordForkPayload ( p * api . ForkPayload , meta * DiscordMeta ) ( * DiscordPayload , error ) {
// fork
title := fmt . Sprintf ( "%s is forked to %s" , p . Forkee . FullName , p . Repo . FullName )
return & DiscordPayload {
Username : meta . Username ,
AvatarURL : meta . IconURL ,
Embeds : [ ] DiscordEmbed {
{
Title : title ,
URL : p . Repo . HTMLURL ,
Color : successColor ,
Author : DiscordEmbedAuthor {
Name : p . Sender . UserName ,
URL : setting . AppURL + p . Sender . UserName ,
IconURL : p . Sender . AvatarURL ,
} ,
} ,
} ,
} , nil
}
2017-08-28 08:06:45 +03:00
func getDiscordPushPayload ( p * api . PushPayload , meta * DiscordMeta ) ( * DiscordPayload , 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 : meta . Username ,
AvatarURL : meta . IconURL ,
Embeds : [ ] DiscordEmbed {
{
Title : title ,
Description : text ,
URL : titleLink ,
Color : successColor ,
Author : DiscordEmbedAuthor {
Name : p . Sender . UserName ,
URL : setting . AppURL + p . Sender . UserName ,
IconURL : p . Sender . AvatarURL ,
} ,
} ,
} ,
} , nil
}
2018-05-16 17:01:55 +03:00
func getDiscordIssuesPayload ( p * api . IssuePayload , meta * DiscordMeta ) ( * DiscordPayload , error ) {
var text , title string
var color int
2018-09-18 10:04:46 +03:00
url := fmt . Sprintf ( "%s/issues/%d" , p . Repository . HTMLURL , p . Issue . Index )
2018-05-16 17:01:55 +03:00
switch p . Action {
case api . HookIssueOpened :
title = fmt . Sprintf ( "[%s] Issue opened: #%d %s" , p . Repository . FullName , p . Index , p . Issue . Title )
text = p . Issue . Body
color = warnColor
case api . HookIssueClosed :
title = fmt . Sprintf ( "[%s] Issue closed: #%d %s" , p . Repository . FullName , p . Index , p . Issue . Title )
color = failedColor
text = p . Issue . Body
case api . HookIssueReOpened :
title = fmt . Sprintf ( "[%s] Issue re-opened: #%d %s" , p . Repository . FullName , p . Index , p . Issue . Title )
text = p . Issue . Body
color = warnColor
case api . HookIssueEdited :
title = fmt . Sprintf ( "[%s] Issue edited: #%d %s" , p . Repository . FullName , p . Index , p . Issue . Title )
text = p . Issue . Body
color = warnColor
case api . HookIssueAssigned :
title = fmt . Sprintf ( "[%s] Issue assigned to %s: #%d %s" , p . Repository . FullName ,
p . Issue . Assignee . UserName , p . Index , p . Issue . Title )
text = p . Issue . Body
color = successColor
case api . HookIssueUnassigned :
title = fmt . Sprintf ( "[%s] Issue unassigned: #%d %s" , p . Repository . FullName , p . Index , p . Issue . Title )
text = p . Issue . Body
color = warnColor
case api . HookIssueLabelUpdated :
title = fmt . Sprintf ( "[%s] Issue labels updated: #%d %s" , p . Repository . FullName , p . Index , p . Issue . Title )
text = p . Issue . Body
color = warnColor
case api . HookIssueLabelCleared :
title = fmt . Sprintf ( "[%s] Issue labels cleared: #%d %s" , p . Repository . FullName , p . Index , p . Issue . Title )
text = p . Issue . Body
color = warnColor
case api . HookIssueSynchronized :
title = fmt . Sprintf ( "[%s] Issue synchronized: #%d %s" , p . Repository . FullName , p . Index , p . Issue . Title )
text = p . Issue . Body
color = warnColor
2018-05-21 05:28:29 +03:00
case api . HookIssueMilestoned :
title = fmt . Sprintf ( "[%s] Issue milestone: #%d %s" , p . Repository . FullName , p . Index , p . Issue . Title )
text = p . Issue . Body
color = warnColor
case api . HookIssueDemilestoned :
title = fmt . Sprintf ( "[%s] Issue clear milestone: #%d %s" , p . Repository . FullName , p . Index , p . Issue . Title )
text = p . Issue . Body
color = warnColor
2018-05-16 17:01:55 +03:00
}
return & DiscordPayload {
Username : meta . Username ,
AvatarURL : meta . IconURL ,
Embeds : [ ] DiscordEmbed {
{
Title : title ,
Description : text ,
2018-09-18 10:04:46 +03:00
URL : url ,
2018-05-16 17:01:55 +03:00
Color : color ,
Author : DiscordEmbedAuthor {
Name : p . Sender . UserName ,
URL : setting . AppURL + p . Sender . UserName ,
IconURL : p . Sender . AvatarURL ,
} ,
} ,
} ,
} , nil
}
func getDiscordIssueCommentPayload ( p * api . IssueCommentPayload , discord * DiscordMeta ) ( * DiscordPayload , error ) {
title := fmt . Sprintf ( "#%d %s" , p . Issue . Index , p . Issue . Title )
url := fmt . Sprintf ( "%s/issues/%d#%s" , p . Repository . HTMLURL , p . Issue . Index , CommentHashTag ( p . Comment . ID ) )
content := ""
var color int
switch p . Action {
case api . HookIssueCommentCreated :
title = "New comment: " + title
content = p . Comment . Body
color = successColor
case api . HookIssueCommentEdited :
title = "Comment edited: " + title
content = p . Comment . Body
color = warnColor
case api . HookIssueCommentDeleted :
title = "Comment deleted: " + title
url = fmt . Sprintf ( "%s/issues/%d" , p . Repository . HTMLURL , p . Issue . Index )
content = p . Comment . Body
color = warnColor
}
return & DiscordPayload {
Username : discord . Username ,
AvatarURL : discord . IconURL ,
Embeds : [ ] DiscordEmbed {
{
Title : title ,
Description : content ,
URL : url ,
Color : color ,
Author : DiscordEmbedAuthor {
Name : p . Sender . UserName ,
URL : setting . AppURL + p . Sender . UserName ,
IconURL : p . Sender . AvatarURL ,
} ,
} ,
} ,
} , nil
}
2017-08-28 08:06:45 +03:00
func getDiscordPullRequestPayload ( p * api . PullRequestPayload , meta * DiscordMeta ) ( * DiscordPayload , error ) {
var text , title string
var color int
switch p . Action {
case api . HookIssueOpened :
title = fmt . Sprintf ( "[%s] Pull request opened: #%d %s" , p . Repository . FullName , p . Index , p . PullRequest . Title )
text = p . PullRequest . Body
color = warnColor
case api . HookIssueClosed :
if p . PullRequest . HasMerged {
title = fmt . Sprintf ( "[%s] Pull request merged: #%d %s" , p . Repository . FullName , p . Index , p . PullRequest . Title )
color = successColor
} else {
title = fmt . Sprintf ( "[%s] Pull request closed: #%d %s" , p . Repository . FullName , p . Index , p . PullRequest . Title )
color = failedColor
}
text = p . PullRequest . Body
case api . HookIssueReOpened :
title = fmt . Sprintf ( "[%s] Pull request re-opened: #%d %s" , p . Repository . FullName , p . Index , p . PullRequest . Title )
text = p . PullRequest . Body
color = warnColor
case api . HookIssueEdited :
title = fmt . Sprintf ( "[%s] Pull request edited: #%d %s" , p . Repository . FullName , p . Index , p . PullRequest . Title )
text = p . PullRequest . Body
color = warnColor
case api . HookIssueAssigned :
2019-02-18 00:12:39 +03:00
list := make ( [ ] string , len ( p . PullRequest . Assignees ) )
for i , user := range p . PullRequest . Assignees {
list [ i ] = user . UserName
2018-05-09 19:29:04 +03:00
}
2019-02-18 00:12:39 +03:00
title = fmt . Sprintf ( "[%s] Pull request assigned to %s: #%d by %s" , p . Repository . FullName ,
strings . Join ( list , ", " ) ,
p . Index , p . PullRequest . Title )
2017-08-28 08:06:45 +03:00
text = p . PullRequest . Body
color = successColor
case api . HookIssueUnassigned :
title = fmt . Sprintf ( "[%s] Pull request unassigned: #%d %s" , p . Repository . FullName , p . Index , p . PullRequest . Title )
text = p . PullRequest . Body
color = warnColor
case api . HookIssueLabelUpdated :
title = fmt . Sprintf ( "[%s] Pull request labels updated: #%d %s" , p . Repository . FullName , p . Index , p . PullRequest . Title )
text = p . PullRequest . Body
color = warnColor
case api . HookIssueLabelCleared :
title = fmt . Sprintf ( "[%s] Pull request labels cleared: #%d %s" , p . Repository . FullName , p . Index , p . PullRequest . Title )
text = p . PullRequest . Body
color = warnColor
case api . HookIssueSynchronized :
title = fmt . Sprintf ( "[%s] Pull request synchronized: #%d %s" , p . Repository . FullName , p . Index , p . PullRequest . Title )
text = p . PullRequest . Body
color = warnColor
2018-05-21 05:28:29 +03:00
case api . HookIssueMilestoned :
title = fmt . Sprintf ( "[%s] Pull request milestone: #%d %s" , p . Repository . FullName , p . Index , p . PullRequest . Title )
text = p . PullRequest . Body
color = warnColor
case api . HookIssueDemilestoned :
title = fmt . Sprintf ( "[%s] Pull request clear milestone: #%d %s" , p . Repository . FullName , p . Index , p . PullRequest . Title )
text = p . PullRequest . Body
color = warnColor
2017-08-28 08:06:45 +03:00
}
return & DiscordPayload {
Username : meta . Username ,
AvatarURL : meta . IconURL ,
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
}
2018-12-27 21:04:30 +03:00
func getDiscordPullRequestApprovalPayload ( p * api . PullRequestPayload , meta * DiscordMeta , event HookEventType ) ( * DiscordPayload , error ) {
var text , title string
var color int
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 . PullRequest . Body
color = warnColor
}
return & DiscordPayload {
Username : meta . Username ,
AvatarURL : meta . IconURL ,
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
}
2017-09-03 11:20:24 +03:00
func getDiscordRepositoryPayload ( p * api . RepositoryPayload , meta * DiscordMeta ) ( * DiscordPayload , 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 = successColor
case api . HookRepoDeleted :
title = fmt . Sprintf ( "[%s] Repository deleted" , p . Repository . FullName )
color = warnColor
}
return & DiscordPayload {
Username : meta . Username ,
AvatarURL : meta . IconURL ,
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
}
2018-05-16 17:01:55 +03:00
func getDiscordReleasePayload ( p * api . ReleasePayload , meta * DiscordMeta ) ( * DiscordPayload , error ) {
var title , url string
var color int
switch p . Action {
case api . HookReleasePublished :
title = fmt . Sprintf ( "[%s] Release created" , p . Release . TagName )
url = p . Release . URL
color = successColor
2018-05-21 05:28:29 +03:00
case api . HookReleaseUpdated :
title = fmt . Sprintf ( "[%s] Release updated" , p . Release . TagName )
url = p . Release . URL
color = successColor
case api . HookReleaseDeleted :
title = fmt . Sprintf ( "[%s] Release deleted" , p . Release . TagName )
url = p . Release . URL
color = successColor
2018-05-16 17:01:55 +03:00
}
return & DiscordPayload {
Username : meta . Username ,
AvatarURL : meta . IconURL ,
Embeds : [ ] DiscordEmbed {
{
Title : title ,
2019-06-12 22:41:28 +03:00
Description : p . Release . Note ,
2018-05-16 17:01:55 +03:00
URL : url ,
Color : color ,
Author : DiscordEmbedAuthor {
Name : p . Sender . UserName ,
URL : setting . AppURL + p . Sender . UserName ,
IconURL : p . Sender . AvatarURL ,
} ,
} ,
} ,
} , nil
}
2017-08-28 08:06:45 +03:00
// GetDiscordPayload converts a discord webhook into a DiscordPayload
func GetDiscordPayload ( p api . Payloader , event HookEventType , meta string ) ( * DiscordPayload , error ) {
s := new ( DiscordPayload )
discord := & DiscordMeta { }
if err := json . Unmarshal ( [ ] byte ( meta ) , & discord ) ; err != nil {
return s , errors . New ( "GetDiscordPayload meta json:" + err . Error ( ) )
}
switch event {
case HookEventCreate :
return getDiscordCreatePayload ( p . ( * api . CreatePayload ) , discord )
2018-05-16 17:01:55 +03:00
case HookEventDelete :
return getDiscordDeletePayload ( p . ( * api . DeletePayload ) , discord )
case HookEventFork :
return getDiscordForkPayload ( p . ( * api . ForkPayload ) , discord )
case HookEventIssues :
return getDiscordIssuesPayload ( p . ( * api . IssuePayload ) , discord )
case HookEventIssueComment :
return getDiscordIssueCommentPayload ( p . ( * api . IssueCommentPayload ) , discord )
2017-08-28 08:06:45 +03:00
case HookEventPush :
return getDiscordPushPayload ( p . ( * api . PushPayload ) , discord )
case HookEventPullRequest :
return getDiscordPullRequestPayload ( p . ( * api . PullRequestPayload ) , discord )
2018-12-27 21:04:30 +03:00
case HookEventPullRequestRejected , HookEventPullRequestApproved , HookEventPullRequestComment :
return getDiscordPullRequestApprovalPayload ( p . ( * api . PullRequestPayload ) , discord , event )
2017-09-03 11:20:24 +03:00
case HookEventRepository :
return getDiscordRepositoryPayload ( p . ( * api . RepositoryPayload ) , discord )
2018-05-16 17:01:55 +03:00
case HookEventRelease :
return getDiscordReleasePayload ( p . ( * api . ReleasePayload ) , discord )
2017-08-28 08:06:45 +03:00
}
return s , nil
}
2018-12-27 21:04:30 +03:00
func parseHookPullRequestEventType ( event HookEventType ) ( string , error ) {
switch event {
case HookEventPullRequestApproved :
return "approved" , nil
case HookEventPullRequestRejected :
return "rejected" , nil
case HookEventPullRequestComment :
return "comment" , nil
default :
return "" , errors . New ( "unknown event type" )
}
}