forked from brettbuddin/victor
/
robot.go
209 lines (182 loc) · 4.65 KB
/
robot.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
package victor
import (
"fmt"
"log"
"os"
"strings"
"github.com/brettbuddin/victor/pkg/chat"
// Blank import used init adapters which registers them with victor
_ "github.com/brettbuddin/victor/pkg/chat/campfire"
_ "github.com/brettbuddin/victor/pkg/chat/hipchat"
_ "github.com/brettbuddin/victor/pkg/chat/shell"
_ "github.com/brettbuddin/victor/pkg/chat/slack"
_ "github.com/brettbuddin/victor/pkg/chat/slackRealtime"
"github.com/brettbuddin/victor/pkg/httpserver"
"github.com/brettbuddin/victor/pkg/store"
// Blank import used init adapters which registers them with victor
_ "github.com/brettbuddin/victor/pkg/store/boltstore"
_ "github.com/brettbuddin/victor/pkg/store/memory"
"github.com/gorilla/mux"
)
// Robot provides an interface for a victor chat robot.
type Robot interface {
Run()
Stop()
Name() string
HandleFunc(string, HandlerFunc)
Handle(string, Handler)
Direct(string) string
Receive(chat.Message)
Chat() chat.Adapter
Store() store.Adapter
HTTP() *mux.Router
AdapterConfig() (interface{}, bool)
StoreConfig() (interface{}, bool)
}
// Config provides all of the configuration parameters needed in order to
// initialize a robot. It also allows for optional configuration structs for
// both the chat and storage adapters which they may or may not require.
type Config struct {
Name,
ChatAdapter,
StoreAdapter,
HTTPAddr string
AdapterConfig,
StoreConfig interface{}
}
type robot struct {
*dispatch
name string
http *httpserver.Server
httpAddr string
httpRouter *mux.Router
store store.Adapter
chat chat.Adapter
incoming chan chat.Message
stop chan struct{}
adapterConfig,
storeConfig interface{}
}
// New returns a robot
func New(config Config) *robot {
chatAdapter := config.ChatAdapter
if chatAdapter == "" {
chatAdapter = "shell"
}
chatInitFunc, err := chat.Load(config.ChatAdapter)
if err != nil {
log.Println(err)
os.Exit(1)
}
storeAdapter := config.StoreAdapter
if storeAdapter == "" {
storeAdapter = "memory"
}
storeInitFunc, err := store.Load(storeAdapter)
if err != nil {
log.Println(err)
os.Exit(1)
}
botName := config.Name
if botName == "" {
botName = "victor"
}
httpAddr := config.HTTPAddr
if httpAddr == "" {
httpAddr = ":9000"
}
bot := &robot{
name: botName,
httpAddr: httpAddr,
incoming: make(chan chat.Message),
stop: make(chan struct{}),
}
bot.store = storeInitFunc(bot)
bot.adapterConfig = config.AdapterConfig
bot.dispatch = newDispatch(bot)
bot.chat = chatInitFunc(bot)
return bot
}
// Receive accepts messages for processing
func (r *robot) Receive(m chat.Message) {
r.incoming <- m
}
// Run starts the robot.
func (r *robot) Run() {
go r.chat.Run()
go func() {
for {
select {
case <-r.stop:
close(r.incoming)
return
case m := <-r.incoming:
if strings.ToLower(m.UserName()) != r.name {
go r.ProcessMessage(m)
}
}
}
}()
}
// Stop shuts down the bot
func (r *robot) Stop() {
r.chat.Stop()
close(r.stop)
if r.http != nil {
r.http.Stop()
}
}
// Name returns the name of the bot
func (r *robot) Name() string {
return r.name
}
// Store returns the data store adapter
func (r *robot) Store() store.Adapter {
return r.store
}
// HTTP returns the HTTP router.
// The HTTP router is disabled (uninitialized) by default but is created upon
// the first call to HTTP().
//
// TODO consider having one explicitly enable the storage access endpoints
// (defined in http_handlers.go) since, as far as I can tell, a chat/storage
// adapter might want to use the included http router without enabling access
// to the entire store via those endpoints. At the moment these are coupled
// together.
func (r *robot) HTTP() *mux.Router {
if r.httpRouter == nil {
r.initHTTP()
}
return r.httpRouter
}
// Chat returns the chat adapter
func (r *robot) Chat() chat.Adapter {
return r.chat
}
func (r *robot) AdapterConfig() (interface{}, bool) {
return r.adapterConfig, r.adapterConfig != nil
}
func (r *robot) StoreConfig() (interface{}, bool) {
return r.storeConfig, r.storeConfig != nil
}
func (r *robot) initHTTP() {
log.Println("Initializing victor's HTTP server.")
r.http = httpserver.New()
r.httpRouter = handlers(r)
r.http.Handle("/", r.httpRouter)
r.http.ListenAndServe(r.httpAddr)
}
// OnlyAllow provides a way of permitting specific users
// to execute a handler registered with the bot
func OnlyAllow(userNames []string, action func(s State)) func(State) {
return func(s State) {
actual := s.Message().UserName()
for _, name := range userNames {
if name == actual {
action(s)
return
}
}
s.Chat().Send(s.Message().ChannelID(), fmt.Sprintf("Sorry, %s. I can't let you do that.", actual))
}
}