the intake allowlist has to be a numeric chat id (V-637)

Two defects my own review found.

The push half accepts @channelusername as a destination. The intake half
cannot: an inbound update names its chat by numeric id, so that config would
read the chat, match nothing, and answer none of it. Refused at NewPoller,
which turns a dead reach into a line in the log.

And getUpdates returns at most 100 updates per call, so one call was not the
backlog. The skip loops, bounded at ten rounds rather than until empty, so
an instance that keeps handing back a full batch cannot spin.
This commit is contained in:
2026-08-06 21:01:16 +04:00
parent 400653810e
commit 06c1cf247e
+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)
}
}