forked from abourget/slick
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlistener.go
260 lines (213 loc) · 7.05 KB
/
listener.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
package slick
import (
"fmt"
"log"
"regexp"
"time"
"github.com/nlopes/slack"
)
type Listener struct {
// replyAck is filled when you call Listen() on a Reply.
replyAck *slack.AckMessage
// ListenUntil sets an absolute date at which this Listener
// expires and stops listening. ListenUntil and ListenDuration
// are optional and mutually exclusive.
ListenUntil time.Time
// ListenDuration sets a timeout Duration, after which this
// Listener stops listening and is garbage collected. A call
// to `ResetTimeout()` restarts the listening period for another
// `ListenDuration`.
ListenDuration time.Duration
// FromUser filters out incoming messages that are not with
// `*User` (publicly or privately)
FromUser *slack.User
// FromChannel filters messages that are sent to a different room than
// `Room`. This can be mixed and matched with `FromUser`
FromChannel *Channel
// PrivateOnly filters out public messages.
PrivateOnly bool
// PublicOnly filters out private messages. Mutually exclusive
// with `PrivateOnly`.
PublicOnly bool
// Contains checks whether the `string` is in the message body
// (after lower-casing both components).
Contains string
// ContainsAny checks that any one of the specified strings exist
// as substrings in the message body. Mutually exclusive with
// `Contains`.
ContainsAny []string
// Matches checks that the given text matches the given Regexp
// with a `FindStringSubmatch` call. It will set the `Message.Match`
// attribute.
//
// NOTE: if you spin off a goroutine in the MessageHandlerFunc,
// make sure to keep a copy of the `Message.Match` object because it
// will be overwritten by the next Listener the moment your
// MessageHandlerFunc unblocks.
Matches *regexp.Regexp
// ListenForEdits will trigger a message when a user edits a
// message as well as creates a new one.
ListenForEdits bool
// MentionsMe filters out messages that do not mention the Bot's
// `bot.Config.MentionName`
MentionsMeOnly bool
// MatchMyMessages equal to false filters out messages that the bot
// himself sent.
MatchMyMessages bool
// MessageHandlerFunc is a handling function provided by the user, and
// called when a relevant message comes in.
MessageHandlerFunc func(*Listener, *Message)
// EventHandlerFunc is a handling function provided by the user, and
// called when any event is received. These messages are dispatched
// to each Listener in turn, after the bot has processed it.
// If the event is a Message, then the `slick.Message` will be non-nil.
//
// When receiving a `*slack.MessageEvent`, slick will wrap it in a `*slick.Message`
// which embeds the the original event, but adds quite a few functionalities, like
// reply modes, etc..
EventHandlerFunc func(*Listener, interface{})
// TimeoutFunc is called when a conversation expires after
// `ListenDuration` or `ListenUntil` delays. It is *not* called
// if you explicitly call `Close()` on the conversation, or if
// you did not set `ListenDuration` nor `ListenUntil`.
//
// Also, if you override TimeoutFunc, you need to call Close() yourself
// otherwise, the conversation is not removed from the listeners
TimeoutFunc func(*Listener)
// Bot is a reference to the bot instance. It will always be populated before being
// passed to handler functions.
Bot *Bot
resetCh chan bool
doneCh chan bool
}
// Close terminates the Listener management goroutine, and stops
// any further listening and message handling
func (listen *Listener) Close() {
listen.Bot.delListenerCh <- listen
listen.doneCh <- true
}
// ReplyAck returns the AckMessage received that corresponds to the Reply
// on which you called Listen()
func (listen *Listener) ReplyAck() *slack.AckMessage {
return listen.replyAck
}
// ResetDuration re-initializes the timeout set by
// `Listener.ListenDuration`, and continues listening for another
// such duration.
func (listen *Listener) ResetDuration() error {
if int64(listen.ListenDuration) == 0 {
msg := "Listener has no ListenDuration"
log.Println("ResetDuration() error: ", msg)
return fmt.Errorf(msg)
}
listen.resetCh <- true
return nil
}
func (listen *Listener) isManaged() bool {
timeout := listen.timeoutDuration()
return int64(timeout) != 0
}
func (listen *Listener) launchManager() {
for {
timeout := listen.timeoutDuration()
select {
case <-time.After(timeout):
if listen.TimeoutFunc != nil {
listen.TimeoutFunc(listen)
}
return
case <-listen.resetCh:
continue
case <-listen.doneCh:
return
}
}
}
func (listen *Listener) timeoutDuration() (timeout time.Duration) {
if !listen.ListenUntil.IsZero() {
now := time.Now()
timeout = listen.ListenUntil.Sub(now)
if int64(timeout) < 0 {
timeout = 1 * time.Millisecond
}
} else if int64(listen.ListenDuration) != 0 {
timeout = listen.ListenDuration
}
return
}
func (listen *Listener) checkParams() error {
if !listen.ListenUntil.IsZero() && int64(listen.ListenDuration) != 0 {
return fmt.Errorf("specify `ListenUntil` *or* `ListenDuration`, not both.")
}
if listen.PrivateOnly && listen.PublicOnly {
return fmt.Errorf("`PrivateOnly` and `PublicOnly` are mutually exclusive.")
}
if listen.Contains != "" && len(listen.ContainsAny) > 0 {
return fmt.Errorf("`Contains` and `ContainsAny` are mutually exclusive.")
}
if (listen.MessageHandlerFunc == nil && listen.EventHandlerFunc == nil) || (listen.MessageHandlerFunc != nil && listen.EventHandlerFunc != nil) {
return fmt.Errorf("One and only one of `MessageHandlerFunc` and `EventHandlerFunc` is required.")
}
return nil
}
func (listen *Listener) setupChannels() {
listen.resetCh = make(chan bool, 10)
listen.doneCh = make(chan bool, 10)
}
func (listen *Listener) filterAndDispatchMessage(msg *Message) {
if listen.filterMessage(msg) {
listen.MessageHandlerFunc(listen, msg)
}
}
// filterMessage applies checks from a Listener against a Message.
func (listen *Listener) filterMessage(msg *Message) bool {
if msg.Msg.SubType == "message_deleted" {
// like "message_deleted"
return false
}
if msg.Msg.SubType == "message_changed" && !listen.ListenForEdits {
return false
}
// Never pick up on other bot's messages
if msg.Msg.SubType == "bot_message" {
return false
}
if listen.MentionsMeOnly && !msg.MentionsMe {
return false
}
if listen.PrivateOnly && !msg.IsPrivate() {
return false
}
if listen.PublicOnly && msg.IsPrivate() {
return false
}
if listen.Contains != "" && !msg.Contains(listen.Contains) {
return false
}
if len(listen.ContainsAny) > 0 && !msg.ContainsAny(listen.ContainsAny) {
return false
}
if listen.Matches != nil {
match := listen.Matches.FindStringSubmatch(msg.Text)
msg.Match = match
if match == nil {
return false
}
}
// If there is no msg.FromUser, the message is filtered out.
if listen.FromUser != nil && (msg.FromUser == nil || msg.FromUser.ID != listen.FromUser.ID) {
return false
}
if listen.FromChannel != nil {
if msg.FromChannel == nil {
return false
}
if msg.FromChannel.ID != listen.FromChannel.ID {
return false
}
}
if !listen.MatchMyMessages && msg.FromMe {
return false
}
return true
}