From 1d1b505b60829668fcc1dc67bf6bcf2b50af248a Mon Sep 17 00:00:00 2001 From: Malte E Date: Tue, 13 Feb 2024 21:49:47 +0100 Subject: [PATCH] update full group info --- ROADMAP.md | 12 +-- pkg/signalmeow/groups.go | 225 ++++++++++++++++++++++++++------------- portal.go | 214 +++++++++++++++++++++++++++++++++++-- 3 files changed, 361 insertions(+), 90 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 3899a4a2..2c20688d 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -52,12 +52,12 @@ * [x] Name * [x] Avatar * [x] Topic - * [ ] Membership actions - * [ ] Join - * [ ] Invite - * [ ] Request join (via invite link, requires a client that supports knocks) - * [ ] Leave - * [ ] Kick/Ban/Unban + * [x] Membership actions + * [x] Join + * [x] Invite + * [x] Request join (via invite link, requires a client that supports knocks) + * [x] Leave + * [x] Kick/Ban/Unban * [ ] Group permissions * [x] Typing notifications * [x] Read receipts diff --git a/pkg/signalmeow/groups.go b/pkg/signalmeow/groups.go index ff93789e..4147170f 100644 --- a/pkg/signalmeow/groups.go +++ b/pkg/signalmeow/groups.go @@ -81,12 +81,18 @@ type Group struct { AnnouncementsOnly bool Revision uint32 DisappearingMessagesDuration uint32 + AccessControl *GroupAccessControl + PendingMembers []*PendingMember + RequestingMembers []*RequestingMember + BannedMembers []*BannedMember //PublicKey *libsignalgo.PublicKey - //AccessControl *AccessControl - //PendingMembers []*PendingMember - //RequestingMembers []*RequestingMember //InviteLinkPassword []byte - //BannedMembers []*BannedMember +} + +type GroupAccessControl struct { + Members AccessControl + AddFromInviteLink AccessControl + Attributes AccessControl } func (group *Group) getGroupMasterKey() types.SerializedGroupMasterKey { @@ -370,31 +376,62 @@ func decryptGroup(ctx context.Context, encryptedGroup *signalpb.Group, groupMast decryptedGroup.Revision = encryptedGroup.Revision // Decrypt members - decryptedGroup.Members = make([]*GroupMember, 0) for _, member := range encryptedGroup.Members { if member == nil { continue } - encryptedUserID := libsignalgo.UUIDCiphertext(member.UserId) - userID, err := groupSecretParams.DecryptUUID(encryptedUserID) + decryptedMember, err := decryptMember(ctx, member, groupSecretParams) if err != nil { - log.Err(err).Msg("DecryptUUID UserId error") return nil, err } - encryptedProfileKey := libsignalgo.ProfileKeyCiphertext(member.ProfileKey) - profileKey, err := groupSecretParams.DecryptProfileKey(encryptedProfileKey, userID) + decryptedGroup.Members = append(decryptedGroup.Members, decryptedMember) + } + + for _, pendingMember := range encryptedGroup.PendingMembers { + if pendingMember == nil { + continue + } + decryptedPendingMember, err := decryptPendingMember(ctx, pendingMember, groupSecretParams) if err != nil { - log.Err(err).Msg("DecryptProfileKey ProfileKey error") return nil, err } - decryptedGroup.Members = append(decryptedGroup.Members, &GroupMember{ - UserID: userID, - ProfileKey: *profileKey, - Role: GroupMemberRole(member.Role), - JoinedAtRevision: member.JoinedAtRevision, + decryptedGroup.PendingMembers = append(decryptedGroup.PendingMembers, decryptedPendingMember) + } + + for _, requestingMember := range encryptedGroup.RequestingMembers { + if requestingMember == nil { + continue + } + decryptedRequestingMember, err := decryptRequestingMember(ctx, requestingMember, groupSecretParams) + if err != nil { + return nil, err + } + decryptedGroup.RequestingMembers = append(decryptedGroup.RequestingMembers, decryptedRequestingMember) + } + + for _, bannedMember := range encryptedGroup.BannedMembers { + if bannedMember == nil { + continue + } + encryptedUserID := libsignalgo.UUIDCiphertext(bannedMember.UserId) + userID, err := groupSecretParams.DecryptUUID(encryptedUserID) + if err != nil { + log.Err(err).Msg("DecryptUUID UserId error") + return nil, err + } + decryptedGroup.BannedMembers = append(decryptedGroup.BannedMembers, &BannedMember{ + UserID: userID, + Timestamp: bannedMember.Timestamp, }) } + if encryptedGroup.AccessControl != nil { + decryptedGroup.AccessControl = &GroupAccessControl{ + Members: (AccessControl)(encryptedGroup.AccessControl.Members), + Attributes: (AccessControl)(encryptedGroup.AccessControl.Attributes), + AddFromInviteLink: (AccessControl)(encryptedGroup.AccessControl.AddFromInviteLink), + } + } return decryptedGroup, nil } @@ -497,6 +534,18 @@ func (cli *Client) fetchGroupByID(ctx context.Context, gid types.GroupIdentifier return nil, fmt.Errorf("failed to store profile key: %w", err) } } + for _, pendingMember := range group.PendingMembers { + err = cli.Store.ProfileKeyStore.StoreProfileKey(ctx, pendingMember.UserID, pendingMember.ProfileKey) + if err != nil { + return nil, fmt.Errorf("failed to store profile key: %w", err) + } + } + for _, requestingMember := range group.RequestingMembers { + err = cli.Store.ProfileKeyStore.StoreProfileKey(ctx, requestingMember.UserID, requestingMember.ProfileKey) + if err != nil { + return nil, fmt.Errorf("failed to store profile key: %w", err) + } + } return group, nil } @@ -659,28 +708,15 @@ func (cli *Client) DecryptGroupChange(ctx context.Context, groupContext *signalp if addMember == nil { continue } - encryptedUserID := libsignalgo.UUIDCiphertext(addMember.Added.UserId) - userID, err := groupSecretParams.DecryptUUID(encryptedUserID) - if err != nil { - log.Err(err).Msg("DecryptUUID UserId error") - return nil, err - } - encryptedProfileKey := libsignalgo.ProfileKeyCiphertext(addMember.Added.ProfileKey) - profileKey, err := groupSecretParams.DecryptProfileKey(encryptedProfileKey, userID) + decryptedMember, err := decryptMember(ctx, addMember.Added, groupSecretParams) if err != nil { - log.Err(err).Msg("DecryptProfileKey ProfileKey error") return nil, err } decryptedGroupChange.AddMembers = append(decryptedGroupChange.AddMembers, &AddMember{ - GroupMember: GroupMember{ - UserID: userID, - ProfileKey: *profileKey, - Role: GroupMemberRole(addMember.Added.Role), - JoinedAtRevision: addMember.Added.JoinedAtRevision, - }, + GroupMember: *decryptedMember, JoinFromInviteLink: addMember.JoinFromInviteLink, }) - err = cli.Store.ProfileKeyStore.StoreProfileKey(ctx, userID, *profileKey) + err = cli.Store.ProfileKeyStore.StoreProfileKey(ctx, decryptedMember.UserID, decryptedMember.ProfileKey) if err != nil { log.Err(err).Msg("failed to store profile key") return nil, err @@ -745,35 +781,12 @@ func (cli *Client) DecryptGroupChange(ctx context.Context, groupContext *signalp continue } pendingMember := addPendingMember.Added - encryptedUserID := libsignalgo.UUIDCiphertext(pendingMember.Member.UserId) - userID, err := groupSecretParams.DecryptUUID(encryptedUserID) + decryptedPendingMember, err := decryptPendingMember(ctx, pendingMember, groupSecretParams) if err != nil { - log.Err(err).Msg("DecryptUUID UserId error") return nil, err } - encryptedProfileKey := libsignalgo.ProfileKeyCiphertext(pendingMember.Member.ProfileKey) - profileKey, err := groupSecretParams.DecryptProfileKey(encryptedProfileKey, userID) - if err != nil { - log.Err(err).Msg("DecryptProfileKey ProfileKey error") - return nil, err - } - encryptedAddedByUserID := pendingMember.AddedByUserId - addedByUserId, err := groupSecretParams.DecryptUUID(libsignalgo.UUIDCiphertext(encryptedAddedByUserID)) - if err != nil { - log.Err(err).Msg("DecryptUUID addedByUserId error") - return nil, err - } - decryptedGroupChange.AddPendingMembers = append(decryptedGroupChange.AddPendingMembers, &PendingMember{ - GroupMember: GroupMember{ - UserID: userID, - ProfileKey: *profileKey, - Role: GroupMemberRole(pendingMember.Member.Role), - JoinedAtRevision: pendingMember.Member.JoinedAtRevision, - }, - AddedByUserID: addedByUserId, - Timestamp: addPendingMember.Added.Timestamp, - }) - cli.Store.ProfileKeyStore.StoreProfileKey(ctx, userID, *profileKey) + decryptedGroupChange.AddPendingMembers = append(decryptedGroupChange.AddPendingMembers, decryptedPendingMember) + cli.Store.ProfileKeyStore.StoreProfileKey(ctx, decryptedPendingMember.UserID, decryptedPendingMember.ProfileKey) if err != nil { log.Err(err).Msg("failed to store profile key") return nil, err @@ -824,25 +837,12 @@ func (cli *Client) DecryptGroupChange(ctx context.Context, groupContext *signalp if addRequestingMember == nil { continue } - requestingMember := addRequestingMember.Added - encryptedUserID := libsignalgo.UUIDCiphertext(requestingMember.UserId) - userID, err := groupSecretParams.DecryptUUID(encryptedUserID) + decryptedRequestingMember, err := decryptRequestingMember(ctx, addRequestingMember.Added, groupSecretParams) if err != nil { - log.Err(err).Msg("DecryptUUID UserId error") return nil, err } - encryptedProfileKey := libsignalgo.ProfileKeyCiphertext(requestingMember.ProfileKey) - profileKey, err := groupSecretParams.DecryptProfileKey(encryptedProfileKey, userID) - if err != nil { - log.Err(err).Msg("DecryptProfileKey ProfileKey error") - return nil, err - } - decryptedGroupChange.AddRequestingMembers = append(decryptedGroupChange.AddRequestingMembers, &RequestingMember{ - UserID: userID, - ProfileKey: *profileKey, - Timestamp: addRequestingMember.Added.Timestamp, - }) - cli.Store.ProfileKeyStore.StoreProfileKey(ctx, userID, *profileKey) + decryptedGroupChange.AddRequestingMembers = append(decryptedGroupChange.AddRequestingMembers, decryptedRequestingMember) + cli.Store.ProfileKeyStore.StoreProfileKey(ctx, decryptedRequestingMember.UserID, decryptedRequestingMember.ProfileKey) if err != nil { log.Err(err).Msg("failed to store profile key") return nil, err @@ -891,7 +891,7 @@ func (cli *Client) DecryptGroupChange(ctx context.Context, groupContext *signalp } decryptedGroupChange.AddBannedMembers = append(decryptedGroupChange.AddBannedMembers, &BannedMember{ UserID: userID, - Timestamp: addBannedMember.Added.Timestamp, + Timestamp: bannedMember.Timestamp, }) } @@ -934,3 +934,78 @@ func (cli *Client) DecryptGroupChange(ctx context.Context, groupContext *signalp return decryptedGroupChange, nil } + +func decryptMember(ctx context.Context, member *signalpb.Member, groupSecretParams libsignalgo.GroupSecretParams) (*GroupMember, error) { + log := zerolog.Ctx(ctx) + encryptedUserID := libsignalgo.UUIDCiphertext(member.UserId) + userID, err := groupSecretParams.DecryptUUID(encryptedUserID) + if err != nil { + log.Err(err).Msg("DecryptUUID UserId error") + return nil, err + } + encryptedProfileKey := libsignalgo.ProfileKeyCiphertext(member.ProfileKey) + profileKey, err := groupSecretParams.DecryptProfileKey(encryptedProfileKey, userID) + if err != nil { + log.Err(err).Msg("DecryptProfileKey ProfileKey error") + return nil, err + } + return &GroupMember{ + UserID: userID, + ProfileKey: *profileKey, + Role: GroupMemberRole(member.Role), + JoinedAtRevision: member.JoinedAtRevision, + }, err +} + +func decryptPendingMember(ctx context.Context, pendingMember *signalpb.PendingMember, groupSecretParams libsignalgo.GroupSecretParams) (*PendingMember, error) { + log := zerolog.Ctx(ctx) + encryptedUserID := libsignalgo.UUIDCiphertext(pendingMember.Member.UserId) + userID, err := groupSecretParams.DecryptUUID(encryptedUserID) + if err != nil { + log.Err(err).Msg("DecryptUUID UserId error") + return nil, err + } + encryptedProfileKey := libsignalgo.ProfileKeyCiphertext(pendingMember.Member.ProfileKey) + profileKey, err := groupSecretParams.DecryptProfileKey(encryptedProfileKey, userID) + if err != nil { + log.Err(err).Msg("DecryptProfileKey ProfileKey error") + return nil, err + } + encryptedAddedByUserID := pendingMember.AddedByUserId + addedByUserId, err := groupSecretParams.DecryptUUID(libsignalgo.UUIDCiphertext(encryptedAddedByUserID)) + if err != nil { + log.Err(err).Msg("DecryptUUID addedByUserId error") + return nil, err + } + return &PendingMember{ + GroupMember: GroupMember{ + UserID: userID, + ProfileKey: *profileKey, + Role: GroupMemberRole(pendingMember.Member.Role), + JoinedAtRevision: pendingMember.Member.JoinedAtRevision, + }, + AddedByUserID: addedByUserId, + Timestamp: pendingMember.Timestamp, + }, nil +} + +func decryptRequestingMember(ctx context.Context, requestingMember *signalpb.RequestingMember, groupSecretParams libsignalgo.GroupSecretParams) (*RequestingMember, error) { + log := zerolog.Ctx(ctx) + encryptedUserID := libsignalgo.UUIDCiphertext(requestingMember.UserId) + userID, err := groupSecretParams.DecryptUUID(encryptedUserID) + if err != nil { + log.Err(err).Msg("DecryptUUID UserId error") + return nil, err + } + encryptedProfileKey := libsignalgo.ProfileKeyCiphertext(requestingMember.ProfileKey) + profileKey, err := groupSecretParams.DecryptProfileKey(encryptedProfileKey, userID) + if err != nil { + log.Err(err).Msg("DecryptProfileKey ProfileKey error") + return nil, err + } + return &RequestingMember{ + UserID: userID, + ProfileKey: *profileKey, + Timestamp: requestingMember.Timestamp, + }, nil +} diff --git a/portal.go b/portal.go index 925c2ae4..ccb2801b 100644 --- a/portal.go +++ b/portal.go @@ -1725,7 +1725,9 @@ func (portal *Portal) CreateMatrixRoom(ctx context.Context, user *User, groupRev portal.log.Error().Msg("Didn't get group info after updating portal") return errors.New("failed to get group info") } - invite = append(invite, portal.SyncParticipants(ctx, user, groupInfo)...) + for member := range portal.SyncParticipants(ctx, user, groupInfo) { + invite = append(invite, member) + } } req := &mautrix.ReqCreateRoom{ @@ -1798,7 +1800,8 @@ func (portal *Portal) UpdateInfo(ctx context.Context, source *User, groupInfo *s } groupInfo = portal.UpdateGroupInfo(ctx, source, groupInfo, revision, false) if groupInfo != nil { - portal.SyncParticipants(ctx, source, groupInfo) + members := portal.SyncParticipants(ctx, source, groupInfo) + portal.updatePowerLevelsAndJoinRule(ctx, groupInfo, members) } } @@ -1838,6 +1841,70 @@ func (portal *Portal) UpdateDMInfo(ctx context.Context, forceSave bool) { } } +func (portal *Portal) updatePowerLevelsAndJoinRule(ctx context.Context, info *signalmeow.Group, members map[id.UserID]int) { + log := zerolog.Ctx(ctx).With(). + Str("function", "updatePowerLevelsAndJoinRule"). + Logger() + log.Trace().Msg("Updating power levels and join rule") + state, err := portal.MainIntent().State(ctx, portal.MXID) + if err != nil { + log.Err(err).Msg("Failed to get room state") + return + } + joinRule := state[event.StateJoinRules][""].Content.AsJoinRules().JoinRule + newJoinRule := event.JoinRuleInvite + levels := state[event.StatePowerLevels][""].Content.AsPowerLevels() + botLevel := levels.GetUserLevel(portal.MainIntent().UserID) + changed := false + for mxid, level := range members { + if levels.GetUserLevel(mxid) < botLevel { + changed = levels.EnsureUserLevel(mxid, level) || changed + } + } + newEventsDefault := 0 + if info.AnnouncementsOnly { + newEventsDefault = 50 + } + if newEventsDefault != levels.EventsDefault { + levels.EventsDefault = newEventsDefault + changed = true + } + if info.AccessControl != nil { + level := 0 + if info.AccessControl.Attributes == signalmeow.AccessControl_ADMINISTRATOR { + level = 50 + } + changed = levels.EnsureEventLevel(event.StateRoomName, level) || changed + changed = levels.EnsureEventLevel(event.StateTopic, level) || changed + changed = levels.EnsureEventLevel(event.StateRoomAvatar, level) || changed + level = 0 + if info.AccessControl.Members == signalmeow.AccessControl_ADMINISTRATOR { + level = 50 + } + if levels.InvitePtr == nil || *levels.InvitePtr != level { + levels.InvitePtr = &level + changed = true + } + if info.AccessControl.AddFromInviteLink == signalmeow.AccessControl_ADMINISTRATOR { + newJoinRule = event.JoinRuleKnock + } else if info.AccessControl.AddFromInviteLink == signalmeow.AccessControl_ANY && (portal.bridge.Config.Bridge.PublicPortals || joinRule == event.JoinRulePublic) { + newJoinRule = event.JoinRulePublic + } + } + if newJoinRule != joinRule { + _, err = portal.MainIntent().SendStateEvent(ctx, portal.MXID, event.StateJoinRules, "", &event.JoinRulesEventContent{JoinRule: joinRule}) + if err != nil { + log.Err(err).Msg("Failed to set join rule") + } + } + if changed { + _, err = portal.MainIntent().SetPowerLevels(ctx, portal.MXID, levels) + if err != nil { + log.Err(err).Msg("Failed to set power levels") + } + } +} + func (portal *Portal) UpdateGroupInfo(ctx context.Context, source *User, info *signalmeow.Group, revision uint32, forceFetch bool) *signalmeow.Group { logWith := zerolog.Ctx(ctx).With(). Str("function", "UpdateGroupInfo"). @@ -2040,13 +2107,18 @@ func (portal *Portal) updateAvatarInRoom(ctx context.Context, sender *Puppet) { } } -func (portal *Portal) SyncParticipants(ctx context.Context, source *User, info *signalmeow.Group) []id.UserID { +func (portal *Portal) SyncParticipants(ctx context.Context, source *User, info *signalmeow.Group) map[id.UserID]int { log := zerolog.Ctx(ctx) - userIDs := make([]id.UserID, 0, len(info.Members)) - for _, member := range info.Members { - if member.UserID == source.SignalID { - continue + userIDs := make(map[id.UserID]int) + currentMembers := make(map[id.UserID]event.Membership) + if portal.MXID != "" { + memberEventData, _ := portal.MainIntent().Members(ctx, portal.MXID, mautrix.ReqMembers{}) + for _, evt := range memberEventData.Chunk { + evt.Content.ParseRaw(event.StateMember) + currentMembers[id.UserID(*evt.StateKey)] = evt.Content.AsMember().Membership } + } + for _, member := range info.Members { puppet := portal.bridge.GetPuppetBySignalID(member.UserID) if puppet == nil { log.Warn().Stringer("signal_user_id", member.UserID).Msg("Couldn't get puppet for group member") @@ -2054,15 +2126,139 @@ func (portal *Portal) SyncParticipants(ctx context.Context, source *User, info * } puppet.UpdateInfo(ctx, source) intent := puppet.IntentFor(portal) - userIDs = append(userIDs, intent.UserID) + if member.UserID != source.SignalID && portal.MXID != "" { + userIDs[intent.UserID] = ((int)(member.Role) >> 1) * 50 + } if portal.MXID != "" { err := intent.EnsureJoined(ctx, portal.MXID) if err != nil { log.Err(err).Stringer("signal_user_id", member.UserID).Msg("Failed to ensure user is joined to portal") } + if puppet.customIntent == nil { + user := portal.bridge.GetUserBySignalID(member.UserID) + if user != nil { + user.ensureInvited(ctx, intent, portal.MXID, false) + userIDs[user.MXID] = ((int)(member.Role) >> 1) * 50 + delete(currentMembers, user.MXID) + } + } + } + delete(currentMembers, intent.UserID) + } + if portal.MXID == "" { + return userIDs + } + for _, pendingMember := range info.PendingMembers { + puppet := portal.bridge.GetPuppetBySignalID(pendingMember.UserID) + if puppet == nil { + log.Warn().Stringer("signal_user_id", pendingMember.UserID).Msg("Couldn't get puppet for group member") + continue + } + mxid := puppet.IntentFor(portal).UserID + membership := currentMembers[mxid] + var err error + if membership == event.MembershipJoin || membership == event.MembershipBan { + _, err = portal.MainIntent().SendCustomMembershipEvent(ctx, portal.MXID, mxid, event.MembershipLeave, "") + } + if membership != event.MembershipInvite { + _, err = portal.MainIntent().SendCustomMembershipEvent(ctx, portal.MXID, mxid, event.MembershipInvite, "") + } + if err != nil { + log.Warn().Stringer("mxid", mxid).Msg("Couldn't change membership to invite") + } + userIDs[mxid] = ((int)(pendingMember.Role) >> 1) * 50 + delete(currentMembers, mxid) + if puppet.customIntent == nil { + user := portal.bridge.GetUserBySignalID(pendingMember.UserID) + if user == nil { + continue + } + mxid = user.MXID + membership := currentMembers[mxid] + err = nil + if membership == event.MembershipJoin || membership == event.MembershipBan { + _, err = portal.MainIntent().SendCustomMembershipEvent(ctx, portal.MXID, mxid, event.MembershipLeave, "") + } + if membership != event.MembershipInvite { + _, err = portal.MainIntent().SendCustomMembershipEvent(ctx, portal.MXID, mxid, event.MembershipInvite, "") + } + if err != nil { + log.Warn().Stringer("mxid", mxid).Msg("Couldn't change membership to invite") + } + userIDs[mxid] = ((int)(pendingMember.Role) >> 1) * 50 + delete(currentMembers, mxid) + } + } + for _, requestingMember := range info.RequestingMembers { + puppet := portal.bridge.GetPuppetBySignalID(requestingMember.UserID) + if puppet == nil { + log.Warn().Stringer("signal_user_id", requestingMember.UserID).Msg("Couldn't get puppet for group member") + continue + } + mxid := puppet.IntentFor(portal).UserID + membership := currentMembers[mxid] + var err error + if membership == event.MembershipJoin || membership == event.MembershipBan { + _, err = portal.MainIntent().SendCustomMembershipEvent(ctx, portal.MXID, mxid, event.MembershipLeave, "") + } + if membership != event.MembershipKnock { + _, err = puppet.IntentFor(portal).SendCustomMembershipEvent(ctx, portal.MXID, mxid, event.MembershipKnock, "") + } + if err != nil { + log.Warn().Stringer("mxid", mxid).Msg("Couldn't change membership to knock") + } + delete(currentMembers, mxid) + } + for _, bannedMember := range info.BannedMembers { + puppet := portal.bridge.GetPuppetBySignalID(bannedMember.UserID) + if puppet == nil { + log.Warn().Stringer("signal_user_id", bannedMember.UserID).Msg("Couldn't get puppet for group member") + continue + } + mxid := puppet.IntentFor(portal).UserID + var err error + if currentMembers[mxid] != event.MembershipBan { + _, err = portal.MainIntent().SendCustomMembershipEvent(ctx, portal.MXID, mxid, event.MembershipBan, "") + } + if err != nil { + log.Warn().Stringer("mxid", mxid).Msg("Couldn't change membership to ban") + } + delete(currentMembers, mxid) + if puppet.customIntent == nil { + user := portal.bridge.GetUserBySignalID(bannedMember.UserID) + if user == nil { + continue + } + mxid = user.MXID + err = nil + if currentMembers[mxid] != event.MembershipBan { + _, err = portal.MainIntent().SendCustomMembershipEvent(ctx, portal.MXID, mxid, event.MembershipBan, "") + } + if err != nil { + log.Warn().Stringer("mxid", mxid).Msg("Couldn't change membership to ban") + } + delete(currentMembers, mxid) + } + } + for mxid, _ := range currentMembers { + user := portal.bridge.GetUserByMXIDIfExists(mxid) + if user != nil { + if user.IsLoggedIn() { + _, err := portal.MainIntent().SendCustomMembershipEvent(ctx, portal.MXID, mxid, event.MembershipLeave, "") + if err != nil { + log.Warn().Stringer("mxid", mxid).Msg("Couldn't change membership to leave") + } + } + } else { + puppet := portal.bridge.GetPuppetByMXID(mxid) + if puppet != nil { + _, err := portal.MainIntent().SendCustomMembershipEvent(ctx, portal.MXID, mxid, event.MembershipLeave, "") + if err != nil { + log.Warn().Stringer("mxid", mxid).Msg("Couldn't change membership to leave") + } + } } } - // TODO kick extra members on Matrix, handle pending and requesting participants on Signal return userIDs }