Skip to content

Commit abe3230

Browse files
committed
implementation of discord webhook
1 parent d9d8fad commit abe3230

File tree

12 files changed

+402
-6
lines changed

12 files changed

+402
-6
lines changed

models/webhook.go

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -314,12 +314,14 @@ const (
314314
GOGS HookTaskType = iota + 1
315315
SLACK
316316
GITEA
317+
DISCORD
317318
)
318319

319320
var hookTaskTypes = map[string]HookTaskType{
320-
"gitea": GITEA,
321-
"gogs": GOGS,
322-
"slack": SLACK,
321+
"gitea": GITEA,
322+
"gogs": GOGS,
323+
"slack": SLACK,
324+
"discord": DISCORD,
323325
}
324326

325327
// ToHookTaskType returns HookTaskType by given name.
@@ -336,6 +338,8 @@ func (t HookTaskType) Name() string {
336338
return "gogs"
337339
case SLACK:
338340
return "slack"
341+
case DISCORD:
342+
return "discord"
339343
}
340344
return ""
341345
}
@@ -515,6 +519,11 @@ func PrepareWebhooks(repo *Repository, event HookEventType, p api.Payloader) err
515519
if err != nil {
516520
return fmt.Errorf("GetSlackPayload: %v", err)
517521
}
522+
case DISCORD:
523+
payloader, err = GetDiscordPayload(p, event, w.Meta)
524+
if err != nil {
525+
return fmt.Errorf("GetSlackPayload: %v", err)
526+
}
518527
default:
519528
p.SetSecret(w.Secret)
520529
payloader = p

models/webhook_discord.go

Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
package models
2+
3+
import (
4+
"encoding/json"
5+
"errors"
6+
"fmt"
7+
"strings"
8+
9+
"code.gitea.io/git"
10+
api "code.gitea.io/sdk/gitea"
11+
12+
"code.gitea.io/gitea/modules/setting"
13+
)
14+
15+
type (
16+
// DiscordEmbedFooter for Embed Footer Structure.
17+
DiscordEmbedFooter struct {
18+
Text string `json:"text"`
19+
}
20+
21+
// DiscordEmbedAuthor for Embed Author Structure
22+
DiscordEmbedAuthor struct {
23+
Name string `json:"name"`
24+
URL string `json:"url"`
25+
IconURL string `json:"icon_url"`
26+
}
27+
28+
// DiscordEmbedField for Embed Field Structure
29+
DiscordEmbedField struct {
30+
Name string `json:"name"`
31+
Value string `json:"value"`
32+
}
33+
34+
// DiscordEmbed is for Embed Structure
35+
DiscordEmbed struct {
36+
Title string `json:"title"`
37+
Description string `json:"description"`
38+
URL string `json:"url"`
39+
Color int `json:"color"`
40+
Footer DiscordEmbedFooter `json:"footer"`
41+
Author DiscordEmbedAuthor `json:"author"`
42+
Fields []DiscordEmbedField `json:"fields"`
43+
}
44+
45+
// DiscordPayload represents
46+
DiscordPayload struct {
47+
Wait bool `json:"wait"`
48+
Content string `json:"content"`
49+
Username string `json:"username"`
50+
AvatarURL string `json:"avatar_url"`
51+
TTS bool `json:"tts"`
52+
Embeds []DiscordEmbed `json:"embeds"`
53+
}
54+
55+
// DiscordMeta contains the discord metadata
56+
DiscordMeta struct {
57+
Username string `json:"username"`
58+
IconURL string `json:"icon_url"`
59+
Color int `json:"color"`
60+
}
61+
)
62+
63+
// SetSecret sets the slack secret
64+
func (p *DiscordPayload) SetSecret(_ string) {}
65+
66+
// JSONPayload Marshals the SlackPayload to json
67+
func (p *DiscordPayload) JSONPayload() ([]byte, error) {
68+
data, err := json.MarshalIndent(p, "", " ")
69+
if err != nil {
70+
return []byte{}, err
71+
}
72+
return data, nil
73+
}
74+
75+
func replaceBadCharsForDiscord(in string) string {
76+
return strings.NewReplacer("[", "", "]", ":", ":", "/").Replace(in)
77+
}
78+
79+
func getDiscordCreatePayload(p *api.CreatePayload, meta *DiscordMeta) (*DiscordPayload, error) {
80+
// created tag/branch
81+
refName := git.RefEndName(p.Ref)
82+
83+
repoLink := SlackLinkFormatter(p.Repo.HTMLURL, p.Repo.Name)
84+
refLink := SlackLinkFormatter(p.Repo.HTMLURL+"/src/"+refName, refName)
85+
86+
format := "[%s:%s] %s created by %s"
87+
format = replaceBadCharsForDiscord(format)
88+
text := fmt.Sprintf(format, repoLink, refLink, p.RefType, p.Sender.UserName)
89+
90+
var username = meta.Username
91+
if username == "" {
92+
username = "Gitea"
93+
}
94+
95+
return &DiscordPayload{
96+
Content: text,
97+
Username: username,
98+
AvatarURL: meta.IconURL,
99+
}, nil
100+
}
101+
102+
func getDiscordPushPayload(p *api.PushPayload, meta *DiscordMeta) (*DiscordPayload, error) {
103+
// n new commits
104+
var (
105+
branchName = git.RefEndName(p.Ref)
106+
commitDesc string
107+
commitString string
108+
)
109+
110+
if len(p.Commits) == 1 {
111+
commitDesc = "1 new commit"
112+
} else {
113+
commitDesc = fmt.Sprintf("%d new commits", len(p.Commits))
114+
}
115+
if len(p.CompareURL) > 0 {
116+
commitString = SlackLinkFormatter(p.CompareURL, commitDesc)
117+
} else {
118+
commitString = commitDesc
119+
}
120+
121+
repoLink := SlackLinkFormatter(p.Repo.HTMLURL, p.Repo.Name)
122+
branchLink := SlackLinkFormatter(p.Repo.HTMLURL+"/src/"+branchName, branchName)
123+
124+
format := "[%s:%s] %s pushed by %s"
125+
format = replaceBadCharsForDiscord(format)
126+
text := fmt.Sprintf(format, repoLink, branchLink, commitString, p.Pusher.UserName)
127+
128+
var attachmentText string
129+
// for each commit, generate attachment text
130+
for i, commit := range p.Commits {
131+
attachmentText += fmt.Sprintf("%s: %s - %s", SlackLinkFormatter(commit.URL, commit.ID[:7]), SlackShortTextFormatter(commit.Message), SlackTextFormatter(commit.Author.Name))
132+
// add linebreak to each commit but the last
133+
if i < len(p.Commits)-1 {
134+
attachmentText += "\n"
135+
}
136+
}
137+
138+
var username = meta.Username
139+
if username == "" {
140+
username = "Gitea"
141+
}
142+
143+
return &DiscordPayload{
144+
//Content: text,
145+
Username: username,
146+
AvatarURL: meta.IconURL,
147+
Embeds: []DiscordEmbed{
148+
{
149+
Title: text,
150+
Description: attachmentText,
151+
//URL: branchLink, // FIXME
152+
Color: meta.Color,
153+
Author: DiscordEmbedAuthor{
154+
Name: p.Sender.UserName,
155+
URL: setting.AppURL + p.Sender.UserName,
156+
IconURL: p.Sender.AvatarURL,
157+
},
158+
},
159+
},
160+
}, nil
161+
}
162+
163+
func getDiscordPullRequestPayload(p *api.PullRequestPayload, meta *DiscordMeta) (*DiscordPayload, error) {
164+
senderLink := SlackLinkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName)
165+
titleLink := SlackLinkFormatter(fmt.Sprintf("%s/pulls/%d", p.Repository.HTMLURL, p.Index),
166+
fmt.Sprintf("#%d %s", p.Index, p.PullRequest.Title))
167+
var text, title string
168+
switch p.Action {
169+
case api.HookIssueOpened:
170+
text = fmt.Sprintf("[%s] Pull request submitted by %s", p.Repository.FullName, senderLink)
171+
title = titleLink
172+
//attachmentText = SlackTextFormatter(p.PullRequest.Body)
173+
case api.HookIssueClosed:
174+
if p.PullRequest.HasMerged {
175+
text = fmt.Sprintf("[%s] Pull request merged: %s by %s", p.Repository.FullName, titleLink, senderLink)
176+
} else {
177+
text = fmt.Sprintf("[%s] Pull request closed: %s by %s", p.Repository.FullName, titleLink, senderLink)
178+
}
179+
case api.HookIssueReOpened:
180+
text = fmt.Sprintf("[%s] Pull request re-opened: %s by %s", p.Repository.FullName, titleLink, senderLink)
181+
case api.HookIssueEdited:
182+
text = fmt.Sprintf("[%s] Pull request edited: %s by %s", p.Repository.FullName, titleLink, senderLink)
183+
//attachmentText = SlackTextFormatter(p.PullRequest.Body)
184+
case api.HookIssueAssigned:
185+
text = fmt.Sprintf("[%s] Pull request assigned to %s: %s by %s", p.Repository.FullName,
186+
SlackLinkFormatter(setting.AppURL+p.PullRequest.Assignee.UserName, p.PullRequest.Assignee.UserName),
187+
titleLink, senderLink)
188+
case api.HookIssueUnassigned:
189+
text = fmt.Sprintf("[%s] Pull request unassigned: %s by %s", p.Repository.FullName, titleLink, senderLink)
190+
case api.HookIssueLabelUpdated:
191+
text = fmt.Sprintf("[%s] Pull request labels updated: %s by %s", p.Repository.FullName, titleLink, senderLink)
192+
case api.HookIssueLabelCleared:
193+
text = fmt.Sprintf("[%s] Pull request labels cleared: %s by %s", p.Repository.FullName, titleLink, senderLink)
194+
case api.HookIssueSynchronized:
195+
text = fmt.Sprintf("[%s] Pull request synchronized: %s by %s", p.Repository.FullName, titleLink, senderLink)
196+
}
197+
198+
var username = meta.Username
199+
if username == "" {
200+
username = "Gitea"
201+
}
202+
203+
return &DiscordPayload{
204+
//Content: text,
205+
Username: username,
206+
AvatarURL: meta.IconURL,
207+
Embeds: []DiscordEmbed{
208+
{
209+
Title: title,
210+
Description: text,
211+
URL: p.PullRequest.HTMLURL,
212+
Color: meta.Color,
213+
Author: DiscordEmbedAuthor{
214+
Name: p.Sender.UserName,
215+
URL: setting.AppURL + p.Sender.UserName,
216+
IconURL: p.Sender.AvatarURL,
217+
},
218+
},
219+
},
220+
}, nil
221+
}
222+
223+
// GetDiscordPayload converts a slack webhook into a SlackPayload
224+
func GetDiscordPayload(p api.Payloader, event HookEventType, meta string) (*DiscordPayload, error) {
225+
s := new(DiscordPayload)
226+
227+
discord := &DiscordMeta{}
228+
if err := json.Unmarshal([]byte(meta), &discord); err != nil {
229+
return s, errors.New("GetSlackPayload meta json:" + err.Error())
230+
}
231+
232+
switch event {
233+
case HookEventCreate:
234+
return getDiscordCreatePayload(p.(*api.CreatePayload), discord)
235+
case HookEventPush:
236+
return getDiscordPushPayload(p.(*api.PushPayload), discord)
237+
case HookEventPullRequest:
238+
return getDiscordPullRequestPayload(p.(*api.PullRequestPayload), discord)
239+
}
240+
241+
return s, nil
242+
}

models/webhook_slack.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,9 @@ func getSlackCreatePayload(p *api.CreatePayload, slack *SlackMeta) (*SlackPayloa
8686

8787
repoLink := SlackLinkFormatter(p.Repo.HTMLURL, p.Repo.Name)
8888
refLink := SlackLinkFormatter(p.Repo.HTMLURL+"/src/"+refName, refName)
89-
text := fmt.Sprintf("[%s:%s] %s created by %s", repoLink, refLink, p.RefType, p.Sender.UserName)
89+
90+
format := "[%s:%s] %s created by %s"
91+
text := fmt.Sprintf(format, repoLink, refLink, p.RefType, p.Sender.UserName)
9092

9193
return &SlackPayload{
9294
Channel: slack.Channel,
@@ -117,7 +119,9 @@ func getSlackPushPayload(p *api.PushPayload, slack *SlackMeta) (*SlackPayload, e
117119

118120
repoLink := SlackLinkFormatter(p.Repo.HTMLURL, p.Repo.Name)
119121
branchLink := SlackLinkFormatter(p.Repo.HTMLURL+"/src/"+branchName, branchName)
120-
text := fmt.Sprintf("[%s:%s] %s pushed by %s", repoLink, branchLink, commitString, p.Pusher.UserName)
122+
123+
format := "[%s:%s] %s pushed by %s"
124+
text := fmt.Sprintf(format, repoLink, branchLink, commitString, p.Pusher.UserName)
121125

122126
var attachmentText string
123127
// for each commit, generate attachment text

modules/auth/repo_form.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,20 @@ func (f *NewSlackHookForm) Validate(ctx *macaron.Context, errs binding.Errors) b
183183
return validate(errs, ctx.Data, f, ctx.Locale)
184184
}
185185

186+
// NewDiscordHookForm form for creating slack hook
187+
type NewDiscordHookForm struct {
188+
PayloadURL string `binding:"Required;ValidUrl"`
189+
Username string
190+
IconURL string
191+
Color int
192+
WebhookForm
193+
}
194+
195+
// Validate validates the fields
196+
func (f *NewDiscordHookForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
197+
return validate(errs, ctx.Data, f, ctx.Locale)
198+
}
199+
186200
// .___
187201
// | | ______ ________ __ ____
188202
// | |/ ___// ___/ | \_/ __ \

modules/setting/setting.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1367,7 +1367,7 @@ func newWebhookService() {
13671367
Webhook.QueueLength = sec.Key("QUEUE_LENGTH").MustInt(1000)
13681368
Webhook.DeliverTimeout = sec.Key("DELIVER_TIMEOUT").MustInt(5)
13691369
Webhook.SkipTLSVerify = sec.Key("SKIP_TLS_VERIFY").MustBool()
1370-
Webhook.Types = []string{"gitea", "gogs", "slack"}
1370+
Webhook.Types = []string{"gitea", "gogs", "slack", "discord"}
13711371
Webhook.PagingNum = sec.Key("PAGING_NUM").MustInt(10)
13721372
}
13731373

options/locale/locale_en-US.ini

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -902,6 +902,7 @@ settings.add_slack_hook_desc = Add <a href="%s">Slack</a> integration to your re
902902
settings.slack_token = Token
903903
settings.slack_domain = Domain
904904
settings.slack_channel = Channel
905+
settings.add_discord_hook_desc = Add <a href="%s">Discord</a> integration to your repository.
905906
settings.deploy_keys = Deploy Keys
906907
settings.add_deploy_key = Add Deploy Key
907908
settings.deploy_key_desc = Deploy keys have read-only access. They are not the same as personal account SSH keys.

public/img/discord.png

1.52 KB
Loading

0 commit comments

Comments
 (0)