From 7e1b1f1bc6308fa0df019fd00146d95ecbbdc33b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 11 Oct 2026 19:53:38 +0000 Subject: [PATCH 1/2] Apply remaining changes Co-authored-by: hloeung <95021+hloeung@users.noreply.github.com> --- client.go | 103 ++++++++++++++++++++++++++++++++++++++-------------- go.mod | 6 +++ go.sum | 87 ++++++++++++++++++++++++++++++++++++++++++++ handlers.go | 46 ++++++++++++++++++++--- types.go | 7 ++-- 5 files changed, 213 insertions(+), 36 deletions(-) diff --git a/client.go b/client.go index 378f986..57a75e5 100644 --- a/client.go +++ b/client.go @@ -5,6 +5,7 @@ package matrixclient import ( "context" "errors" + "maps" "slices" "strings" "sync" @@ -15,7 +16,8 @@ import ( "maunium.net/go/mautrix/event" "maunium.net/go/mautrix/id" - logger "github.com/sirupsen/logrus" + prefixed "github.com/matterbridge/logrus-prefixed-formatter" + "github.com/sirupsen/logrus" ) // Client is a logged in Matrix client keeping track of rooms and users. @@ -28,16 +30,38 @@ type Client struct { ctx context.Context //nolint:containedctx cancel context.CancelFunc - firstSync atomic.Bool connected atomic.Bool + logger *logrus.Entry + rootLogger *logrus.Logger + apiLogger *logrus.Entry + rootAPILogger *logrus.Logger + channels map[id.RoomID]*Channel dmChannels map[id.RoomID][]id.UserID + directData map[id.UserID][]id.RoomID // last known m.direct account data users map[id.UserID]*User } // New logs in and starts syncing. Handler.Ready is called after the initial sync. func New(ctx context.Context, cred Credentials, h Handler) (*Client, error) { + // Logger for the rest of matrixclient + rootLogger := logrus.New() + rootLogger.SetFormatter(&prefixed.TextFormatter{ + PrefixPadding: 13, + DisableColors: false, + FullTimestamp: true, + }) + // Logger for Matrix API calls + rootAPILogger := logrus.New() + rootAPILogger.SetFormatter(&prefixed.TextFormatter{ + PrefixPadding: 18, + DisableColors: false, + FullTimestamp: true, + }) + // Default to higher than "warn" to not log anything + rootAPILogger.SetLevel(logrus.ErrorLevel) + mc, err := mautrix.NewClient(cred.Server, "", "") if err != nil { return nil, err @@ -54,10 +78,17 @@ func New(ctx context.Context, cred Credentials, h Handler) (*Client, error) { } c := &Client{ - mc: mc, - handler: h, + mc: mc, + handler: h, + + rootLogger: rootLogger, + logger: rootLogger.WithFields(logrus.Fields{"prefix": "matrixclient"}), + rootAPILogger: rootAPILogger, + apiLogger: rootAPILogger.WithFields(logrus.Fields{"prefix": "matrixclient: API"}), + channels: make(map[id.RoomID]*Channel), dmChannels: make(map[id.RoomID][]id.UserID), + directData: make(map[id.UserID][]id.RoomID), users: make(map[id.UserID]*User), } c.ctx, c.cancel = context.WithCancel(ctx) @@ -69,8 +100,11 @@ func New(ctx context.Context, cred Credentials, h Handler) (*Client, error) { c.register(syncer) + // Ready is called from the sync goroutine, after the events of the first + // sync response were processed, so handler calls stay serialized. + mc.Syncer = &readySyncer{DefaultSyncer: syncer, ready: h.Ready} + go c.syncLoop() - go c.waitReady() c.connected.Store(true) @@ -84,11 +118,6 @@ func (c *Client) register(s *mautrix.DefaultSyncer) { s.OnEventType(event.StateRoomName, c.handleRoomName) s.OnEventType(event.AccountDataDirectChats, c.handleDM) s.OnEventType(event.StateCanonicalAlias, c.handleCanonicalAlias) - s.OnSync(func(_ context.Context, _ *mautrix.RespSync, _ string) bool { - c.firstSync.Store(true) - - return true - }) } func (c *Client) syncLoop() { @@ -98,7 +127,7 @@ func (c *Client) syncLoop() { continue } - logger.Errorf("matrixclient: sync: %s", err) + c.apiLogger.Errorf("sync: %s", err) select { case <-c.ctx.Done(): @@ -107,22 +136,21 @@ func (c *Client) syncLoop() { } } -// waitReady waits for the first sync response, and gives its events a moment -// to be processed before reporting ready. -func (c *Client) waitReady() { - for !c.firstSync.Load() { - select { - case <-c.ctx.Done(): - return - case <-time.After(250 * time.Millisecond): - } - } +// readySyncer calls ready once, after the first sync response was processed. +type readySyncer struct { + *mautrix.DefaultSyncer - select { - case <-c.ctx.Done(): - case <-time.After(time.Second): - c.handler.Ready() + once sync.Once + ready func() +} + +func (r *readySyncer) ProcessResponse(ctx context.Context, res *mautrix.RespSync, since string) error { + err := r.DefaultSyncer.ProcessResponse(ctx, res, since) + if err == nil { + r.once.Do(r.ready) } + + return err } // Logout stops syncing. @@ -186,10 +214,15 @@ func (c *Client) Rooms() []RoomInfo { for roomID, ch := range c.channels { ch.RLock() - alias, direct, members := ch.Alias, ch.IsDirect, len(ch.Members) + name, alias, direct, members := ch.Name, ch.Alias, ch.IsDirect, len(ch.Members) ch.RUnlock() + // A canonical alias is preferred over the room name. + if alias == "" { + alias = id.RoomAlias(name) + } + if alias == "" { alias = id.RoomAlias(roomID) } @@ -289,7 +322,23 @@ func (c *Client) SendDirect(ctx context.Context, userID id.UserID, parentID id.E roomID = resp.RoomID c.Lock() - c.dmChannels[roomID] = []id.UserID{userID} + direct := maps.Clone(c.directData) + c.Unlock() + + if direct == nil { + direct = make(map[id.UserID][]id.RoomID) + } + + direct[userID] = append(slices.Clone(direct[userID]), roomID) + + // m.direct is not set by the homeserver, persist it ourselves. + if err := c.mc.SetAccountData(ctx, event.AccountDataDirectChats.Type, direct); err != nil { + c.logger.Errorf("failed to persist m.direct for %s: %s", roomID, err) + } + + c.Lock() + c.directData[userID] = direct[userID] + c.addDirectLocked(roomID, userID, nil) c.Unlock() } diff --git a/go.mod b/go.mod index 4bc16e2..43e64ca 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/matterbridge/matrixclient go 1.26.0 require ( + github.com/matterbridge/logrus-prefixed-formatter v0.5.3-0.20200523233437-d971309a77ba github.com/sirupsen/logrus v1.9.4 maunium.net/go/mautrix v0.31.0 ) @@ -11,15 +12,20 @@ require ( filippo.io/edwards25519 v1.2.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect + github.com/onsi/ginkgo v1.16.5 // indirect + github.com/onsi/gomega v1.44.0 // indirect github.com/rs/zerolog v1.35.1 // indirect github.com/tidwall/gjson v1.19.0 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.1 // indirect github.com/tidwall/sjson v1.2.5 // indirect + github.com/x-cray/logrus-prefixed-formatter v0.5.2 // indirect go.mau.fi/util v0.10.1 // indirect golang.org/x/crypto v0.57.0 // indirect golang.org/x/exp v0.0.0-20260908205506-85c1c2202aba // indirect golang.org/x/net v0.59.0 // indirect golang.org/x/sys v0.48.0 // indirect + golang.org/x/term v0.46.0 // indirect golang.org/x/text v0.42.0 // indirect ) diff --git a/go.sum b/go.sum index 869f097..66a94aa 100644 --- a/go.sum +++ b/go.sum @@ -1,13 +1,50 @@ filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo= filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/matterbridge/logrus-prefixed-formatter v0.5.3-0.20200523233437-d971309a77ba h1:XleOY4IjAEIcxAh+IFwT5JT5Ze3RHiYz6m+4ZfZ0rc0= +github.com/matterbridge/logrus-prefixed-formatter v0.5.3-0.20200523233437-d971309a77ba/go.mod h1:iXGEotOvwI1R1SjLxRc+BF5rUORTMtE0iMZBT2lxqAU= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= +github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.44.0 h1:eAiGl3Pw5jz5GQdDff0BcxYpAX1JxW8xD7mFUuwNfZQ= +github.com/onsi/gomega v1.44.0/go.mod h1:e/C2HwaZ1DhvjzXXuFhcR7hY7Sh9pl7MmoWKEjzwcdA= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rs/zerolog v1.35.1 h1:m7xQeoiLIiV0BCEY4Hs+j2NG4Gp2o2KPKmhnnLiazKI= github.com/rs/zerolog v1.35.1/go.mod h1:EjML9kdfa/RMA7h/6z6pYmq1ykOuA8/mjWaEvGI+jcw= github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.12.0 h1:K6Mr6jO9JICuend/5xzTM03ydSV3vdNRYAdPSukj8uI= github.com/stretchr/testify v1.12.0/go.mod h1:bOYBZb5qJ00vPzWfIqBUZPaxK8jWiXc6d3ErP4Ca9Gw= github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= @@ -20,19 +57,69 @@ github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +github.com/x-cray/logrus-prefixed-formatter v0.5.2 h1:00txxvfBM9muc0jiLIEAkAcIMJzfthRT6usrui8uGmg= +github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.mau.fi/util v0.10.1 h1:1oSqb4TwzLA0cUDY0aomyBPFKkZ3J5fqCmrXV3VH3GQ= go.mau.fi/util v0.10.1/go.mod h1:40TDo7/ekSeOjgr8KAmX31Yf4zrOF94j83WQB+u5ZPc= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.57.0 h1:3ZVCjf8Ggz7zneR/EHRVx68Ctf+2pmIMP2UFhh9cC6M= golang.org/x/crypto v0.57.0/go.mod h1:Fdz0i5U6CoizGwLda9DttjSk6qlZo25zYNtR+ycvuZA= golang.org/x/exp v0.0.0-20260908205506-85c1c2202aba h1:Ck8QetSgk912qxWLMCKxd0in+aiyBQyDSMae6e/xmpU= golang.org/x/exp v0.0.0-20260908205506-85c1c2202aba/go.mod h1:50RgIsmK7OwqzTTeqcSXQW8SswW0o8fRcDxmqGluJ8E= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.59.0 h1:5zfYln+w5XCxwrnMMJPufRgNoXEaGxl0wo5GqPXyues= golang.org/x/net v0.59.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo= golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og= +golang.org/x/term v0.46.0 h1:3+OXuTbaKDgwk8jTi3aSLHRlmWqHEUDUtxnbFigO4YE= +golang.org/x/term v0.46.0/go.mod h1:+K02xbkittuwc0Am4abfA3Fc+XRGXkvBXNO88NCXPoc= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI= golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= maunium.net/go/mautrix v0.31.0 h1:x6XNBSa0kOaBgcleI+ZBLkGvBdHIE0qdeY6TZ4wWUeI= diff --git a/handlers.go b/handlers.go index 3b42a97..f5e0e90 100644 --- a/handlers.go +++ b/handlers.go @@ -19,9 +19,18 @@ func (c *Client) channelLocked(roomID id.RoomID) *Channel { return ch } +// addDirectLocked marks roomID as direct with userID. A nil member keeps any +// cached profile of the user. func (c *Client) addDirectLocked(roomID id.RoomID, userID id.UserID, member *event.MemberEventContent) { - u := &User{ID: userID, MemberEventContent: member} - c.users[userID] = u + u, ok := c.users[userID] + if !ok { + u = &User{ID: userID} + c.users[userID] = u + } + + if member != nil { + u.MemberEventContent = member + } ch := c.channelLocked(roomID) @@ -35,6 +44,7 @@ func (c *Client) addDirectLocked(roomID id.RoomID, userID id.UserID, member *eve } } +// handleDM rebuilds the direct room state, m.direct is a full snapshot. func (c *Client) handleDM(_ context.Context, ev *event.Event) { chats := ev.Content.AsDirectChats() if chats == nil { @@ -44,9 +54,22 @@ func (c *Client) handleDM(_ context.Context, ev *event.Event) { c.Lock() defer c.Unlock() + for roomID := range c.dmChannels { + if ch, ok := c.channels[roomID]; ok { + ch.Lock() + ch.IsDirect = false + ch.Unlock() + } + } + + c.dmChannels = make(map[id.RoomID][]id.UserID) + c.directData = make(map[id.UserID][]id.RoomID, len(*chats)) + for userID, rooms := range *chats { + c.directData[userID] = slices.Clone(rooms) + for _, roomID := range rooms { - c.addDirectLocked(roomID, userID, &event.MemberEventContent{}) + c.addDirectLocked(roomID, userID, nil) } } } @@ -70,6 +93,17 @@ func (c *Client) handleMember(_ context.Context, ev *event.Event) { u.MemberEventContent = member + ch := c.channelLocked(ev.RoomID) + + ch.Lock() + switch member.Membership { + case event.MembershipJoin, event.MembershipInvite: + ch.Members[targetID] = u + default: + delete(ch.Members, targetID) + } + ch.Unlock() + if member.IsDirect { c.addDirectLocked(ev.RoomID, targetID, member) } @@ -89,9 +123,9 @@ func (c *Client) handleRoomName(_ context.Context, ev *event.Event) { ch.Lock() defer ch.Unlock() - // A canonical alias is preferred over the room name. - if ch.Alias == "" { - ch.Alias = id.RoomAlias("#" + strings.ReplaceAll(name.Name, " ", "")) + ch.Name = "" + if name.Name != "" { + ch.Name = "#" + strings.ReplaceAll(name.Name, " ", "") } } diff --git a/types.go b/types.go index 5c45de5..d46c6e5 100644 --- a/types.go +++ b/types.go @@ -26,6 +26,7 @@ type Channel struct { sync.RWMutex ID id.RoomID + Name string Alias id.RoomAlias AltAliases []id.RoomAlias Members map[id.UserID]*User @@ -54,10 +55,10 @@ type Reaction struct { Direct bool } -// Handler receives events from the client. All methods are called from the -// sync goroutine and must not block for long. +// Handler receives events from the client. All methods, including Ready, are +// called from the sync goroutine and must not block for long. type Handler interface { - // Ready is called once, after the initial sync completed. + // Ready is called once, after the events of the initial sync were processed. Ready() OnMessage(msg *Message) OnReaction(r *Reaction) From 3d73df4344a1f777b633970a26b39137fc2713f7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 11 Oct 2026 20:12:47 +0000 Subject: [PATCH 2/2] Apply remaining changes Co-authored-by: hloeung <95021+hloeung@users.noreply.github.com> --- client.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client.go b/client.go index 57a75e5..c3d6f54 100644 --- a/client.go +++ b/client.go @@ -332,7 +332,8 @@ func (c *Client) SendDirect(ctx context.Context, userID id.UserID, parentID id.E direct[userID] = append(slices.Clone(direct[userID]), roomID) // m.direct is not set by the homeserver, persist it ourselves. - if err := c.mc.SetAccountData(ctx, event.AccountDataDirectChats.Type, direct); err != nil { + err = c.mc.SetAccountData(ctx, event.AccountDataDirectChats.Type, direct) + if err != nil { c.logger.Errorf("failed to persist m.direct for %s: %s", roomID, err) }