X-Git-Url: https://git.giacomolli.uk/onechat/blobdiff_plain/80e348724041a694002fc38f57e8868b9b9a2be9..bd7da3557dc7a17020931f5a49ed5007fa81a645:/telegram.go?ds=inline diff --git a/telegram.go b/telegram.go index 59ea9b4..85b0f44 100644 --- a/telegram.go +++ b/telegram.go @@ -74,10 +74,10 @@ type TelegramConfig struct { // itself, and as message entities only when Telegram refuses a rich message. // The rest is plain text. Tool calls do not go to the chat. // -// The offset file holds the ID of the next update. It moves past an update -// only after the message of the update is durable in the log, so a restart -// does not lose a message. A crash between the flush and the offset write -// makes Telegram send the update again, and the message repeats once. +// The offset file holds the ID of the next update. It moves past an update only +// after the message of the update is durable in the log, so a restart does not +// lose a message. A crash between the flush and the offset write makes Telegram +// send the update again, and the message repeats once. // // A failed Bot API call waits and tries again, from telegramRetryDelay up to // telegramRetryLimit, or as long as the Bot API asks. It never stops the agent. @@ -192,35 +192,6 @@ func (c TelegramConfig) validate() error { return nil } -// Notify queues event for the chat. It never blocks, so a slow network does not -// stop the agent. The WhatsApp batches and the runs of the schedules do not go -// to the chat: the reply of the agent tells the user what needs the user. -func (b *TelegramBot) Notify(event Event) { - switch event.Kind { - case "busy", "idle", "reply", "error", "notice", "subagent": - case "work": - if event.Source == whatsappSource || event.Source == scheduleSource { - return - } - case "user": - if event.Source == telegramSource { - return - } - default: - return - } - - b.mutex.Lock() - - b.queue = append(b.queue, event) - b.mutex.Unlock() - - select { - case b.ready <- struct{}{}: - default: - } -} - // Run polls for the messages of the owner and sends the queued events to the // chat until ctx ends. It returns an error only when a message or the offset or // image cannot be written, because then the bot would lose or repeat messages. @@ -293,7 +264,7 @@ func (b *TelegramBot) send(ctx context.Context) { func (b *TelegramBot) poll(ctx context.Context, l *TurnLoop, whatsapp *WhatsApp) error { offset, err := telegramLoadOffset(b.offsetPath) if err != nil { - return err + return fmt.Errorf("load Telegram offset: %w", err) } outage := &TelegramOutage{ @@ -342,7 +313,11 @@ poll: continue poll } - return err + if err != nil { + return fmt.Errorf("handle Telegram update: %w", err) + } + + return nil } imageOutage.End() @@ -352,7 +327,7 @@ poll: offset = update.UpdateID + 1 err = telegramSaveOffset(b.offsetPath, offset) if err != nil { - return err + return fmt.Errorf("save Telegram offset: %w", err) } } } @@ -378,7 +353,8 @@ func (b *TelegramBot) next() (Event, bool) { // typing shows the typing action once. A failure is not logged, because the // next message or poll logs the state of the network. func (b *TelegramBot) typing(ctx context.Context) { - _ = b.api.SendTyping(ctx, b.owner) + // The next message or poll reports a network failure. + b.api.SendTyping(ctx, b.owner) // nolint: errcheck } // post sends event to the chat. A reply goes as rich messages of its Markdown, @@ -504,7 +480,7 @@ func (b *TelegramBot) handle(ctx context.Context, l *TurnLoop, whatsapp *WhatsAp default: _, err := l.Submit(telegramSource, message.Text) if err != nil { - return err + return fmt.Errorf("submit Telegram text: %w", err) } err = l.Flush(context.WithoutCancel(ctx)) @@ -595,6 +571,14 @@ func (b *TelegramBot) deliverRich(ctx context.Context, markdown string) { } } +// notice queues a short answer of the bot for the chat. +func (b *TelegramBot) notice(text string) { + b.Notify(Event{ + Kind: "notice", + Text: text, + }) +} + // sendOutage returns a new outage of the messages to the chat. func (b *TelegramBot) sendOutage() *TelegramOutage { return &TelegramOutage{ @@ -604,22 +588,33 @@ func (b *TelegramBot) sendOutage() *TelegramOutage { } } -// notice queues a short answer of the bot for the chat. -func (b *TelegramBot) notice(text string) { - b.Notify(Event{ - Kind: "notice", - Text: text, - }) -} - -// Fail logs the start of an outage with err and delay, unless an outage runs. -func (o *TelegramOutage) Fail(err error, delay time.Duration) { - if !o.start.IsZero() { +// Notify queues event for the chat. It never blocks, so a slow network does not +// stop the agent. The WhatsApp batches and the runs of the schedules do not go +// to the chat: the reply of the agent tells the user what needs the user. +func (b *TelegramBot) Notify(event Event) { + switch event.Kind { + case "busy", "idle", "reply", "error", "notice", "subagent": + case "work": + if event.Source == whatsappSource || event.Source == scheduleSource { + return + } + case "user": + if event.Source == telegramSource { + return + } + default: return } - o.start = time.Now() - o.logger.Warn(o.loss, "err", err, "retry", delay) + b.mutex.Lock() + + b.queue = append(b.queue, event) + b.mutex.Unlock() + + select { + case b.ready <- struct{}{}: + default: + } } // End logs the end of the outage that runs, with its length. @@ -633,6 +628,16 @@ func (o *TelegramOutage) End() { o.start = time.Time{} } +// Fail logs the start of an outage with err and delay, unless an outage runs. +func (o *TelegramOutage) Fail(err error, delay time.Duration) { + if !o.start.IsZero() { + return + } + + o.start = time.Now() + o.logger.Warn(o.loss, "err", err, "retry", delay) +} + // telegramLoadOffset reads the update offset at path. A missing file gives 0, // which asks for every update that Telegram keeps. func telegramLoadOffset(path string) (int64, error) {