/
discord.go
148 lines (121 loc) · 3.53 KB
/
discord.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
package discord
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"net/url"
"github.com/containrrr/shoutrrr/pkg/format"
"github.com/containrrr/shoutrrr/pkg/services/standard"
"github.com/containrrr/shoutrrr/pkg/types"
"github.com/containrrr/shoutrrr/pkg/util"
)
// Service providing Discord as a notification service
type Service struct {
standard.Standard
config *Config
pkr format.PropKeyResolver
}
var limits = types.MessageLimit{
ChunkSize: 2000,
TotalChunkSize: 6000,
ChunkCount: 10,
}
const (
hookURL = "https://discord.com/api/webhooks"
// Only search this many runes for a good split position
maxSearchRunes = 100
)
// Send a notification message to discord
func (service *Service) Send(message string, params *types.Params) error {
var firstErr error
if service.config.JSON {
postURL := CreateAPIURLFromConfig(service.config)
firstErr = doSend([]byte(message), postURL)
} else {
batches := CreateItemsFromPlain(message, service.config.SplitLines)
for _, items := range batches {
if err := service.sendItems(items, params); err != nil {
service.Log(err)
if firstErr == nil {
firstErr = err
}
}
}
}
if firstErr != nil {
return fmt.Errorf("failed to send discord notification: %v", firstErr)
}
return nil
}
// SendItems sends items with additional meta data and richer appearance
func (service *Service) SendItems(items []types.MessageItem, params *types.Params) error {
return service.sendItems(items, params)
}
func (service *Service) sendItems(items []types.MessageItem, params *types.Params) error {
var err error
config := *service.config
if err = service.pkr.UpdateConfigFromParams(&config, params); err != nil {
return err
}
var payload WebhookPayload
payload, err = CreatePayloadFromItems(items, config.Title, config.LevelColors())
if err != nil {
return err
}
payload.Username = config.Username
payload.AvatarURL = config.Avatar
var payloadBytes []byte
payloadBytes, err = json.Marshal(payload)
if err != nil {
return err
}
postURL := CreateAPIURLFromConfig(&config)
return doSend(payloadBytes, postURL)
}
// CreateItemsFromPlain creates a set of MessageItems that is compatible with Discords webhook payload
func CreateItemsFromPlain(plain string, splitLines bool) (batches [][]types.MessageItem) {
if splitLines {
return util.MessageItemsFromLines(plain, limits)
}
for {
items, omitted := util.PartitionMessage(plain, limits, maxSearchRunes)
batches = append(batches, items)
if omitted == 0 {
break
}
plain = plain[len(plain)-omitted:]
}
return
}
// Initialize loads ServiceConfig from configURL and sets logger for this Service
func (service *Service) Initialize(configURL *url.URL, logger types.StdLogger) error {
service.Logger.SetLogger(logger)
service.config = &Config{}
service.pkr = format.NewPropKeyResolver(service.config)
if err := service.pkr.SetDefaultProps(service.config); err != nil {
return err
}
if err := service.config.SetURL(configURL); err != nil {
return err
}
return nil
}
// CreateAPIURLFromConfig takes a discord config object and creates a post url
func CreateAPIURLFromConfig(config *Config) string {
return fmt.Sprintf(
"%s/%s/%s",
hookURL,
config.WebhookID,
config.Token)
}
func doSend(payload []byte, postURL string) error {
res, err := http.Post(postURL, "application/json", bytes.NewBuffer(payload))
if res == nil && err == nil {
err = fmt.Errorf("unknown error")
}
if err == nil && res.StatusCode != http.StatusNoContent {
err = fmt.Errorf("response status code %s", res.Status)
}
return err
}