Inbound telegram: turns and corrections from the chat #187

Merged
claude merged 9 commits from task/637-inbound-telegram-turns-and-corrections-f into master 2026-08-06 19:02:00 +02:00
Showing only changes of commit 06c1cf247e - Show all commits
+27 -8
View File
@@ -14,6 +14,7 @@ package telegramsink
import (
"context"
"errors"
"fmt"
"log"
"net/http"
"strings"
@@ -58,6 +59,13 @@ func NewPoller(s *Sink, turn Turn, correct Correct) (*Poller, error) {
if turn == nil {
return nil, errors.New("telegramsink: intake needs a turn handler")
}
// The push half accepts @channelusername as a destination. The intake half
// cannot: an inbound update names its chat by numeric id, so an @-name would
// match nothing and the poller would read the chat and answer none of it.
// Refusing here is the difference between a boot error and a dead reach.
if strings.HasPrefix(strings.TrimSpace(s.cfg.ChatID), "@") {
return nil, fmt.Errorf("telegramsink: intake needs the numeric chat id, not %s", s.cfg.ChatID)
}
// The sink's transport already carries the relay. Only the timeout differs,
// and it has to clear the long poll.
hc := &http.Client{
@@ -101,15 +109,26 @@ func (p *Poller) Run(ctx context.Context) {
// stopped mattering, and a reminder set from it would land at the wrong time.
// Missing it is the safe direction.
func (p *Poller) discardBacklog(ctx context.Context) {
updates, err := p.getUpdates(ctx, 0)
if err != nil {
// Not fatal. The offset stays 0, so the first real poll sees the backlog
// and the messages below get answered late. Say so rather than hide it.
log.Printf("telegram intake: could not skip the backlog, old messages may be answered: %v", err)
return
// getUpdates returns at most 100 per call, so one call is not the queue. The
// loop is bounded rather than "until empty": the timeout is 0, so an instance
// that keeps handing back a full batch would spin, and a thousand skipped
// messages is already a box that was down for a long time.
skipped := 0
for range 10 {
updates, err := p.getUpdates(ctx, 0)
if err != nil {
// Not fatal. The offset stays where it was, so the first real poll sees
// what is left and answers it late. Say so rather than hide it.
log.Printf("telegram intake: could not skip the backlog, old messages may be answered: %v", err)
return
}
skipped += len(updates)
if len(updates) == 0 {
break
}
}
if len(updates) > 0 {
log.Printf("telegram intake: skipped %d message(s) queued while the daemon was down", len(updates))
if skipped > 0 {
log.Printf("telegram intake: skipped %d message(s) queued while the daemon was down", skipped)
}
}