/
slack.go
153 lines (125 loc) · 3.29 KB
/
slack.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
package slack
import (
"errors"
"github.com/botopolis/bot"
"github.com/nlopes/slack"
)
type slackLogger struct{ bot.Logger }
func (l slackLogger) Output(i int, s string) error {
l.Debug("slack:", s)
return nil
}
// Adapter is the bot slack adapter it implements
// bot.Plugin and bot.Chat interfaces
type Adapter struct {
proxy interface {
Connect() chan bot.Message
Disconnect()
Send(bot.Message) error
React(bot.Message) error
SetTopic(room, topic string) error
}
Robot *bot.Robot
Client *slack.Client
Store Store
BotID string
Name string
}
// New called with one's slack token provides a new adapter
func New(secret string) *Adapter {
client := slack.New(secret)
a := &Adapter{
Client: client,
Store: newMemoryStore(client),
}
a.proxy = newProxy(a)
return a
}
// Load provides the slack adapter access to the Robot's logger
func (a *Adapter) Load(r *bot.Robot) {
slack.SetLogger(&slackLogger{r.Logger})
a.Client.SetDebug(true)
a.Robot = r
}
// Unload disconnects from slack's RTM socket
func (a *Adapter) Unload(r *bot.Robot) { a.proxy.Disconnect() }
// Username returns the bot's username
func (a *Adapter) Username() string { return a.Name }
// Messages connects to Slack's RTM API and channels messages through
func (a *Adapter) Messages() <-chan bot.Message { return a.proxy.Connect() }
func emptyMessage(m bot.Message) bool {
return m.Text == "" && m.Params == nil
}
// Send send messages to Slack. If only text is provided, it uses
// the already open RTM connection. If slack.PostMessageParamters
// are provided in the message.Params field, it will send a web
// API request.
func (a *Adapter) Send(m bot.Message) error {
if emptyMessage(m) {
return nil
}
if err := a.parse(&m, parseRoom, parseParams); err != nil {
return err
}
return a.proxy.Send(m)
}
// Direct does the same thing as send, but also ensures the message
// is sent directly to the user
func (a *Adapter) Direct(m bot.Message) error {
if emptyMessage(m) {
return nil
}
if err := a.parse(
&m,
parseRoom,
parseUser,
parseDM,
parseParams,
); err != nil {
return err
}
return a.proxy.Send(m)
}
// Reply does the same thing as send, but prefixes the message
// with <@userID>, notifying the user of the message.
func (a *Adapter) Reply(m bot.Message) error {
if emptyMessage(m) {
return nil
}
if err := a.parse(
&m,
parseRoom,
parseUser,
parseParams,
); err != nil {
return err
}
if m.Room == "" {
return errors.New("No room provided")
}
// No need to @ the user if it's a DM
if m.Room[0] != 'D' {
m.Text = "<@" + m.User + "> " + m.Text
}
return a.proxy.Send(m)
}
// Topic uses the web API to change the topic. It prefers
// the message.Room and falls back to message.Extra.Channel
// to determine what channel's topic should be updated.
func (a *Adapter) Topic(m bot.Message) error {
if err := parseRoom(a, &m); err != nil {
return err
}
if m.Room == "" {
return errors.New("No Channel provided")
}
return a.proxy.SetTopic(m.Room, m.Topic)
}
// React adds an emote to the last message sent (requires an Envelope to be set).
// It relies on the timestamp and channel for a message to be present
func (a *Adapter) React(m bot.Message) error {
if _, ok := m.Envelope.(slack.Message); !ok {
return errors.New("Empty envelope provided")
}
return a.proxy.React(m)
}