2017-10-25 08:13:45 +03:00
// Copyright 2017 The Gitea Authors. All rights reserved.
2022-11-27 21:20:29 +03:00
// SPDX-License-Identifier: MIT
2017-10-25 08:13:45 +03:00
2019-11-04 01:13:25 +03:00
package webhook
2017-08-28 08:06:45 +03:00
import (
"errors"
"fmt"
2022-09-04 22:54:23 +03:00
"net/url"
2017-08-28 08:06:45 +03:00
"strconv"
"strings"
2021-11-10 08:13:16 +03:00
webhook_model "code.gitea.io/gitea/models/webhook"
2019-03-27 12:33:00 +03:00
"code.gitea.io/gitea/modules/git"
2021-07-24 19:03:58 +03:00
"code.gitea.io/gitea/modules/json"
2019-11-04 01:13:25 +03:00
"code.gitea.io/gitea/modules/log"
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"
2021-11-16 21:18:25 +03:00
"code.gitea.io/gitea/modules/util"
2023-01-01 18:23:15 +03:00
webhook_module "code.gitea.io/gitea/modules/webhook"
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" `
2023-01-10 22:01:52 +03:00
AvatarURL string ` json:"avatar_url,omitempty" `
2017-08-28 08:06:45 +03:00
TTS bool ` json:"tts" `
Embeds [ ] DiscordEmbed ` json:"embeds" `
}
// DiscordMeta contains the discord metadata
DiscordMeta struct {
Username string ` json:"username" `
IconURL string ` json:"icon_url" `
}
)
2019-11-04 01:13:25 +03:00
// GetDiscordHook returns discord metadata
2021-11-10 08:13:16 +03:00
func GetDiscordHook ( w * webhook_model . Webhook ) * DiscordMeta {
2019-11-04 01:13:25 +03:00
s := & DiscordMeta { }
if err := json . Unmarshal ( [ ] byte ( w . Meta ) , s ) ; err != nil {
log . Error ( "webhook.GetDiscordHook(%d): %v" , w . ID , err )
}
return s
}
2017-08-28 08:06:45 +03:00
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 (
2019-10-19 01:42:04 +03:00
greenColor = color ( "1ac600" )
greenColorLight = color ( "bfe5bf" )
yellowColor = color ( "ffd930" )
greyColor = color ( "4f545c" )
purpleColor = color ( "7289da" )
orangeColor = color ( "eb6420" )
orangeColorLight = color ( "e68d60" )
redColor = color ( "ff3232" )
2017-08-28 08:06:45 +03:00
)
// JSONPayload Marshals the DiscordPayload to json
2020-09-05 05:57:13 +03:00
func ( d * DiscordPayload ) JSONPayload ( ) ( [ ] byte , error ) {
data , err := json . MarshalIndent ( d , "" , " " )
2017-08-28 08:06:45 +03:00
if err != nil {
return [ ] byte { } , err
}
return data , nil
}
2022-01-20 20:46:10 +03:00
var _ PayloadConvertor = & DiscordPayload { }
2020-09-05 05:57:13 +03:00
// Create implements PayloadConvertor Create method
func ( d * DiscordPayload ) Create ( p * api . CreatePayload ) ( api . Payloader , error ) {
2017-08-28 08:06:45 +03:00
// created tag/branch
refName := git . RefEndName ( p . Ref )
title := fmt . Sprintf ( "[%s] %s %s created" , p . Repo . FullName , p . RefType , refName )
2021-11-16 21:18:25 +03:00
return d . createPayload ( p . Sender , title , "" , p . Repo . HTMLURL + "/src/" + util . PathEscapeSegments ( refName ) , greenColor ) , nil
2017-08-28 08:06:45 +03:00
}
2020-09-05 05:57:13 +03:00
// Delete implements PayloadConvertor Delete method
func ( d * DiscordPayload ) Delete ( p * api . DeletePayload ) ( api . Payloader , error ) {
2018-05-16 17:01:55 +03:00
// deleted tag/branch
refName := git . RefEndName ( p . Ref )
title := fmt . Sprintf ( "[%s] %s %s deleted" , p . Repo . FullName , p . RefType , refName )
2021-11-16 21:18:25 +03:00
return d . createPayload ( p . Sender , title , "" , p . Repo . HTMLURL + "/src/" + util . PathEscapeSegments ( refName ) , redColor ) , nil
2018-05-16 17:01:55 +03:00
}
2020-09-05 05:57:13 +03:00
// Fork implements PayloadConvertor Fork method
func ( d * DiscordPayload ) Fork ( p * api . ForkPayload ) ( api . Payloader , error ) {
2018-05-16 17:01:55 +03:00
title := fmt . Sprintf ( "%s is forked to %s" , p . Forkee . FullName , p . Repo . FullName )
2021-06-21 05:12:19 +03:00
return d . createPayload ( p . Sender , title , "" , p . Repo . HTMLURL , greenColor ) , nil
2018-05-16 17:01:55 +03:00
}
2020-09-05 05:57:13 +03:00
// Push implements PayloadConvertor Push method
func ( d * DiscordPayload ) Push ( p * api . PushPayload ) ( api . Payloader , error ) {
2017-08-28 08:06:45 +03:00
var (
branchName = git . RefEndName ( p . Ref )
commitDesc string
)
var titleLink string
2022-10-16 19:22:34 +03:00
if p . TotalCommits == 1 {
2017-08-28 08:06:45 +03:00
commitDesc = "1 new commit"
titleLink = p . Commits [ 0 ] . URL
} else {
2022-10-16 19:22:34 +03:00
commitDesc = fmt . Sprintf ( "%d new commits" , p . TotalCommits )
2017-08-28 08:06:45 +03:00
titleLink = p . CompareURL
}
if titleLink == "" {
2021-11-16 21:18:25 +03:00
titleLink = p . Repo . HTMLURL + "/src/" + util . PathEscapeSegments ( branchName )
2017-08-28 08:06:45 +03:00
}
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"
}
}
2021-06-21 05:12:19 +03:00
return d . createPayload ( p . Sender , title , text , titleLink , greenColor ) , nil
2017-08-28 08:06:45 +03:00
}
2020-09-05 05:57:13 +03:00
// Issue implements PayloadConvertor Issue method
func ( d * DiscordPayload ) Issue ( p * api . IssuePayload ) ( api . Payloader , error ) {
2021-06-21 05:12:19 +03:00
title , _ , text , color := getIssuesPayloadInfo ( p , noneLinkFormatter , false )
2018-05-16 17:01:55 +03:00
2021-06-21 05:12:19 +03:00
return d . createPayload ( p . Sender , title , text , p . Issue . HTMLURL , color ) , nil
2018-05-16 17:01:55 +03:00
}
2020-09-05 05:57:13 +03:00
// IssueComment implements PayloadConvertor IssueComment method
func ( d * DiscordPayload ) IssueComment ( p * api . IssueCommentPayload ) ( api . Payloader , error ) {
2021-06-21 05:12:19 +03:00
title , _ , color := getIssueCommentPayloadInfo ( p , noneLinkFormatter , false )
2019-10-19 01:42:04 +03:00
2021-06-21 05:12:19 +03:00
return d . createPayload ( p . Sender , title , p . Comment . Body , p . Comment . HTMLURL , color ) , nil
2018-05-16 17:01:55 +03:00
}
2020-09-05 05:57:13 +03:00
// PullRequest implements PayloadConvertor PullRequest method
func ( d * DiscordPayload ) PullRequest ( p * api . PullRequestPayload ) ( api . Payloader , error ) {
2021-06-21 05:12:19 +03:00
title , _ , text , color := getPullRequestPayloadInfo ( p , noneLinkFormatter , false )
2017-08-28 08:06:45 +03:00
2021-06-21 05:12:19 +03:00
return d . createPayload ( p . Sender , title , text , p . PullRequest . HTMLURL , color ) , nil
2017-08-28 08:06:45 +03:00
}
2020-09-05 05:57:13 +03:00
// Review implements PayloadConvertor Review method
2023-01-01 18:23:15 +03:00
func ( d * DiscordPayload ) Review ( p * api . PullRequestPayload , event webhook_module . HookEventType ) ( api . Payloader , error ) {
2018-12-27 21:04:30 +03:00
var text , title string
var color int
switch p . Action {
2020-03-06 08:10:48 +03:00
case api . HookIssueReviewed :
2018-12-27 21:04:30 +03:00
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 )
2019-10-19 01:42:04 +03:00
text = p . Review . Content
2019-10-18 11:33:19 +03:00
switch event {
2023-01-01 18:23:15 +03:00
case webhook_module . HookEventPullRequestReviewApproved :
2019-10-19 01:42:04 +03:00
color = greenColor
2023-01-01 18:23:15 +03:00
case webhook_module . HookEventPullRequestReviewRejected :
2019-10-19 01:42:04 +03:00
color = redColor
2023-03-24 08:13:04 +03:00
case webhook_module . HookEventPullRequestReviewComment :
2019-10-19 01:42:04 +03:00
color = greyColor
2019-10-18 11:33:19 +03:00
default :
2019-10-19 01:42:04 +03:00
color = yellowColor
2019-10-18 11:33:19 +03:00
}
2018-12-27 21:04:30 +03:00
}
2021-06-21 05:12:19 +03:00
return d . createPayload ( p . Sender , title , text , p . PullRequest . HTMLURL , color ) , nil
2018-12-27 21:04:30 +03:00
}
2020-09-05 05:57:13 +03:00
// Repository implements PayloadConvertor Repository method
func ( d * DiscordPayload ) Repository ( p * api . RepositoryPayload ) ( api . Payloader , error ) {
2017-09-03 11:20:24 +03:00
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
2019-10-19 01:42:04 +03:00
color = greenColor
2017-09-03 11:20:24 +03:00
case api . HookRepoDeleted :
title = fmt . Sprintf ( "[%s] Repository deleted" , p . Repository . FullName )
2019-10-19 01:42:04 +03:00
color = redColor
2017-09-03 11:20:24 +03:00
}
2021-06-21 05:12:19 +03:00
return d . createPayload ( p . Sender , title , "" , url , color ) , nil
2017-09-03 11:20:24 +03:00
}
2022-09-04 22:54:23 +03:00
// Wiki implements PayloadConvertor Wiki method
func ( d * DiscordPayload ) Wiki ( p * api . WikiPayload ) ( api . Payloader , error ) {
text , color , _ := getWikiPayloadInfo ( p , noneLinkFormatter , false )
htmlLink := p . Repository . HTMLURL + "/wiki/" + url . PathEscape ( p . Page )
var description string
if p . Action != api . HookWikiDeleted {
description = p . Comment
}
return d . createPayload ( p . Sender , text , description , htmlLink , color ) , nil
}
2020-09-05 05:57:13 +03:00
// Release implements PayloadConvertor Release method
func ( d * DiscordPayload ) Release ( p * api . ReleasePayload ) ( api . Payloader , error ) {
2020-01-05 01:20:15 +03:00
text , color := getReleasePayloadInfo ( p , noneLinkFormatter , false )
2018-05-16 17:01:55 +03:00
2021-06-21 05:12:19 +03:00
return d . createPayload ( p . Sender , text , p . Release . Note , p . Release . URL , color ) , nil
2018-05-16 17:01:55 +03:00
}
2017-08-28 08:06:45 +03:00
// GetDiscordPayload converts a discord webhook into a DiscordPayload
2023-01-01 18:23:15 +03:00
func GetDiscordPayload ( p api . Payloader , event webhook_module . HookEventType , meta string ) ( api . Payloader , error ) {
2017-08-28 08:06:45 +03:00
s := new ( DiscordPayload )
discord := & DiscordMeta { }
if err := json . Unmarshal ( [ ] byte ( meta ) , & discord ) ; err != nil {
return s , errors . New ( "GetDiscordPayload meta json:" + err . Error ( ) )
}
2020-09-05 05:57:13 +03:00
s . Username = discord . Username
s . AvatarURL = discord . IconURL
2017-08-28 08:06:45 +03:00
2020-09-05 05:57:13 +03:00
return convertPayloader ( s , p , event )
2017-08-28 08:06:45 +03:00
}
2018-12-27 21:04:30 +03:00
2023-01-01 18:23:15 +03:00
func parseHookPullRequestEventType ( event webhook_module . HookEventType ) ( string , error ) {
2018-12-27 21:04:30 +03:00
switch event {
2023-01-01 18:23:15 +03:00
case webhook_module . HookEventPullRequestReviewApproved :
2018-12-27 21:04:30 +03:00
return "approved" , nil
2023-01-01 18:23:15 +03:00
case webhook_module . HookEventPullRequestReviewRejected :
2018-12-27 21:04:30 +03:00
return "rejected" , nil
2023-03-24 08:13:04 +03:00
case webhook_module . HookEventPullRequestReviewComment :
2018-12-27 21:04:30 +03:00
return "comment" , nil
default :
return "" , errors . New ( "unknown event type" )
}
}
2021-06-21 05:12:19 +03:00
func ( d * DiscordPayload ) createPayload ( s * api . User , title , text , url string , color int ) * DiscordPayload {
return & DiscordPayload {
Username : d . Username ,
AvatarURL : d . AvatarURL ,
Embeds : [ ] DiscordEmbed {
{
Title : title ,
Description : text ,
URL : url ,
Color : color ,
Author : DiscordEmbedAuthor {
Name : s . UserName ,
URL : setting . AppURL + s . UserName ,
IconURL : s . AvatarURL ,
} ,
} ,
} ,
}
}