This repository has been archived by the owner on Feb 9, 2019. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathrouter.go
336 lines (305 loc) · 9.4 KB
/
router.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
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
package dhelpers
import (
"regexp"
"sort"
"strings"
"github.com/BurntSushi/toml"
"github.com/bwmarrin/discordgo"
)
// Routing JSON Config
type rawRoutingEntryContainer struct {
Module []rawRoutingEntry
}
type rawRoutingEntry struct {
Active bool
Always bool // if true: will run even if there have been previous (higher priority) matches
AllowBots bool // if set to true, will trigger for messages by bots
AllowMyself bool // if set to true, will trigger for messages by this bot itself
AllowDM bool
Events []EventType
Module string
Destination string
Requirement []rawRoutingRequirementEntry // will only get matched with EventTypeMessageCreate, EventTypeMessageUpdate, or EventTypeMessageDelete, will match everything if slice is empty
Priority int // higher runs before lower
ErrorHandlers []string
}
type rawRoutingRequirementEntry struct {
Beginning []string // can be empty, will match all
Regex string // can be empty, will match all
DoNotPrependPrefix bool // if false, prepends guild prefix to regex
CaseSensitive bool // prepends (?i) to regex on go, language dependent#
Alias string
}
// RoutingRule is a a compiled routing rule used for matching
type RoutingRule struct {
Event EventType
ErrorHandlers []ErrorHandlerType
Module string
DestinationMain string
DestinationSub string
Beginning string
Alias string
Regex *regexp.Regexp
DoNotPrependPrefix bool
CaseSensitive bool
Always bool
AllowBots bool
AllowMyself bool
AllowDM bool
}
// GetRoutings returns a sorted slice (by priority) with all rules
func GetRoutings() (routingRules []RoutingRule, err error) {
// read and unmarshal config from file
// TODO: load from S3 instead
var rawRoutingContainer rawRoutingEntryContainer
_, err = toml.DecodeFile("routing.toml", &rawRoutingContainer)
if err != nil {
return nil, err
}
// group rules by priorities
rawEntriesByPriority := make(map[int][]rawRoutingEntry)
for _, rawRoutingEntry := range rawRoutingContainer.Module {
rawEntriesByPriority[rawRoutingEntry.Priority] = append(
rawEntriesByPriority[rawRoutingEntry.Priority], rawRoutingEntry,
)
}
// sort entries
var keys []int
for k := range rawEntriesByPriority {
keys = append(keys, k)
}
sort.Ints(keys)
for i, j := 0, len(keys)-1; i < j; i, j = i+1, j-1 {
keys[i], keys[j] = keys[j], keys[i]
}
// generated compiled routings
for _, k := range keys {
for _, rawRule := range rawEntriesByPriority[k] {
// skip disabled rules
if !rawRule.Active {
continue
}
// skip empty event slices
if rawRule.Events == nil || len(rawRule.Events) < 0 {
continue
}
// skip empty or invalid destinations
if rawRule.Destination == "" || !strings.Contains(rawRule.Destination, "/") {
continue
}
// generate route for each type
for _, ruleType := range rawRule.Events {
parts := strings.SplitN(rawRule.Destination, "/", 2)
newEntry := RoutingRule{
Event: ruleType,
Module: rawRule.Module,
DestinationMain: replaceDestinations(parts[0]),
DestinationSub: replaceDestinations(parts[1]),
Always: rawRule.Always,
AllowMyself: rawRule.AllowMyself,
AllowBots: rawRule.AllowBots,
AllowDM: rawRule.AllowDM,
Beginning: "",
Regex: nil,
DoNotPrependPrefix: false,
CaseSensitive: false,
}
for _, errorHandler := range rawRule.ErrorHandlers {
switch errorHandler {
case string(SentryErrorHandler):
newEntry.ErrorHandlers = append(newEntry.ErrorHandlers, SentryErrorHandler)
case string(DiscordErrorHandler):
newEntry.ErrorHandlers = append(newEntry.ErrorHandlers, DiscordErrorHandler)
}
}
if (ruleType == MessageCreateEventType ||
ruleType == MessageUpdateEventType ||
ruleType == MessageDeleteEventType) &&
rawRule.Requirement != nil && len(rawRule.Requirement) > 0 {
for _, requirement := range rawRule.Requirement {
if requirement.Beginning != nil && len(requirement.Beginning) > 0 {
for _, beginning := range requirement.Beginning {
newEntryCopy := newEntry
newEntryCopy.Beginning = beginning
if requirement.Regex != "" {
if requirement.CaseSensitive {
newEntryCopy.Regex = regexp.MustCompile(requirement.Regex)
} else {
newEntryCopy.Regex = regexp.MustCompile("(?i)" + requirement.Regex)
}
}
newEntryCopy.DoNotPrependPrefix = requirement.DoNotPrependPrefix
newEntryCopy.CaseSensitive = requirement.CaseSensitive
newEntryCopy.Alias = requirement.Alias
routingRules = append(routingRules, newEntryCopy)
}
} else {
newEntryCopy := newEntry
if requirement.Regex != "" {
if requirement.CaseSensitive {
newEntryCopy.Regex = regexp.MustCompile(requirement.Regex)
} else {
newEntryCopy.Regex = regexp.MustCompile("(?i)" + requirement.Regex)
}
}
newEntryCopy.DoNotPrependPrefix = requirement.DoNotPrependPrefix
newEntryCopy.CaseSensitive = requirement.CaseSensitive
newEntryCopy.Alias = requirement.Alias
routingRules = append(routingRules, newEntryCopy)
}
}
} else {
routingRules = append(routingRules, newEntry)
}
}
}
}
return routingRules, nil
}
// RoutingMatchMessage checks if a message content matches the requirements of the routing rule
func RoutingMatchMessage(routingEntry RoutingRule, author, bot *discordgo.User, channel *discordgo.Channel, content string, args []string, prefix string) (match bool) {
// ignore bots?
if !routingEntry.AllowBots {
if author.Bot {
return false
}
}
// ignore itself?
if !routingEntry.AllowMyself {
if author.ID == bot.ID {
return false
}
}
// DMs?
if !routingEntry.AllowDM {
if channel.Type == discordgo.ChannelTypeDM {
return false
}
}
if routingEntry.Beginning != "" || routingEntry.Regex != nil {
// check prefix if should check
if !routingEntry.DoNotPrependPrefix {
if prefix == "" {
return false
}
}
// match beginning if beginning is set
if routingEntry.Beginning != "" {
if routingEntry.CaseSensitive {
if args[0] != routingEntry.Beginning {
return false
}
} else {
if strings.ToLower(args[0]) != strings.ToLower(routingEntry.Beginning) {
return false
}
}
}
// match regex if regex is set
if routingEntry.Regex != nil {
matchContent := content
if !routingEntry.DoNotPrependPrefix {
matchContent = strings.TrimSpace(strings.TrimLeft(content, prefix))
}
if !routingEntry.Regex.MatchString(matchContent) {
return false
}
}
}
return true
}
// GetMessageArguments trims the prefix and returns all arguments, including the command, and the prefix used
func GetMessageArguments(content string, prefixes []string) (args []string, prefix string) {
for _, possiblePrefix := range prefixes {
if strings.HasPrefix(content, possiblePrefix) {
content = strings.TrimLeft(content, possiblePrefix)
prefix = possiblePrefix
break
}
}
args, err := ToArgv(content)
if err == nil {
return args, prefix
}
return []string{content}, prefix
}
// ContainerDestinations figures out the correct destinations for an event container
func ContainerDestinations(session *discordgo.Session, routingConfig []RoutingRule, container EventContainer) (destinations []DestinationData) {
var handled int
for _, routingEntry := range routingConfig {
if handled > 0 && !routingEntry.Always {
continue
}
if container.Type != routingEntry.Event {
continue
}
// check requirements
if container.Type == MessageCreateEventType {
channel, err := session.State.Channel(container.MessageCreate.ChannelID)
if err != nil {
continue
}
if !RoutingMatchMessage(
routingEntry,
container.MessageCreate.Author,
session.State.User,
channel,
container.MessageCreate.Content,
container.Args,
container.Prefix,
) {
continue
}
}
if container.Type == MessageUpdateEventType {
channel, err := session.State.Channel(container.MessageUpdate.ChannelID)
if err != nil {
continue
}
if !RoutingMatchMessage(
routingEntry,
container.MessageUpdate.Author,
session.State.User,
channel,
container.MessageUpdate.Content,
container.Args,
container.Prefix,
) {
continue
}
}
if container.Type == MessageDeleteEventType {
channel, err := session.State.Channel(container.MessageDelete.ChannelID)
if err != nil {
continue
}
if !RoutingMatchMessage(
routingEntry,
container.MessageDelete.Author,
session.State.User,
channel,
container.MessageDelete.Content,
container.Args,
container.Prefix,
) {
continue
}
}
handled++
switch routingEntry.DestinationMain {
case "kafka":
destinations = append(destinations, DestinationData{
Type: KafkaDestinationType,
Name: routingEntry.DestinationSub,
ErrorHandlers: routingEntry.ErrorHandlers,
Alias: routingEntry.Alias,
})
}
}
return
}
// replaceDestinations reples various placeholders
// currently supported {ENV} => current environment
func replaceDestinations(input string) string {
return strings.Replace(input, "{ENV}", string(GetEnvironment()), -1)
}