package bot import ( "context" "log/slog" "time" "botpontos/internal/adapters/telegram" ) // Runner faz o long polling do Telegram e responde cada mensagem. type Runner struct { client *telegram.Client router *Router log *slog.Logger pollTimeout int } func NewRunner(client *telegram.Client, router *Router, log *slog.Logger, pollTimeout int) *Runner { return &Runner{client: client, router: router, log: log, pollTimeout: pollTimeout} } // Run roda até o contexto ser cancelado. Erros de rede não derrubam o // worker: o loop espera alguns segundos e tenta de novo. func (r *Runner) Run(ctx context.Context) { var offset int64 backoff := time.Second r.log.Info("bot do Telegram iniciado") for ctx.Err() == nil { updates, err := r.client.GetUpdates(ctx, offset, r.pollTimeout) if err != nil { if ctx.Err() != nil { return } r.log.Warn("falha no getUpdates do Telegram", "error", err, "retry_in", backoff.String()) select { case <-ctx.Done(): return case <-time.After(backoff): } if backoff < 30*time.Second { backoff *= 2 } continue } backoff = time.Second for _, update := range updates { offset = update.UpdateID + 1 msg := update.Message // Só conversas privadas: o bot trata dados pessoais de jornada. if msg == nil || msg.Text == "" || (msg.Chat.Type != "" && msg.Chat.Type != "private") { continue } r.handle(ctx, msg) } } } func (r *Runner) handle(ctx context.Context, msg *telegram.Message) { defer func() { if v := recover(); v != nil { r.log.Error("panic ao tratar mensagem do Telegram", "panic", v) } }() handleCtx, cancel := context.WithTimeout(ctx, 45*time.Second) defer cancel() reply := r.router.Handle(handleCtx, msg.Chat.ID, msg.From.Name(), msg.Text) if reply == "" { return } if _, err := r.client.Send(ctx, msg.Chat.ID, reply); err != nil { r.log.Warn("falha ao responder no Telegram", "error", err) } }