package telegohandler import ( "context" "errors" "fmt" "sync" "github.com/mymmrac/telego" ) // Handler handles update that came from bot type Handler func(ctx *Context, update telego.Update) error // Predicate allows filtering updates for handlers // Note: Predicate can't change the update, because it uses a copy, not original value type Predicate func(ctx context.Context, update telego.Update) bool // ErrorHandler handles error that came from bot handing update type ErrorHandler func(ctx *Context, update telego.Update, err error) // BotHandler represents a bot handler that can handle updated matching by predicates type BotHandler struct { bot *telego.Bot updates <-chan telego.Update baseGroup *HandlerGroup errorHandler ErrorHandler running bool lock sync.RWMutex stop chan struct{} handlers sync.WaitGroup } // BotHandlerOption represents an option that can be applied to bot handler type BotHandlerOption func(bh *BotHandler) error // NewBotHandler creates new bot handler // Note: Currently no options available, they may be added in future func NewBotHandler(bot *telego.Bot, updates <-chan telego.Update, options ...BotHandlerOption) (*BotHandler, error) { bh := &BotHandler{ bot: bot, updates: updates, baseGroup: &HandlerGroup{}, } for _, option := range options { if err := option(bh); err != nil { return nil, fmt.Errorf("telego: bot handler options: %w", err) } } return bh, nil } // Start starts handling of updates, blocks execution, caller is responsible for handling all unhandled updates in the // update channel after bot handler stop (start will return an error in this case) // Note: Calling if already running will return an error func (h *BotHandler) Start() error { h.lock.Lock() if h.running { h.lock.Unlock() return errors.New("telego: bot handler already running") } h.running = true h.stop = make(chan struct{}) // Prevents calling Wait before single Add call h.handlers.Add(1) defer h.handlers.Done() h.lock.Unlock() depth := h.baseGroup.depth(1) for { select { case <-h.stop: if unhandled := len(h.updates); unhandled > 0 { return fmt.Errorf("telego: bot handler stopped, %d update(s) left unhandled", unhandled) } return nil case update, ok := <-h.updates: if !ok { return nil } // Process update h.handlers.Go(func() { ctx, cancel := context.WithCancel(context.Background()) defer cancel() go func() { select { case <-ctx.Done(): // Done processing case <-h.stop: cancel() } }() bCtx := &Context{ ctx: ctx, ctxBase: &ctxBase{ bot: h.bot, updateID: update.UpdateID, group: h.baseGroup, finalGroup: nil, // Not set stack: append(make([]int, 0, depth), -1), }, } if err := bCtx.Next(update); err != nil { if h.errorHandler != nil { h.errorHandler(bCtx, update, err) } else { h.bot.Logger().Errorf("Error processing update %d, err: %s", update.UpdateID, err) } } }) } } } // IsRunning tells if Start is running func (h *BotHandler) IsRunning() bool { h.lock.RLock() defer h.lock.RUnlock() return h.running } // StopWithContext stops handling of updates, blocks until all updates have been processes (only if update was received // from the update channel) or when context is canceled, if not all updates were received from the update channel // [BotHandler.Start] will return an error, if context is canceled context error will be returned // Note: Calling [BotHandler.StopWithContext] method multiple times or before [BotHandler.Start] does nothing func (h *BotHandler) StopWithContext(ctx context.Context) error { h.lock.Lock() defer h.lock.Unlock() if !h.running { return nil } close(h.stop) h.running = false select { case <-ctx.Done(): return ctx.Err() default: // Continue } wait := make(chan struct{}) go func() { h.handlers.Wait() close(wait) }() select { case <-ctx.Done(): // Wait for context to be done return ctx.Err() case <-wait: // Wait for handlers to complete return nil } } // Stop stops handling of updates, will block until all updates have been processes. // It's recommended to use [BotHandler.StopWithContext] if you want to force stop after some timeout. func (h *BotHandler) Stop() error { return h.StopWithContext(context.Background()) } // Handle registers new handler in the base group, update will be processed only by first-matched route, // order of registration determines the order of matching routes. // Important to notice handler's context will be automatically canceled once the handler will finish processing or // the bot handler stopped. // Note: All handlers will process updates in parallel, there is no guaranty on order of processed updates, also keep // in mind that middlewares and predicates are run sequentially. // // Warning: Panics if nil handler or predicates passed func (h *BotHandler) Handle(handler Handler, predicates ...Predicate) { h.baseGroup.Handle(handler, predicates...) } // Group creates a new group of handlers and middlewares from the base group, update will be processed only by // first-matched route, order of registration determines the order of matching routes // // Warning: Panics if nil predicates passed func (h *BotHandler) Group(predicates ...Predicate) *HandlerGroup { return h.baseGroup.Group(predicates...) } // Use applies middleware to the base group, update will be processed only by first-matched route, // order of registration determines the order of matching routes. // Note: The chain will be stopped if middleware doesn't call the [Context.Next] // // Warning: Panics if nil middlewares passed func (h *BotHandler) Use(middlewares ...Handler) { h.baseGroup.Use(middlewares...) } // BaseGroup returns a base group used by default in [BotHandler] methods func (h *BotHandler) BaseGroup() *HandlerGroup { return h.baseGroup }