Skip to content

Commit 6125c21

Browse files
committed
add running chan + additional config options for NewBot
1 parent 7ce8500 commit 6125c21

File tree

2 files changed

+118
-7
lines changed

2 files changed

+118
-7
lines changed

Diff for: quadlek/bot.go

+116-4
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,34 @@ type Bot struct {
4646
ctx context.Context
4747
cancel context.CancelFunc
4848
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+
}
4977
}
5078

5179
// GetUserId returns the Slack user ID for the Bot.
@@ -222,18 +250,25 @@ func (b *Bot) handleEvents() {
222250
// Start activates the Bot, creating a new API client.
223251
// It also calls out to the Slack API to obtain all of the channels and users.
224252
func (b *Bot) Start() {
253+
if b.isRunning {
254+
b.Log.Error("bot is already running")
255+
return
256+
}
257+
b.isRunning = true
225258
go b.WebhookServer()
226259
go b.handleEvents()
227260
err := b.initInfo()
228261
if err != nil {
229-
panic(err)
262+
b.Log.Panic("failed to init quadlek", zap.Error(err))
230263
}
264+
close(b.readyChan)
231265
}
232266

233267
// Stop cancel's the bots main context, closes the DB handle, and disconnects from slack
234268
func (b *Bot) Stop() {
235269
b.cancel()
236270
b.wg.Wait()
271+
b.isStopped = true
237272
if b.db != nil {
238273
b.db.Close()
239274
}
@@ -257,23 +292,97 @@ func (b *Bot) Stop() {
257292
// return r, e
258293
// }
259294

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+
260354
// NewBot creates a new instance of Bot for use.
261355
//
262356
// apiKey is the Slack API key that the Bot should use to authenticate
263357
//
264358
// verificationToken is the webhook token that is used to validate webhooks are coming from slack
265359
//
266360
// 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) {
268362
// Seed the RNG with the current time globally
269363
rand.Seed(time.Now().UnixNano())
270364

271365
db, err := bolt.Open(dbPath, 0600, &bolt.Options{Timeout: 1 * time.Second})
272366
if err != nil {
273367
return nil, err
274368
}
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
275384

276-
log, err := zap.NewProduction()
385+
log, err := cfg.Build()
277386
if err != nil {
278387
return nil, err
279388
}
@@ -286,7 +395,7 @@ func NewBot(parentCtx context.Context, apiKey, verificationToken, dbPath string,
286395
cancel: cancel,
287396
apiKey: apiKey,
288397
verificationToken: verificationToken,
289-
api: slack.New(apiKey, slack.OptionDebug(debug)),
398+
api: slack.New(apiKey, slack.OptionDebug(o.debug)),
290399
channels: make(map[string]slack.Channel, 10),
291400
humanChannels: make(map[string]slack.Channel),
292401
humanUsers: make(map[string]slack.User),
@@ -300,5 +409,8 @@ func NewBot(parentCtx context.Context, apiKey, verificationToken, dbPath string,
300409
reactionHooks: []*registeredReactionHook{},
301410
hooks: []*registeredHook{},
302411
db: db,
412+
readyChan: make(chan struct{}),
413+
serverPort: o.serverPort,
414+
listenAddr: o.listenAddr,
303415
}, nil
304416
}

Diff for: quadlek/webhooks.go

+2-3
Original file line numberDiff line numberDiff line change
@@ -207,8 +207,7 @@ func (b *Bot) WebhookServer() {
207207
r.HandleFunc("/slack/interaction", b.handleSlackInteraction).Methods("POST")
208208
r.HandleFunc("/slack/event", b.handleSlackEvent).Methods("POST")
209209

210-
// TODO(jirwin): This listen address should be configurable
211-
srv := &http.Server{Addr: ":8000", Handler: r}
210+
srv := &http.Server{Addr: fmt.Sprintf("%s:%d", b.listenAddr, b.serverPort), Handler: r}
212211

213212
go func() {
214213
if err := srv.ListenAndServe(); err != nil {
@@ -228,7 +227,7 @@ func (b *Bot) WebhookServer() {
228227

229228
var InvalidRequestSignature = errors.New("invalid request signature")
230229

231-
// Validates the signature header for slack webhooks
230+
// ValidateSlackRequest validates the signature header for slack webhooks
232231
func (b *Bot) ValidateSlackRequest(r *http.Request) error {
233232
body, err := io.ReadAll(r.Body)
234233
if err != nil {

0 commit comments

Comments
 (0)