From 1eb15a31359221dee91976bba9ed38579b811aa9 Mon Sep 17 00:00:00 2001 From: Logan Pratt Date: Sun, 18 Oct 2026 08:48:05 -0400 Subject: [PATCH] fix(watch,wait): accept me/self and id: peer filters watch and wait passed their peer argument straight to the username resolver, so `tg wait me` / `tg watch me` failed with USERNAME_INVALID, and the id: form documented for every never worked as a filter. Resolve the filter like other peer arguments: me/self via the current user, id: from the peer cache, anything else as before. Co-Authored-By: Claude Opus 5.5 --- cmd/tg/peers.go | 16 ++++++++++++++++ cmd/tg/peers_test.go | 26 ++++++++++++++++++++++++++ cmd/tg/watch.go | 2 +- 3 files changed, 43 insertions(+), 1 deletion(-) diff --git a/cmd/tg/peers.go b/cmd/tg/peers.go index 6659211..4152b63 100644 --- a/cmd/tg/peers.go +++ b/cmd/tg/peers.go @@ -150,6 +150,22 @@ func builderFor(ctx context.Context, m *peerManager, sender *message.Sender, pee return sender.Resolve(peer), nil } +// resolveManagedPeer resolves any argument — me/self, id:, +// @username, phone or link — to a typed peer. +func resolveManagedPeer(ctx context.Context, m *peerManager, arg string) (peers.Peer, error) { + if isSelf(arg) { + self, err := m.Self(ctx) + if err != nil { + return nil, errors.Wrap(err, "resolve self") + } + return self, nil + } + if isIDArg(arg) { + return m.resolveID(ctx, arg) + } + return m.Resolve(ctx, arg) +} + // resolvePeer turns a peer string into an InputPeer using the cached manager. // The empty string, "me" and "self" resolve to the current account; an "id:" // argument resolves a cached peer by numeric id. diff --git a/cmd/tg/peers_test.go b/cmd/tg/peers_test.go index e5c3007..c42bc08 100644 --- a/cmd/tg/peers_test.go +++ b/cmd/tg/peers_test.go @@ -5,6 +5,9 @@ import ( "path/filepath" "testing" + "github.com/go-faster/errors" + + "github.com/gotd/td/bin" "github.com/gotd/td/telegram/message" "github.com/gotd/td/telegram/peers" "github.com/gotd/td/tg" @@ -139,3 +142,26 @@ func TestBuilderForSelf(t *testing.T) { } } } + +func TestResolveManagedPeerSelf(t *testing.T) { + // Regression: watch/wait passed "me" to the username resolver, which + // failed with USERNAME_INVALID. + store, err := peercache.Open(filepath.Join(t.TempDir(), "peers.json")) + if err != nil { + t.Fatal(err) + } + api := newFuncAPI(t, func(req bin.Encoder) (bin.Encoder, error) { + if _, ok := req.(*tg.UsersGetUsersRequest); !ok { + return nil, errors.Errorf("unexpected request %T", req) + } + return &tg.UserClassVector{Elems: []tg.UserClass{&tg.User{ID: 42, Self: true, AccessHash: 1}}}, nil + }) + m := &peerManager{Manager: peers.Options{Storage: store}.Build(api), store: store} + + for _, arg := range []string{"Me", "SELF", ""} { + p, err := resolveManagedPeer(context.Background(), m, arg) + if err != nil || p.ID() != 42 { + t.Errorf("resolveManagedPeer(%q) = %v, %v; want self (42)", arg, p, err) + } + } +} diff --git a/cmd/tg/watch.go b/cmd/tg/watch.go index 012de71..71b98cb 100644 --- a/cmd/tg/watch.go +++ b/cmd/tg/watch.go @@ -69,7 +69,7 @@ func (a *app) resolveFilterFor(ctx context.Context, api *tg.Client, st *accountS if err != nil { return 0, err } - p, err := m.Resolve(ctx, args[0]) + p, err := resolveManagedPeer(ctx, m, args[0]) if err != nil { return 0, errors.Wrapf(err, "resolve %q", args[0]) }