Skip to content

Commit

Permalink
v2: bridge all member states and permissions
Browse files Browse the repository at this point in the history
  • Loading branch information
tulir committed Jun 28, 2024
1 parent 9121a0c commit 7b53850
Show file tree
Hide file tree
Showing 4 changed files with 230 additions and 14 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ require (
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8
golang.org/x/net v0.26.0
google.golang.org/protobuf v1.34.2
maunium.net/go/mautrix v0.19.0-beta.1.0.20240627155110-35f8d837b5f5
maunium.net/go/mautrix v0.19.0-beta.1.0.20240628154312-d7251a4c6950
nhooyr.io/websocket v1.8.11
)

Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ 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.20240627155110-35f8d837b5f5 h1:dJBtUNpc9G0IKq1uRU6kZetBmgMtcmh1F/sMiyDpbDc=
maunium.net/go/mautrix v0.19.0-beta.1.0.20240627155110-35f8d837b5f5/go.mod h1:eu/C1dTewrW7yiFNiCKGm4zuWJANyt7zPjaY5g3f3r4=
maunium.net/go/mautrix v0.19.0-beta.1.0.20240628154312-d7251a4c6950 h1:A43PQ5ltou4KhcCMxZz1gb3QXrhOjVKweYDW5YQ/Gok=
maunium.net/go/mautrix v0.19.0-beta.1.0.20240628154312-d7251a4c6950/go.mod h1:eu/C1dTewrW7yiFNiCKGm4zuWJANyt7zPjaY5g3f3r4=
nhooyr.io/websocket v1.8.11 h1:f/qXNc2/3DpoSZkHt1DQu6rj4zGC8JmkkLkWss0MgN0=
nhooyr.io/websocket v1.8.11/go.mod h1:rN9OFWIUwuxg4fR5tELlYC04bXYowCP9GX47ivo2l+c=
20 changes: 17 additions & 3 deletions pkg/connector/chatinfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"github.com/rs/zerolog"
"maunium.net/go/mautrix/bridgev2"
"maunium.net/go/mautrix/bridgev2/networkid"
"maunium.net/go/mautrix/event"

"go.mau.fi/mautrix-signal/pkg/libsignalgo"
"go.mau.fi/mautrix-signal/pkg/signalmeow/types"
Expand Down Expand Up @@ -195,7 +196,16 @@ func (s *SignalClient) makeCreateDMResponse(recipient *types.Recipient) *bridgev
isSpace := false
name := ""
topic := PrivateChatTopic
members := []networkid.UserID{makeUserID(s.Client.Store.ACI)}
members := &bridgev2.ChatMemberList{
IsFull: true,
Members: []bridgev2.ChatMember{
{
EventSender: s.makeEventSender(s.Client.Store.ACI),
Membership: event.MembershipJoin,
PowerLevel: moderatorPL,
},
},
}
if s.Main.Config.NumberInTopic && recipient.E164 != "" {
topic = fmt.Sprintf("%s with %s", PrivateChatTopic, recipient.E164)
}
Expand All @@ -215,13 +225,17 @@ func (s *SignalClient) makeCreateDMResponse(recipient *types.Recipient) *bridgev
}
} else {
// The other user is only present if their ACI is known
members = append(members, makeUserID(recipient.ACI))
members.Members = append(members.Members, bridgev2.ChatMember{
EventSender: s.makeEventSender(recipient.ACI),
Membership: event.MembershipJoin,
PowerLevel: moderatorPL,
})
}
serviceID = libsignalgo.NewACIServiceID(recipient.ACI)
}
return &bridgev2.CreateChatResponse{
PortalID: s.makeDMPortalKey(serviceID),
PortalInfo: &bridgev2.PortalInfo{
PortalInfo: &bridgev2.ChatInfo{
Name: &name,
Avatar: avatar,
Topic: &topic,
Expand Down
218 changes: 210 additions & 8 deletions pkg/connector/groupinfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,24 +23,128 @@ import (
"github.com/rs/zerolog"
"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-signal/pkg/libsignalgo"
"go.mau.fi/mautrix-signal/pkg/signalmeow"
"go.mau.fi/mautrix-signal/pkg/signalmeow/types"
)

func (s *SignalClient) getGroupInfo(ctx context.Context, groupID types.GroupIdentifier, minRevision uint32) (*bridgev2.PortalInfo, error) {
var defaultPL = 0
var moderatorPL = 50

func roleToPL(role signalmeow.GroupMemberRole) int {
switch role {
case signalmeow.GroupMember_ADMINISTRATOR:
return moderatorPL
case signalmeow.GroupMember_DEFAULT:
fallthrough
default:
return defaultPL
}
}

func applyAnnouncementsOnly(plc *bridgev2.PowerLevelChanges, announcementsOnly bool) {
if announcementsOnly {
plc.EventsDefault = &moderatorPL
} else {
plc.EventsDefault = &defaultPL
}
}

func applyAttributesAccess(plc *bridgev2.PowerLevelChanges, attributeAccess signalmeow.AccessControl) {
attributePL := defaultPL
if attributeAccess == signalmeow.AccessControl_ADMINISTRATOR {
attributePL = moderatorPL
}
plc.Events[event.StateRoomName] = attributePL
plc.Events[event.StateRoomAvatar] = attributePL
plc.Events[event.StateTopic] = attributePL
}

func applyMembersAccess(plc *bridgev2.PowerLevelChanges, memberAccess signalmeow.AccessControl) {
if memberAccess == signalmeow.AccessControl_ADMINISTRATOR {
plc.Invite = &moderatorPL
} else {
plc.Invite = &defaultPL
}
}

func inviteLinkToJoinRule(inviteLinkAccess signalmeow.AccessControl) event.JoinRule {
switch inviteLinkAccess {
case signalmeow.AccessControl_UNSATISFIABLE:
return event.JoinRuleInvite
case signalmeow.AccessControl_ADMINISTRATOR:
return event.JoinRuleKnock
case signalmeow.AccessControl_ANY:
// TODO allow public portals?
publicPortals := false
if publicPortals {
return event.JoinRulePublic
} else {
return event.JoinRuleKnock
}
default:
return event.JoinRuleInvite
}
}

func (s *SignalClient) getGroupInfo(ctx context.Context, groupID types.GroupIdentifier, minRevision uint32) (*bridgev2.ChatInfo, error) {
groupInfo, err := s.Client.RetrieveGroupByID(ctx, groupID, minRevision)
if err != nil {
return nil, err
}
isDM := false
isSpace := false
members := make([]networkid.UserID, len(groupInfo.Members))
members := &bridgev2.ChatMemberList{
IsFull: true,
Members: make([]bridgev2.ChatMember, len(groupInfo.Members), len(groupInfo.Members)+len(groupInfo.PendingMembers)+len(groupInfo.RequestingMembers)+len(groupInfo.BannedMembers)),
PowerLevels: &bridgev2.PowerLevelChanges{
Events: map[event.Type]int{
event.StatePowerLevels: moderatorPL,
},
},
}
applyAnnouncementsOnly(members.PowerLevels, groupInfo.AnnouncementsOnly)
joinRule := event.JoinRuleInvite
if groupInfo.AccessControl != nil {
applyAttributesAccess(members.PowerLevels, groupInfo.AccessControl.Attributes)
applyMembersAccess(members.PowerLevels, groupInfo.AccessControl.Members)
joinRule = inviteLinkToJoinRule(groupInfo.AccessControl.AddFromInviteLink)
}
for i, member := range groupInfo.Members {
members[i] = makeUserID(member.ACI)
members.Members[i] = bridgev2.ChatMember{
EventSender: s.makeEventSender(member.ACI),
PowerLevel: roleToPL(member.Role),
Membership: event.MembershipJoin,
}
}
for _, member := range groupInfo.PendingMembers {
if member.ServiceID.Type != libsignalgo.ServiceIDTypeACI {
continue
}
members.Members = append(members.Members, bridgev2.ChatMember{
EventSender: s.makeEventSender(member.ServiceID.UUID),
PowerLevel: roleToPL(member.Role),
Membership: event.MembershipInvite,
})
}
for _, member := range groupInfo.RequestingMembers {
members.Members = append(members.Members, bridgev2.ChatMember{
EventSender: s.makeEventSender(member.ACI),
Membership: event.MembershipKnock,
})
}
for _, member := range groupInfo.BannedMembers {
if member.ServiceID.Type != libsignalgo.ServiceIDTypeACI {
continue
}
members.Members = append(members.Members, bridgev2.ChatMember{
EventSender: s.makeEventSender(member.ServiceID.UUID),
Membership: event.MembershipBan,
})
}
return &bridgev2.PortalInfo{
return &bridgev2.ChatInfo{
Name: &groupInfo.Title,
Topic: &groupInfo.Description,
Avatar: s.makeGroupAvatar(groupInfo),
Expand All @@ -51,6 +155,7 @@ func (s *SignalClient) getGroupInfo(ctx context.Context, groupID types.GroupIden
Members: members,
IsDirectChat: &isDM,
IsSpace: &isSpace,
JoinRule: &event.JoinRulesEventContent{JoinRule: joinRule},
ExtraUpdates: makeRevisionUpdater(groupInfo.Revision),
}, nil
}
Expand Down Expand Up @@ -82,20 +187,117 @@ func makeRevisionUpdater(rev uint32) func(ctx context.Context, portal *bridgev2.

func (s *SignalClient) groupChangeToChatInfoChange(ctx context.Context, rev uint32, groupChange *signalmeow.GroupChange) *bridgev2.ChatInfoChange {
ic := &bridgev2.ChatInfoChange{
PortalInfo: &bridgev2.PortalInfo{
ChatInfo: &bridgev2.ChatInfo{
ExtraUpdates: makeRevisionUpdater(rev),
Name: groupChange.ModifyTitle,
Topic: groupChange.ModifyDescription,
Avatar: s.makeGroupAvatar(groupChange),
},
}
if groupChange.ModifyDisappearingMessagesDuration != nil {
ic.PortalInfo.Disappear = &database.DisappearingSetting{
ic.ChatInfo.Disappear = &database.DisappearingSetting{
Type: database.DisappearingTypeAfterRead,
Timer: time.Duration(*groupChange.ModifyDisappearingMessagesDuration) * time.Second,
}
}
// TODO handle member/permission/etc changes

var pls *bridgev2.PowerLevelChanges
if groupChange.ModifyAnnouncementsOnly != nil ||
groupChange.ModifyAttributesAccess != nil ||
groupChange.ModifyMemberAccess != nil {
pls = &bridgev2.PowerLevelChanges{Events: make(map[event.Type]int)}
if groupChange.ModifyAnnouncementsOnly != nil {
applyAnnouncementsOnly(pls, *groupChange.ModifyAnnouncementsOnly)
}
if groupChange.ModifyAttributesAccess != nil {
applyAttributesAccess(pls, *groupChange.ModifyAttributesAccess)
}
if groupChange.ModifyMemberAccess != nil {
applyMembersAccess(pls, *groupChange.ModifyMemberAccess)
}
}
if groupChange.ModifyAddFromInviteLinkAccess != nil {
ic.ChatInfo.JoinRule = &event.JoinRulesEventContent{
JoinRule: inviteLinkToJoinRule(*groupChange.ModifyAddFromInviteLinkAccess),
}
}
var mc []bridgev2.ChatMember
for _, member := range groupChange.AddMembers {
mc = append(mc, bridgev2.ChatMember{
EventSender: s.makeEventSender(member.ACI),
PowerLevel: roleToPL(member.Role),
Membership: event.MembershipJoin,
})
}
for _, member := range groupChange.ModifyMemberRoles {
mc = append(mc, bridgev2.ChatMember{
EventSender: s.makeEventSender(member.ACI),
PowerLevel: roleToPL(member.Role),
Membership: event.MembershipJoin,
})
}
for _, memberACI := range groupChange.DeleteMembers {
mc = append(mc, bridgev2.ChatMember{
EventSender: s.makeEventSender(*memberACI),
Membership: event.MembershipLeave,
PrevMembership: event.MembershipJoin,
})
}
for _, member := range groupChange.AddPendingMembers {
if member.ServiceID.Type != libsignalgo.ServiceIDTypeACI {
continue
}
mc = append(mc, bridgev2.ChatMember{
EventSender: s.makeEventSender(member.ServiceID.UUID),
PowerLevel: roleToPL(member.Role),
Membership: event.MembershipInvite,
})
}
for _, memberServiceID := range groupChange.DeletePendingMembers {
if memberServiceID.Type != libsignalgo.ServiceIDTypeACI {
continue
}
mc = append(mc, bridgev2.ChatMember{
EventSender: s.makeEventSender(memberServiceID.UUID),
Membership: event.MembershipLeave,
PrevMembership: event.MembershipInvite,
})
}
for _, member := range groupChange.AddRequestingMembers {
mc = append(mc, bridgev2.ChatMember{
EventSender: s.makeEventSender(member.ACI),
Membership: event.MembershipKnock,
})
}
for _, memberACI := range groupChange.DeleteRequestingMembers {
mc = append(mc, bridgev2.ChatMember{
EventSender: s.makeEventSender(*memberACI),
Membership: event.MembershipLeave,
PrevMembership: event.MembershipKnock,
})
}
for _, member := range groupChange.AddBannedMembers {
if member.ServiceID.Type != libsignalgo.ServiceIDTypeACI {
continue
}
mc = append(mc, bridgev2.ChatMember{
EventSender: s.makeEventSender(member.ServiceID.UUID),
Membership: event.MembershipBan,
})
}
for _, memberServiceID := range groupChange.DeleteBannedMembers {
if memberServiceID.Type != libsignalgo.ServiceIDTypeACI {
continue
}
mc = append(mc, bridgev2.ChatMember{
EventSender: s.makeEventSender(memberServiceID.UUID),
Membership: event.MembershipLeave,
PrevMembership: event.MembershipBan,
})
}
if len(mc) > 0 || pls != nil {
ic.MemberChanges = &bridgev2.ChatMemberList{Members: mc, PowerLevels: pls}
}
return ic
}

Expand Down

0 comments on commit 7b53850

Please sign in to comment.