forked from BeepBoopHQ/go-slackbot
-
Notifications
You must be signed in to change notification settings - Fork 8
/
bot.go
187 lines (160 loc) · 5.37 KB
/
bot.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
// Package slackbot hopes to ease development of Slack bots by adding helpful
// methods and a mux-router style interface to the github.com/essentialkaos/slack package.
//
// Incoming Slack RTM events are mapped to a handler in the following form:
// bot.Hear("(?i)how are you(.*)").MessageHandler(HowAreYouHandler)
//
// The package adds Reply and ReplyWithAttachments methods:
// func HowAreYouHandler(ctx context.Context, bot *slackbot.Bot, evt *slack.MessageEvent) {
// bot.Reply(evt, "A bit tired. You get it? A bit?", slackbot.WithTyping)
// }
//
// func HowAreYouAttachmentsHandler(ctx context.Context, bot *slackbot.Bot, evt *slack.MessageEvent) {
// txt := "Beep Beep Boop is a ridiculously simple hosting platform for your Slackbots."
// attachment := slack.Attachment{
// Pretext: "We bring bots to life. :sunglasses: :thumbsup:",
// Title: "Host, deploy and share your bot in seconds.",
// TitleLink: "https://beepboophq.com/",
// Text: txt,
// Fallback: txt,
// ImageURL: "https://storage.googleapis.com/beepboophq/_assets/bot-1.22f6fb.png",
// Color: "#7CD197",
// }
//
// attachments := []slack.Attachment{attachment}
// bot.ReplyWithAttachments(evt, attachments, slackbot.WithTyping)
// }
//
// The slackbot package exposes github.com/essentialkaos/slack RTM and Client objects
// enabling a consumer to interact with the lower level package directly:
// func HowAreYouHandler(ctx context.Context, bot *slackbot.Bot, evt *slack.MessageEvent) {
// bot.RTM.NewOutgoingMessage("Hello", "#random")
// }
//
//
// Project home and samples: https://github.com/BeepBoopHQ/go-slackbot
package slackbot
import (
"fmt"
"time"
"golang.org/x/net/context"
"github.com/essentialkaos/slack"
)
const (
WithTyping bool = true
WithoutTyping bool = false
maxTypingSleepMs time.Duration = time.Millisecond * 2000
)
// New constructs a new Bot using the slackToken to authorize against the Slack service.
func New(slackToken string) *Bot {
b := &Bot{Client: slack.New(slackToken)}
return b
}
type Bot struct {
SimpleRouter
// Routes to be matched, in order.
routes []*Route
// Slack UserID of the bot UserID
botUserID string
// Slack API
Client *slack.Client
RTM *slack.RTM
}
// Run listens for incoming slack RTM events, matching them to an appropriate handler.
func (b *Bot) Run() {
b.RTM = b.Client.NewRTM()
fmt.Printf("%v", b.RTM.GetInfo())
go b.RTM.ManageConnection()
for {
select {
case msg := <-b.RTM.IncomingEvents:
ctx := context.Background()
ctx = AddBotToContext(ctx, b)
switch ev := msg.Data.(type) {
case *slack.ConnectedEvent:
fmt.Printf("Connected: %#v\n", ev.Info.User)
b.setBotID(ev.Info.User.ID)
case *slack.MessageEvent:
// ignore messages from the current user, the bot user
if b.botUserID == ev.User {
continue
}
ctx = AddMessageToContext(ctx, ev)
var match RouteMatch
if matched, ctx := b.Match(ctx, &match); matched {
match.Handler(ctx)
}
case *slack.RTMError:
fmt.Printf("Error: %s\n", ev.Error())
case *slack.InvalidAuthEvent:
fmt.Printf("Invalid credentials")
break
default:
// Ignore other events..
// fmt.Printf("Unexpected: %v\n", msg.Data)
}
}
}
}
// Reply replies to a message event with a simple message.
func (b *Bot) Reply(evt *slack.MessageEvent, msg string, typing bool) {
if typing {
b.Type(evt, msg)
}
b.RTM.SendMessage(b.RTM.NewOutgoingMessage(msg, evt.Channel))
}
// ReplyWithAttachments replys to a message event with a Slack Attachments message.
func (b *Bot) ReplyWithAttachments(evt *slack.MessageEvent, attachments []slack.Attachment, typing bool) {
params := slack.PostMessageParameters{AsUser: true}
params.Attachments = attachments
b.Client.PostMessage(evt.Msg.Channel, "", params)
}
// ReplyAsThread
func (b *Bot) ReplyInThread(evt *slack.MessageEvent, msg string, typing bool) {
params := slack.PostMessageParameters{AsUser: true}
if evt.ThreadTimestamp == "" {
params.ThreadTimestamp = evt.Timestamp
} else {
params.ThreadTimestamp = evt.ThreadTimestamp
}
b.Client.PostMessage(evt.Msg.Channel, msg, params)
}
// ReplyInThreadWithAttachments replys to a message event inside a thread with a Slack Attachments message.
func (b *Bot) ReplyInThreadWithAttachments(evt *slack.MessageEvent, attachments []slack.Attachment, typing bool) {
params := slack.PostMessageParameters{AsUser: true}
params.Attachments = attachments
if evt.ThreadTimestamp == "" {
params.ThreadTimestamp = evt.Timestamp
} else {
params.ThreadTimestamp = evt.ThreadTimestamp
}
b.Client.PostMessage(evt.Msg.Channel, "", params)
}
// Type sends a typing message and simulates delay (max 2000ms) based on message size.
func (b *Bot) Type(evt *slack.MessageEvent, msg interface{}) {
msgLen := msgLen(msg)
sleepDuration := time.Minute * time.Duration(msgLen) / 3000
if sleepDuration > maxTypingSleepMs {
sleepDuration = maxTypingSleepMs
}
b.RTM.SendMessage(b.RTM.NewTypingMessage(evt.Channel))
time.Sleep(sleepDuration)
}
// Fetch the botUserID.
func (b *Bot) BotUserID() string {
return b.botUserID
}
func (b *Bot) setBotID(ID string) {
b.botUserID = ID
b.SimpleRouter.SetBotID(ID)
}
// msgLen gets lenght of message and attachment messages. Unsupported types return 0.
func msgLen(msg interface{}) (msgLen int) {
switch m := msg.(type) {
case string:
msgLen = len(m)
case []slack.Attachment:
msgLen = len(fmt.Sprintf("%#v", m))
}
return
}