diff --git a/cmd/tg/history.go b/cmd/tg/history.go index 1f12c64..5844fa3 100644 --- a/cmd/tg/history.go +++ b/cmd/tg/history.go @@ -59,7 +59,14 @@ func (h historyResult) MarshalText(w io.Writer) error { // listHistory reads up to limit recent messages from peer (newest-first from the // API), returning them oldest-first. func listHistory(ctx context.Context, api *tg.Client, peer tg.InputPeerClass, limit int) (historyResult, error) { - iter := query.Messages(api).GetHistory(peer).Iter() + // Fetch in large batches to minimize round trips. The iterator's default + // batch size is 1, so it issues one messages.getHistory RPC per message, + // which makes larger --limit values extremely slow. Telegram does not + // reject a per-request limit above 100 but silently returns fewer/incorrect + // results, so cap at 100. Clamp to at least 1: a non-positive batch size + // would panic the iterator (it preallocates a slice with that capacity). + batch := max(1, min(limit, 100)) + iter := query.Messages(api).GetHistory(peer).BatchSize(batch).Iter() var res historyResult for iter.Next(ctx) { diff --git a/cmd/tg/history_test.go b/cmd/tg/history_test.go index b5ab135..278055d 100644 --- a/cmd/tg/history_test.go +++ b/cmd/tg/history_test.go @@ -64,3 +64,41 @@ func TestListHistoryLimit(t *testing.T) { t.Fatalf("limit not respected: got %d", len(res.Messages)) } } + +// TestListHistoryBatchSize asserts the per-request limit is batched (capped at +// 100) instead of the iterator default of 1, and never goes below 1 (a +// non-positive batch size panics the iterator). +func TestListHistoryBatchSize(t *testing.T) { + for _, tt := range []struct { + limit int + wantBatch int + }{ + {limit: 5, wantBatch: 5}, + {limit: 100, wantBatch: 100}, + {limit: 350, wantBatch: 100}, + {limit: 0, wantBatch: 1}, + {limit: -1, wantBatch: 1}, + } { + var gotBatch int + api := newFuncAPI(t, func(req bin.Encoder) (bin.Encoder, error) { + r, ok := req.(*tg.MessagesGetHistoryRequest) + if !ok { + return nil, errors.Errorf("unexpected request %T", req) + } + gotBatch = r.Limit + return &tg.MessagesMessages{ + Messages: []tg.MessageClass{ + &tg.Message{ID: 1, PeerID: &tg.PeerUser{UserID: 5}, Date: 1}, + }, + Users: []tg.UserClass{&tg.User{ID: 5}}, + }, nil + }) + + if _, err := listHistory(context.Background(), api, &tg.InputPeerSelf{}, tt.limit); err != nil { + t.Fatalf("limit %d: %v", tt.limit, err) + } + if gotBatch != tt.wantBatch { + t.Errorf("limit %d: request limit = %d, want %d", tt.limit, gotBatch, tt.wantBatch) + } + } +}