@@ -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.
224252func (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
234268func (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}
0 commit comments