From 69c9e3c38cde2479c1db5a0ea5f6270358f8268b Mon Sep 17 00:00:00 2001 From: Sumner Evans Date: Tue, 16 Jul 2024 11:00:48 -0600 Subject: [PATCH] client: fix GetChatInfo for channels Signed-off-by: Sumner Evans --- go.mod | 14 ++-- go.sum | 28 ++++---- pkg/connector/client.go | 134 ++++++++++++++++++++++++++++++-------- pkg/connector/config.go | 3 +- pkg/connector/telegram.go | 23 ++++--- 5 files changed, 144 insertions(+), 58 deletions(-) diff --git a/go.mod b/go.mod index c3167b68..d9139b97 100644 --- a/go.mod +++ b/go.mod @@ -6,10 +6,10 @@ require ( github.com/gotd/td v0.102.0 github.com/rs/zerolog v1.33.0 github.com/stretchr/testify v1.9.0 - go.mau.fi/util v0.5.1-0.20240713134429-03648b3ede41 + go.mau.fi/util v0.6.0 go.mau.fi/zerozap v0.1.1 go.uber.org/zap v1.27.0 - maunium.net/go/mautrix v0.19.0-beta.1.0.20240714121051-fb9fb5ae4405 + maunium.net/go/mautrix v0.19.1-0.20240716175930-085859bfdd7c ) require ( @@ -37,16 +37,16 @@ require ( github.com/tidwall/pretty v1.2.0 // indirect github.com/tidwall/sjson v1.2.5 // indirect github.com/yuin/goldmark v1.7.4 // indirect - go.mau.fi/zeroconfig v0.1.2 // indirect + go.mau.fi/zeroconfig v0.1.3 // indirect go.opentelemetry.io/otel v1.26.0 // indirect go.opentelemetry.io/otel/trace v1.26.0 // indirect go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.24.0 // indirect - golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect - golang.org/x/net v0.26.0 // indirect + golang.org/x/crypto v0.25.0 // indirect + golang.org/x/exp v0.0.0-20240707233637-46b078467d37 // indirect + golang.org/x/net v0.27.0 // indirect golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.21.0 // indirect + golang.org/x/sys v0.22.0 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect maunium.net/go/mauflag v1.0.0 // indirect diff --git a/go.sum b/go.sum index c84d8229..aae74fb8 100644 --- a/go.sum +++ b/go.sum @@ -67,10 +67,10 @@ 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/yuin/goldmark v1.7.4 h1:BDXOHExt+A7gwPCJgPIIq7ENvceR7we7rOS9TNoLZeg= github.com/yuin/goldmark v1.7.4/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= -go.mau.fi/util v0.5.1-0.20240713134429-03648b3ede41 h1:suJqVZoWuiqmMo/xojAGSxz04fOYYu0oE7sFPrf2L5c= -go.mau.fi/util v0.5.1-0.20240713134429-03648b3ede41/go.mod h1:DsJzUrJAG53lCZnnYvq9/mOyLuPScWwYhvETiTrpdP4= -go.mau.fi/zeroconfig v0.1.2 h1:DKOydWnhPMn65GbXZOafgkPm11BvFashZWLct0dGFto= -go.mau.fi/zeroconfig v0.1.2/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70= +go.mau.fi/util v0.6.0 h1:W6SyB3Bm/GjenQ5iq8Z8WWdN85Gy2xS6L0wmnR7SVjg= +go.mau.fi/util v0.6.0/go.mod h1:ljYdq3sPfpICc3zMU+/mHV/sa4z0nKxc67hSBwnrk8U= +go.mau.fi/zeroconfig v0.1.3 h1:As9wYDKmktjmNZW5i1vn8zvJlmGKHeVxHVIBMXsm4kM= +go.mau.fi/zeroconfig v0.1.3/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70= go.mau.fi/zerozap v0.1.1 h1:mxE/dW4wtkqBYOXOEEzXldk5qKB+ahsZXjoTGnvEhZQ= go.mau.fi/zerozap v0.1.1/go.mod h1:eRYfQIyL4nTvxaBtVoFqfhdd2vp7pxiHdtvMy2w7XVg= go.opentelemetry.io/otel v1.26.0 h1:LQwgL5s/1W7YiiRwxf03QGnWLb2HW4pLiAhaA5cZXBs= @@ -85,20 +85,20 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= -golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= -golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY= -golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= -golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= -golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= +golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= +golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/exp v0.0.0-20240707233637-46b078467d37 h1:uLDX+AfeFCct3a2C7uIWBKMJIR3CJMhcgfrUAqjRK6w= +golang.org/x/exp v0.0.0-20240707233637-46b078467d37/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= +golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= +golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= -golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= @@ -108,8 +108,8 @@ 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/mauflag v1.0.0 h1:YiaRc0tEI3toYtJMRIfjP+jklH45uDHtT80nUamyD4M= maunium.net/go/mauflag v1.0.0/go.mod h1:nLivPOpTpHnpzEh8jEdSL9UqO9+/KBJFmNRlwKfkPeA= -maunium.net/go/mautrix v0.19.0-beta.1.0.20240714121051-fb9fb5ae4405 h1:ltVzYFIc2mef32PG3qXMlpddreHToSqmZFSdEKAxjiU= -maunium.net/go/mautrix v0.19.0-beta.1.0.20240714121051-fb9fb5ae4405/go.mod h1:ldNVOQXaljMk4YLzlohp+DniMQtCSzTVcwjEFBlYQLM= +maunium.net/go/mautrix v0.19.1-0.20240716175930-085859bfdd7c h1:yREcYm3g5auPgTm0QBrzQVimMxxsL5N3oNU+iFjnjeY= +maunium.net/go/mautrix v0.19.1-0.20240716175930-085859bfdd7c/go.mod h1:UE+mSQ4sDUuJMbjN0aB9EjQSGgXd48AzMvZ6+QJV1k8= nhooyr.io/websocket v1.8.11 h1:f/qXNc2/3DpoSZkHt1DQu6rj4zGC8JmkkLkWss0MgN0= nhooyr.io/websocket v1.8.11/go.mod h1:rN9OFWIUwuxg4fR5tELlYC04bXYowCP9GX47ivo2l+c= rsc.io/qr v0.2.0 h1:6vBLea5/NRMVTz8V66gipeLycZMl/+UlFmk8DvqQ6WY= diff --git a/pkg/connector/client.go b/pkg/connector/client.go index c3c74bee..1d4b5b43 100644 --- a/pkg/connector/client.go +++ b/pkg/connector/client.go @@ -6,11 +6,13 @@ import ( "fmt" "strings" "sync" + "time" "github.com/gotd/td/telegram" "github.com/gotd/td/telegram/updates" "github.com/gotd/td/tg" "github.com/rs/zerolog" + "go.mau.fi/util/ptr" "go.mau.fi/zerozap" "go.uber.org/zap" "maunium.net/go/mautrix/bridge/status" @@ -204,12 +206,13 @@ func (t *TelegramClient) GetChatInfo(ctx context.Context, portal *bridgev2.Porta if err != nil { return nil, err } - var name string + var name, topic *string roomType := database.RoomTypeDM memberList := &bridgev2.ChatMemberList{ IsFull: true, // TODO not true for channels } var avatar *bridgev2.Avatar + var disappearingSetting *database.DisappearingSetting switch peerType { case ids.PeerTypeUser: @@ -223,7 +226,8 @@ func (t *TelegramClient) GetChatInfo(ctx context.Context, portal *bridgev2.Porta if user, ok := users[0].(*tg.User); !ok { return nil, fmt.Errorf("returned user is not *tg.User") } else { - name = util.FormatFullName(user.FirstName, user.LastName) // TODO gate this behind a config? + name = ptr.Ptr(util.FormatFullName(user.FirstName, user.LastName)) // TODO gate this behind a config? + t.updateGhost(ctx, id, user) memberList.Members = []bridgev2.ChatMember{ { EventSender: bridgev2.EventSender{ @@ -248,7 +252,7 @@ func (t *TelegramClient) GetChatInfo(ctx context.Context, portal *bridgev2.Porta } for _, c := range fullChat.Chats { if c.GetID() == id { - name = c.(*tg.Chat).Title + name = &c.(*tg.Chat).Title break } } @@ -268,14 +272,33 @@ func (t *TelegramClient) GetChatInfo(ctx context.Context, portal *bridgev2.Porta } } - for _, user := range fullChat.Users { - memberList.Members = append(memberList.Members, bridgev2.ChatMember{ - EventSender: bridgev2.EventSender{ - IsFromMe: user.GetID() == t.telegramUserID, - SenderLogin: ids.MakeUserLoginID(user.GetID()), - Sender: ids.MakeUserID(user.GetID()), - }, - }) + if ttl, ok := chatFull.GetTTLPeriod(); ok { + disappearingSetting = &database.DisappearingSetting{ + Type: database.DisappearingTypeAfterSend, + Timer: time.Duration(ttl) * time.Second, + } + } + + if about := chatFull.GetAbout(); about != "" { + topic = &about + } + + for _, user := range fullChat.GetUsers() { + if user, ok := user.(*tg.User); ok { + t.updateGhost(ctx, id, user) + } + } + + if participants, ok := chatFull.Participants.(*tg.ChatParticipants); ok { + for _, user := range participants.Participants { + memberList.Members = append(memberList.Members, bridgev2.ChatMember{ + EventSender: bridgev2.EventSender{ + IsFromMe: user.GetUserID() == t.telegramUserID, + SenderLogin: ids.MakeUserLoginID(user.GetUserID()), + Sender: ids.MakeUserID(user.GetUserID()), + }, + }) + } } case ids.PeerTypeChannel: accessHash, found, err := t.ScopedStore.GetChannelAccessHash(ctx, t.telegramUserID, id) @@ -284,7 +307,8 @@ func (t *TelegramClient) GetChatInfo(ctx context.Context, portal *bridgev2.Porta } else if !found { return nil, fmt.Errorf("channel access hash not found for %d", id) } - fullChat, err := t.client.API().ChannelsGetFullChannel(ctx, &tg.InputChannel{ChannelID: id, AccessHash: accessHash}) + inputChannel := &tg.InputChannel{ChannelID: id, AccessHash: accessHash} + fullChat, err := t.client.API().ChannelsGetFullChannel(ctx, inputChannel) if err != nil { return nil, err } @@ -292,20 +316,20 @@ func (t *TelegramClient) GetChatInfo(ctx context.Context, portal *bridgev2.Porta if c.GetID() == id { switch chat := c.(type) { case *tg.Chat: - name = chat.Title + name = &chat.Title case *tg.Channel: - name = chat.Title + name = &chat.Title } break } } - chatFull, ok := fullChat.FullChat.(*tg.ChatFull) + channelFull, ok := fullChat.FullChat.(*tg.ChannelFull) if !ok { - return nil, fmt.Errorf("full chat is %T *tg.ChatFull", fullChat.FullChat) + return nil, fmt.Errorf("full chat is %T not *tg.ChannelFull", fullChat.FullChat) } - if photo, ok := chatFull.GetChatPhoto(); ok { + if photo := channelFull.GetChatPhoto(); photo.TypeID() == tg.PhotoTypeID { avatar = &bridgev2.Avatar{ ID: ids.MakeAvatarID(photo.GetID()), Get: func(ctx context.Context) (data []byte, err error) { @@ -315,15 +339,64 @@ func (t *TelegramClient) GetChatInfo(ctx context.Context, portal *bridgev2.Porta } } + if ttl, ok := channelFull.GetTTLPeriod(); ok { + disappearingSetting = &database.DisappearingSetting{ + Type: database.DisappearingTypeAfterSend, + Timer: time.Duration(ttl) * time.Second, + } + } + + if about := channelFull.GetAbout(); about != "" { + topic = &about + } + + // TODO save available reactions? + // TODO save reactions limit? + // TODO save emojiset? + memberList.IsFull = false - for _, user := range fullChat.Users { - memberList.Members = append(memberList.Members, bridgev2.ChatMember{ - EventSender: bridgev2.EventSender{ - IsFromMe: user.GetID() == t.telegramUserID, - SenderLogin: ids.MakeUserLoginID(user.GetID()), - Sender: ids.MakeUserID(user.GetID()), - }, + + // TODO when do we have to make a request to get the participants? + + for _, user := range fullChat.GetUsers() { + if user, ok := user.(*tg.User); ok { + t.updateGhost(ctx, id, user) + } + } + + if channelFull.CanViewParticipants && !channelFull.ParticipantsHidden { + // TODO paginate + p, err := t.client.API().ChannelsGetParticipants(ctx, &tg.ChannelsGetParticipantsRequest{ + Channel: inputChannel, + Filter: &tg.ChannelParticipantsSearch{}, + Limit: 200, }) + if err != nil { + return nil, err + } + participants, ok := p.(*tg.ChannelsChannelParticipants) + if !ok { + return nil, fmt.Errorf("returned participants is %T not *tg.ChannelsChannelParticipants", p) + } + for _, user := range participants.GetUsers() { + user, ok := user.(*tg.User) + if !ok { + return nil, fmt.Errorf("participant is %T not *tg.User", user) + } + + userInfo, err := t.getUserInfoFromTelegramUser(user) + if err != nil { + return nil, err + } + memberList.Members = append(memberList.Members, bridgev2.ChatMember{ + EventSender: bridgev2.EventSender{ + IsFromMe: user.GetID() == t.telegramUserID, + SenderLogin: ids.MakeUserLoginID(user.GetID()), + Sender: ids.MakeUserID(user.GetID()), + }, + UserInfo: userInfo, + }) + } } default: // fmt.Printf("%s %d\n", peerType, id) @@ -331,10 +404,13 @@ func (t *TelegramClient) GetChatInfo(ctx context.Context, portal *bridgev2.Porta } return &bridgev2.ChatInfo{ - Name: &name, + Name: name, + Topic: topic, Avatar: avatar, Members: memberList, Type: &roomType, + + Disappear: disappearingSetting, }, nil } @@ -343,7 +419,10 @@ func (t *TelegramClient) GetUserInfo(ctx context.Context, ghost *bridgev2.Ghost) if err != nil { return nil, err } - users, err := t.client.API().UsersGetUsers(ctx, []tg.InputUserClass{&tg.InputUser{UserID: id}}) + users, err := t.client.API().UsersGetUsers(ctx, []tg.InputUserClass{&tg.InputUser{ + UserID: id, + AccessHash: ghost.Metadata.(*GhostMetadata).AccessHash, + }}) if err != nil { return nil, err } @@ -386,8 +465,9 @@ func (t *TelegramClient) getUserInfoFromTelegramUser(user *tg.User) (*bridgev2.U Identifiers: identifiers, ExtraUpdates: func(ctx context.Context, ghost *bridgev2.Ghost) (changed bool) { meta := ghost.Metadata.(*GhostMetadata) - changed = meta.IsPremium != user.Premium + changed = meta.IsPremium != user.Premium || meta.AccessHash != user.AccessHash meta.IsPremium = user.Premium + meta.AccessHash = user.AccessHash return changed }, }, nil diff --git a/pkg/connector/config.go b/pkg/connector/config.go index c0e89df0..41d6c489 100644 --- a/pkg/connector/config.go +++ b/pkg/connector/config.go @@ -66,7 +66,8 @@ func (tg *TelegramConnector) GetDBMetaTypes() database.MetaTypes { } type GhostMetadata struct { - IsPremium bool `json:"is_premium"` + IsPremium bool `json:"is_premium"` + AccessHash int64 `json:"access_hash"` } type UserLoginMetadata struct { diff --git a/pkg/connector/telegram.go b/pkg/connector/telegram.go index 66cb6363..a22d47c9 100644 --- a/pkg/connector/telegram.go +++ b/pkg/connector/telegram.go @@ -186,17 +186,22 @@ func (t *TelegramClient) onDeleteMessages(ctx context.Context, update IGetMessag return nil } +func (t *TelegramClient) updateGhost(ctx context.Context, userID int64, user *tg.User) error { + ghost, err := t.main.Bridge.GetGhostByID(ctx, ids.MakeUserID(userID)) + if err != nil { + return err + } + userInfo, err := t.getUserInfoFromTelegramUser(user) + if err != nil { + return err + } + ghost.UpdateInfo(ctx, userInfo) + return nil +} + func (t *TelegramClient) onEntityUpdate(ctx context.Context, e tg.Entities) error { for userID, user := range e.Users { - ghost, err := t.main.Bridge.GetGhostByID(ctx, ids.MakeUserID(userID)) - if err != nil { - return err - } - userInfo, err := t.getUserInfoFromTelegramUser(user) - if err != nil { - return err - } - ghost.UpdateInfo(ctx, userInfo) + t.updateGhost(ctx, userID, user) } return nil }