@@ -46,6 +46,34 @@ type Bot struct {
46
46
ctx context.Context
47
47
cancel context.CancelFunc
48
48
wg sync.WaitGroup
49
+
50
+ readyChan chan struct {}
51
+ isRunning bool
52
+ isStopped bool
53
+
54
+ listenAddr string
55
+ serverPort int
56
+ }
57
+
58
+ // Running returns a channel that will close when the bot is running;
59
+ // this channel is not aware of the bot stopping.
60
+ // If you need to know if the bot has been stopped,
61
+ // use IsRunning
62
+ func (b * Bot ) Running () <- chan struct {} {
63
+ return b .readyChan
64
+ }
65
+
66
+ // IsRunning returns whether the bot is running
67
+ func (b * Bot ) IsRunning () bool {
68
+ if b .isStopped {
69
+ return false
70
+ }
71
+ select {
72
+ case <- b .readyChan :
73
+ return true
74
+ default :
75
+ return false
76
+ }
49
77
}
50
78
51
79
// GetUserId returns the Slack user ID for the Bot.
@@ -222,18 +250,25 @@ func (b *Bot) handleEvents() {
222
250
// Start activates the Bot, creating a new API client.
223
251
// It also calls out to the Slack API to obtain all of the channels and users.
224
252
func (b * Bot ) Start () {
253
+ if b .isRunning {
254
+ b .Log .Error ("bot is already running" )
255
+ return
256
+ }
257
+ b .isRunning = true
225
258
go b .WebhookServer ()
226
259
go b .handleEvents ()
227
260
err := b .initInfo ()
228
261
if err != nil {
229
- panic ( err )
262
+ b . Log . Panic ( "failed to init quadlek" , zap . Error ( err ) )
230
263
}
264
+ close (b .readyChan )
231
265
}
232
266
233
267
// Stop cancel's the bots main context, closes the DB handle, and disconnects from slack
234
268
func (b * Bot ) Stop () {
235
269
b .cancel ()
236
270
b .wg .Wait ()
271
+ b .isStopped = true
237
272
if b .db != nil {
238
273
b .db .Close ()
239
274
}
@@ -257,23 +292,97 @@ func (b *Bot) Stop() {
257
292
// return r, e
258
293
// }
259
294
295
+ type options struct {
296
+ logLevel zap.AtomicLevel
297
+ serverPort int
298
+ listenAddr string
299
+ debug bool
300
+ }
301
+
302
+ func newOptions () * options {
303
+ return & options {
304
+ logLevel : zap .NewAtomicLevel (),
305
+ serverPort : 8000 ,
306
+ }
307
+ }
308
+
309
+ type Opt func (* options ) error
310
+
311
+ func WithLogLevel (logLevel zap.AtomicLevel ) Opt {
312
+ return func (o * options ) error {
313
+ o .logLevel = logLevel
314
+ return nil
315
+ }
316
+ }
317
+
318
+ func WithLogLevelFromEnvironment () Opt {
319
+ return func (o * options ) error {
320
+ lvl , err := zap .ParseAtomicLevel (os .Getenv ("LOG_LEVEL" ))
321
+ if err != nil {
322
+ return err
323
+ }
324
+ o .logLevel = lvl
325
+ return nil
326
+ }
327
+ }
328
+
329
+ func WithListenAddr (listenAddr string ) Opt {
330
+ return func (o * options ) error {
331
+ o .listenAddr = listenAddr
332
+ return nil
333
+ }
334
+ }
335
+
336
+ func WithServerPort (serverPort int ) Opt {
337
+ return func (o * options ) error {
338
+ if serverPort <= 0 {
339
+ return fmt .Errorf ("invalid server port %d" , serverPort )
340
+ }
341
+ o .serverPort = serverPort
342
+ return nil
343
+ }
344
+ }
345
+
346
+ // WithDebug sets the logger to use the config provided by zap.NewDevelopmentConfig and sets the Slack API to debug mode
347
+ func WithDebug () Opt {
348
+ return func (o * options ) error {
349
+ o .debug = true
350
+ return nil
351
+ }
352
+ }
353
+
260
354
// NewBot creates a new instance of Bot for use.
261
355
//
262
356
// apiKey is the Slack API key that the Bot should use to authenticate
263
357
//
264
358
// verificationToken is the webhook token that is used to validate webhooks are coming from slack
265
359
//
266
360
// dbPath is the path to the database on the filesystem.
267
- func NewBot (parentCtx context.Context , apiKey , verificationToken , dbPath string , debug bool ) (* Bot , error ) {
361
+ func NewBot (parentCtx context.Context , apiKey , verificationToken , dbPath string , opts ... Opt ) (* Bot , error ) {
268
362
// Seed the RNG with the current time globally
269
363
rand .Seed (time .Now ().UnixNano ())
270
364
271
365
db , err := bolt .Open (dbPath , 0600 , & bolt.Options {Timeout : 1 * time .Second })
272
366
if err != nil {
273
367
return nil , err
274
368
}
369
+ o := newOptions ()
370
+ for _ , opt := range opts {
371
+ if err := opt (o ); err != nil {
372
+ return nil , err
373
+ }
374
+ }
375
+
376
+ var cfg zap.Config
377
+ if o .debug {
378
+ cfg = zap .NewDevelopmentConfig ()
379
+ } else {
380
+ cfg = zap .NewProductionConfig ()
381
+ }
382
+
383
+ cfg .Level = o .logLevel
275
384
276
- log , err := zap . NewProduction ()
385
+ log , err := cfg . Build ()
277
386
if err != nil {
278
387
return nil , err
279
388
}
@@ -286,7 +395,7 @@ func NewBot(parentCtx context.Context, apiKey, verificationToken, dbPath string,
286
395
cancel : cancel ,
287
396
apiKey : apiKey ,
288
397
verificationToken : verificationToken ,
289
- api : slack .New (apiKey , slack .OptionDebug (debug )),
398
+ api : slack .New (apiKey , slack .OptionDebug (o . debug )),
290
399
channels : make (map [string ]slack.Channel , 10 ),
291
400
humanChannels : make (map [string ]slack.Channel ),
292
401
humanUsers : make (map [string ]slack.User ),
@@ -300,5 +409,8 @@ func NewBot(parentCtx context.Context, apiKey, verificationToken, dbPath string,
300
409
reactionHooks : []* registeredReactionHook {},
301
410
hooks : []* registeredHook {},
302
411
db : db ,
412
+ readyChan : make (chan struct {}),
413
+ serverPort : o .serverPort ,
414
+ listenAddr : o .listenAddr ,
303
415
}, nil
304
416
}
0 commit comments