+// poll gets updates from the stored offset and handles them until ctx ends. A
+// failed image download retries from the same offset. A submitted message is
+// flushed even if ctx ends, and only then can the offset advance.
+func (b *TelegramBot) poll(ctx context.Context, l *TurnLoop, whatsapp *WhatsApp) error {
+ offset, err := telegramLoadOffset(b.offsetPath)
+ if err != nil {
+ return err
+ }
+
+ outage := &TelegramOutage{
+ logger: b.logger,
+ loss: "receive failed",
+ back: "receive back",
+ }
+
+ imageOutage := &TelegramOutage{
+ logger: b.logger,
+ loss: "image receive failed",
+ back: "image receive back",
+ }
+
+ imageWait := telegramRetryDelay
+ wait := telegramRetryDelay
+
+poll:
+ for ctx.Err() == nil {
+ updates, err := b.api.GetUpdates(ctx, offset)
+ if ctx.Err() != nil {
+ return nil
+ }
+
+ if err != nil {
+ _, asked := telegramRetryable(err)
+ wait = b.pause(ctx, outage, err, wait, asked)
+
+ continue
+ }
+
+ outage.End()
+
+ wait = telegramRetryDelay
+
+ for _, update := range updates {
+ if ctx.Err() != nil {
+ return nil
+ }
+
+ err = b.handle(ctx, l, whatsapp, update)
+ if err != nil {
+ retry, asked := telegramRetryable(err)
+ if retry {
+ imageWait = b.pause(ctx, imageOutage, err, imageWait, asked)
+ continue poll
+ }
+
+ return err
+ }
+
+ imageOutage.End()
+
+ imageWait = telegramRetryDelay
+
+ offset = update.UpdateID + 1
+ err = telegramSaveOffset(b.offsetPath, offset)
+ if err != nil {
+ return err
+ }
+ }
+ }
+
+ return nil
+}
+