From 7d9836c86b6472bb5997a8602832b02fbc89bad3 Mon Sep 17 00:00:00 2001 From: Sumner Evans Date: Wed, 2 Oct 2024 22:57:56 -0600 Subject: [PATCH] power levels: bridge rights for group chats Signed-off-by: Sumner Evans --- pkg/connector/chatinfo.go | 161 +++++++++++++++++++++++++++++++++++--- pkg/connector/client.go | 3 + pkg/connector/sync.go | 42 +++++++++- pkg/connector/telegram.go | 20 +++++ 4 files changed, 215 insertions(+), 11 deletions(-) diff --git a/pkg/connector/chatinfo.go b/pkg/connector/chatinfo.go index 2c46e8bb..6dec33a2 100644 --- a/pkg/connector/chatinfo.go +++ b/pkg/connector/chatinfo.go @@ -10,11 +10,68 @@ import ( "maunium.net/go/mautrix/bridgev2" "maunium.net/go/mautrix/bridgev2/database" "maunium.net/go/mautrix/bridgev2/networkid" + "maunium.net/go/mautrix/event" "go.mau.fi/mautrix-telegram/pkg/connector/ids" "go.mau.fi/mautrix-telegram/pkg/connector/media" ) +var ( + anyonePowerLevel = ptr.Ptr(0) + modPowerLevel = ptr.Ptr(50) + superadminPowerLevel = ptr.Ptr(75) + creatorPowerLevel = ptr.Ptr(95) + + otherPowerLevel = ptr.Ptr(40) + anonymousPowerLevel = ptr.Ptr(41) + postMessagesPowerLevel = ptr.Ptr(42) + editMessagesPowerLevel = ptr.Ptr(43) + deleteMessagesPowerLevel = ptr.Ptr(44) + postStoriesPowerLevel = ptr.Ptr(45) + editStoriesPowerLevel = ptr.Ptr(46) + deleteStoriesPowerLevel = ptr.Ptr(47) + changeInfoPowerLevel = ptr.Ptr(50) + inviteUsersPowerLevel = ptr.Ptr(51) + manageCallPowerLevel = ptr.Ptr(52) + pinMessagesPowerLevel = ptr.Ptr(53) + manageTopicsPowerLevel = ptr.Ptr(54) + banUsersPowerLevel = ptr.Ptr(55) + addAdminsPowerLevel = ptr.Ptr(60) +) + +func adminRightsToPowerLevel(rights tg.ChatAdminRights) *int { + if rights.AddAdmins { + return addAdminsPowerLevel + } else if rights.BanUsers { + return banUsersPowerLevel + } else if rights.ManageTopics { + return manageTopicsPowerLevel + } else if rights.PinMessages { + return pinMessagesPowerLevel + } else if rights.ManageCall { + return manageCallPowerLevel + } else if rights.InviteUsers { + return inviteUsersPowerLevel + } else if rights.ChangeInfo { + return changeInfoPowerLevel + } else if rights.DeleteStories { + return deleteStoriesPowerLevel + } else if rights.EditStories { + return editStoriesPowerLevel + } else if rights.PostStories { + return postStoriesPowerLevel + } else if rights.DeleteMessages { + return deleteMessagesPowerLevel + } else if rights.EditMessages { + return editMessagesPowerLevel + } else if rights.PostMessages { + return postMessagesPowerLevel + } else if rights.Anonymous { + return anonymousPowerLevel + } + return otherPowerLevel +} + func (t *TelegramClient) getDMChatInfo(userID int64) (*bridgev2.ChatInfo, error) { networkUserID := ids.MakeUserID(userID) chatInfo := bridgev2.ChatInfo{ @@ -100,15 +157,28 @@ func (t *TelegramClient) avatarFromPhoto(photo tg.PhotoClass) *bridgev2.Avatar { } } -func (t *TelegramClient) filterChannelParticipants(chatParticipants []tg.ChannelParticipantClass, limit int) (members []bridgev2.ChatMember) { - for _, u := range chatParticipants { - userIDable, ok := u.(interface{ GetUserID() int64 }) - if !ok { +func (t *TelegramClient) filterChannelParticipants(participants []tg.ChannelParticipantClass, limit int) (members []bridgev2.ChatMember) { + for _, u := range participants { + var userID int64 + var powerLevel *int + switch participant := u.(type) { + case *tg.ChannelParticipant: + userID = participant.GetUserID() + case *tg.ChannelParticipantSelf: + userID = participant.GetUserID() + case *tg.ChannelParticipantCreator: + userID = participant.GetUserID() + powerLevel = creatorPowerLevel + case *tg.ChannelParticipantAdmin: + userID = participant.GetUserID() + powerLevel = adminRightsToPowerLevel(participant.AdminRights) + default: continue } members = append(members, bridgev2.ChatMember{ - EventSender: t.senderForUserID(userIDable.GetUserID()), + EventSender: t.senderForUserID(userID), + PowerLevel: powerLevel, }) if len(members) >= limit { @@ -163,8 +233,17 @@ func (t *TelegramClient) GetChatInfo(ctx context.Context, portal *bridgev2.Porta continue } + var powerLevel *int + switch user.(type) { + case *tg.ChatParticipantCreator: + powerLevel = creatorPowerLevel + case *tg.ChatParticipantAdmin: + powerLevel = modPowerLevel + } + chatInfo.Members.MemberMap[ids.MakeUserID(user.GetUserID())] = bridgev2.ChatMember{ EventSender: t.senderForUserID(user.GetUserID()), + PowerLevel: powerLevel, } if len(chatInfo.Members.MemberMap) >= t.main.Config.MemberList.NormalizedMaxInitialSync() { @@ -207,14 +286,13 @@ func (t *TelegramClient) GetChatInfo(ctx context.Context, portal *bridgev2.Porta // TODO save emojiset? chatInfo.Members.IsFull = false + chatInfo.Members.PowerLevels = t.getGroupChatPowerLevels(fullChat.GetChats()[0]) if !portal.Metadata.(*PortalMetadata).IsSuperGroup { // Add the channel user - sender := ids.MakeUserID(id) + sender := ids.MakeChannelUserID(id) chatInfo.Members.MemberMap[sender] = bridgev2.ChatMember{ - EventSender: bridgev2.EventSender{ - SenderLogin: ids.MakeUserLoginID(id), - Sender: sender, - }, + EventSender: bridgev2.EventSender{Sender: sender}, + PowerLevel: superadminPowerLevel, } } @@ -298,3 +376,66 @@ func (t *TelegramClient) GetChatInfo(ctx context.Context, portal *bridgev2.Porta panic(fmt.Sprintf("unsupported peer type %s", peerType)) } } + +func (t *TelegramClient) getGroupChatPowerLevels(entity tg.ChatClass) *bridgev2.PowerLevelOverrides { + dbrAble, ok := entity.(interface { + GetDefaultBannedRights() (tg.ChatBannedRights, bool) + }) + if !ok { + panic(fmt.Sprintf("unsupported chat type %T", entity)) + } + dbr, ok := dbrAble.GetDefaultBannedRights() + if !ok { + dbr = tg.ChatBannedRights{ + InviteUsers: true, + ChangeInfo: true, + PinMessages: true, + SendStickers: false, + SendMessages: false, + } + } + return t.getPowerLevelOverridesFromBannedRights(entity, dbr) +} + +func (t *TelegramClient) getPowerLevelOverridesFromBannedRights(entity tg.ChatClass, dbr tg.ChatBannedRights) *bridgev2.PowerLevelOverrides { + var plo bridgev2.PowerLevelOverrides + plo.Ban = banUsersPowerLevel + plo.Kick = banUsersPowerLevel + plo.Redact = deleteMessagesPowerLevel + if dbr.InviteUsers { + plo.Invite = inviteUsersPowerLevel + } else { + plo.Invite = anyonePowerLevel + } + plo.StateDefault = superadminPowerLevel + plo.UsersDefault = anyonePowerLevel + if c, ok := entity.(*tg.Channel); (ok && !c.Megagroup) || dbr.SendMessages { + plo.EventsDefault = postMessagesPowerLevel + } else { + plo.EventsDefault = anyonePowerLevel + } + + plo.Events = map[event.Type]int{ + event.StateEncryption: 99, + event.StateTombstone: 99, + event.StatePowerLevels: 85, + event.StateHistoryVisibility: 85, + } + + if dbr.ChangeInfo { + plo.Events[event.StateRoomName] = *changeInfoPowerLevel + plo.Events[event.StateRoomAvatar] = *changeInfoPowerLevel + plo.Events[event.StateTopic] = *changeInfoPowerLevel + } + + if dbr.PinMessages { + plo.Events[event.StatePinnedEvents] = *pinMessagesPowerLevel + } else { + plo.Events[event.StatePinnedEvents] = 0 + } + + if dbr.SendStickers { + plo.Events[event.EventSticker] = *postMessagesPowerLevel + } + return &plo +} diff --git a/pkg/connector/client.go b/pkg/connector/client.go index a73cf995..842ef305 100644 --- a/pkg/connector/client.go +++ b/pkg/connector/client.go @@ -186,6 +186,9 @@ func NewTelegramClient(ctx context.Context, tc *TelegramConnector, login *bridge dispatcher.OnPinnedDialogs(func(ctx context.Context, e tg.Entities, update *tg.UpdatePinnedDialogs) error { return client.onPinnedDialogs(ctx, update) }) + dispatcher.OnChatDefaultBannedRights(func(ctx context.Context, e tg.Entities, update *tg.UpdateChatDefaultBannedRights) error { + return client.onChatDefaultBannedRights(ctx, e, update) + }) client.ScopedStore = tc.Store.GetScopedStore(telegramUserID) diff --git a/pkg/connector/sync.go b/pkg/connector/sync.go index f8238485..69e6a243 100644 --- a/pkg/connector/sync.go +++ b/pkg/connector/sync.go @@ -66,6 +66,10 @@ func (t *TelegramClient) handleDialogs(ctx context.Context, dialogs tg.ModifiedM for _, user := range dialogs.GetUsers() { users[ids.MakeUserID(user.GetID())] = user } + chats := map[int64]tg.ChatClass{} + for _, chat := range dialogs.GetChats() { + chats[chat.GetID()] = chat + } messages := map[networkid.MessageID]tg.MessageClass{} for _, message := range dialogs.GetMessages() { messages[ids.GetMessageIDFromMessage(message)] = message @@ -99,6 +103,38 @@ func (t *TelegramClient) handleDialogs(ctx context.Context, dialogs tg.ModifiedM } } + var members bridgev2.ChatMemberList + switch peer := dialog.Peer.(type) { + case *tg.PeerUser: + if users[ids.MakeUserID(peer.UserID)].(*tg.User).GetDeleted() { + log.Debug().Msg("Not syncing portal because user is deleted") + continue + } + case *tg.PeerChat: + members.PowerLevels = t.getGroupChatPowerLevels(chats[peer.ChatID]) + case *tg.PeerChannel: + members.PowerLevels = t.getGroupChatPowerLevels(chats[peer.ChannelID]) + if !portal.Metadata.(*PortalMetadata).IsSuperGroup { + // Add the channel user + sender := ids.MakeChannelUserID(peer.ChannelID) + members.MemberMap = map[networkid.UserID]bridgev2.ChatMember{ + sender: bridgev2.ChatMember{ + EventSender: bridgev2.EventSender{Sender: sender}, + PowerLevel: modPowerLevel, + }, + } + if chats[peer.ChannelID].(*tg.Channel).AdminRights.PostMessages { + members.MemberMap = map[networkid.UserID]bridgev2.ChatMember{ + t.userID: bridgev2.ChatMember{ + EventSender: t.mySender(), + PowerLevel: modPowerLevel, + }, + } + } + } + + } + if portal == nil || portal.MXID == "" { // Check what the latest message is topMessage := messages[ids.MakeMessageID(dialog.Peer, dialog.TopMessage)] @@ -127,7 +163,11 @@ func (t *TelegramClient) handleDialogs(ctx context.Context, dialogs tg.ModifiedM } t.main.Bridge.QueueRemoteEvent(t.userLogin, &simplevent.ChatResync{ - ChatInfo: &bridgev2.ChatInfo{Name: &portal.Name, UserLocal: &userLocalInfo}, + ChatInfo: &bridgev2.ChatInfo{ + Name: &portal.Name, + UserLocal: &userLocalInfo, + Members: &members, + }, EventMeta: simplevent.EventMeta{ Type: bridgev2.RemoteEventChatResync, LogContext: func(c zerolog.Context) zerolog.Context { diff --git a/pkg/connector/telegram.go b/pkg/connector/telegram.go index 55f20873..031f8da8 100644 --- a/pkg/connector/telegram.go +++ b/pkg/connector/telegram.go @@ -224,6 +224,7 @@ func (t *TelegramClient) onUpdateNewMessage(ctx context.Context, channels map[in case *tg.MessageActionChannelCreate: eventMeta.Type = bridgev2.RemoteEventChatResync eventMeta.CreatePortal = true + modLevel := 50 t.main.Bridge.QueueRemoteEvent(t.userLogin, &simplevent.ChatResync{ EventMeta: eventMeta, ChatInfo: &bridgev2.ChatInfo{ @@ -233,8 +234,12 @@ func (t *TelegramClient) onUpdateNewMessage(ctx context.Context, channels map[in t.userID: { EventSender: t.mySender(), Membership: event.MembershipJoin, + PowerLevel: &modLevel, }, }, + PowerLevels: &bridgev2.PowerLevelOverrides{ + EventsDefault: &modLevel, + }, }, }, }) @@ -817,3 +822,18 @@ func (t *TelegramClient) HandleRoomTag(ctx context.Context, msg *bridgev2.Matrix }) return err } + +func (t *TelegramClient) onChatDefaultBannedRights(ctx context.Context, entities tg.Entities, update *tg.UpdateChatDefaultBannedRights) error { + t.main.Bridge.QueueRemoteEvent(t.userLogin, &simplevent.ChatResync{ + ChatInfo: &bridgev2.ChatInfo{ + Members: &bridgev2.ChatMemberList{ + PowerLevels: t.getPowerLevelOverridesFromBannedRights(entities.Chats[0], update.DefaultBannedRights), + }, + }, + EventMeta: simplevent.EventMeta{ + Type: bridgev2.RemoteEventChatResync, + PortalKey: t.makePortalKeyFromPeer(update.Peer), + }, + }) + return nil +}