mirror of
https://github.com/mautrix/signal.git
synced 2026-11-11 00:29:16 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a96f214609 |
||
|
|
561064e8f5 |
||
|
|
03c8d29ae7 |
||
|
|
59a4ff2afc |
||
|
|
27e3fc2582 |
||
|
|
15674cc755 |
||
|
|
fa622888a9 |
||
|
|
d59791e7d3 |
||
|
|
cd8bc557d6 |
||
|
|
5c740406db |
||
|
|
c6abc27db6 |
||
|
|
0a4f1d81c8 |
||
|
|
74b4c70ee4 |
||
|
|
8cb696dcfc |
||
|
|
d451962ba2 |
||
|
|
c827c97adb |
||
|
|
c820321548 |
||
|
|
34b4187e07 |
||
|
|
a630835d70 |
19 changed files with 292 additions and 33 deletions
1
.github/workflows/go.yml
vendored
1
.github/workflows/go.yml
vendored
|
|
@ -30,6 +30,7 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
go install golang.org/x/tools/cmd/goimports@latest
|
go install golang.org/x/tools/cmd/goimports@latest
|
||||||
go install honnef.co/go/tools/cmd/staticcheck@latest
|
go install honnef.co/go/tools/cmd/staticcheck@latest
|
||||||
|
go install github.com/beeper/pre-commit-go/bridgelint@latest
|
||||||
export PATH="$HOME/go/bin:$PATH"
|
export PATH="$HOME/go/bin:$PATH"
|
||||||
|
|
||||||
- name: Run pre-commit
|
- name: Run pre-commit
|
||||||
|
|
|
||||||
|
|
@ -27,4 +27,5 @@ repos:
|
||||||
- id: zerolog-ban-msgf
|
- id: zerolog-ban-msgf
|
||||||
- id: zerolog-use-stringer
|
- id: zerolog-use-stringer
|
||||||
- id: bridgelint
|
- id: bridgelint
|
||||||
|
language: system
|
||||||
verbose: true
|
verbose: true
|
||||||
|
|
|
||||||
|
|
@ -125,9 +125,10 @@ func (s *SignalClient) contactToUserInfo(ctx context.Context, contact *types.Rec
|
||||||
}
|
}
|
||||||
name := s.Main.Config.FormatDisplayname(contact, username)
|
name := s.Main.Config.FormatDisplayname(contact, username)
|
||||||
ui.Name = &name
|
ui.Name = &name
|
||||||
if s.Main.Config.UseContactAvatars && contact.ContactAvatar.Hash != "" {
|
contactAvatarID := networkid.AvatarID("hash:" + contact.ContactAvatar.Hash)
|
||||||
|
if s.Main.Config.UseContactAvatars && contact.ContactAvatar.Hash != "" && s.canUseContactAvatar(ctx, contact, contactAvatarID) {
|
||||||
ui.Avatar = &bridgev2.Avatar{
|
ui.Avatar = &bridgev2.Avatar{
|
||||||
ID: networkid.AvatarID("hash:" + contact.ContactAvatar.Hash),
|
ID: contactAvatarID,
|
||||||
Get: func(ctx context.Context) ([]byte, error) {
|
Get: func(ctx context.Context) ([]byte, error) {
|
||||||
if contact.ContactAvatar.Image == nil {
|
if contact.ContactAvatar.Image == nil {
|
||||||
return nil, fmt.Errorf("contact avatar not available")
|
return nil, fmt.Errorf("contact avatar not available")
|
||||||
|
|
@ -172,6 +173,20 @@ func (s *SignalClient) contactToUserInfo(ctx context.Context, contact *types.Rec
|
||||||
return ui, nil
|
return ui, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SignalClient) canUseContactAvatar(ctx context.Context, contact *types.Recipient, avatarID networkid.AvatarID) bool {
|
||||||
|
if contact.ContactAvatar.Image != nil {
|
||||||
|
return true
|
||||||
|
} else if contact.ACI == uuid.Nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
ghost, err := s.Main.Bridge.GetExistingGhostByID(ctx, signalid.MakeUserID(contact.ACI))
|
||||||
|
if err != nil {
|
||||||
|
zerolog.Ctx(ctx).Err(err).Msg("Failed to get ghost to check contact avatar")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return ghost != nil && ghost.AvatarID == avatarID && ghost.AvatarMXC != "" && ghost.AvatarSet
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SignalConnector) ValidateUserID(id networkid.UserID) bool {
|
func (s *SignalConnector) ValidateUserID(id networkid.UserID) bool {
|
||||||
_, err := signalid.ParseUserIDAsServiceID(id)
|
_, err := signalid.ParseUserIDAsServiceID(id)
|
||||||
return err == nil
|
return err == nil
|
||||||
|
|
@ -445,11 +460,15 @@ func (s *SignalClient) GetContactList(ctx context.Context) ([]*bridgev2.ResolveI
|
||||||
}
|
}
|
||||||
resp := make([]*bridgev2.ResolveIdentifierResponse, len(recipients))
|
resp := make([]*bridgev2.ResolveIdentifierResponse, len(recipients))
|
||||||
for i, recipient := range recipients {
|
for i, recipient := range recipients {
|
||||||
recipientResp := &bridgev2.ResolveIdentifierResponse{
|
if err = ctx.Err(); err != nil {
|
||||||
Chat: s.makeCreateDMResponse(ctx, recipient, nil),
|
return nil, err
|
||||||
}
|
}
|
||||||
|
recipientResp := &bridgev2.ResolveIdentifierResponse{}
|
||||||
var username string
|
var username string
|
||||||
if recipient.ACI != uuid.Nil {
|
if recipient.ACI != uuid.Nil {
|
||||||
|
recipientResp.Chat = &bridgev2.CreateChatResponse{
|
||||||
|
PortalKey: s.makeDMPortalKey(libsignalgo.NewACIServiceID(recipient.ACI)),
|
||||||
|
}
|
||||||
recipientResp.UserID = signalid.MakeUserID(recipient.ACI)
|
recipientResp.UserID = signalid.MakeUserID(recipient.ACI)
|
||||||
ghost, err := s.Main.Bridge.GetGhostByID(ctx, recipientResp.UserID)
|
ghost, err := s.Main.Bridge.GetGhostByID(ctx, recipientResp.UserID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -458,7 +477,11 @@ func (s *SignalClient) GetContactList(ctx context.Context) ([]*bridgev2.ResolveI
|
||||||
recipientResp.Ghost = ghost
|
recipientResp.Ghost = ghost
|
||||||
username = ghost.Metadata.(*signalid.GhostMetadata).Username
|
username = ghost.Metadata.(*signalid.GhostMetadata).Username
|
||||||
} else {
|
} else {
|
||||||
recipientResp.UserID = signalid.MakeUserIDFromServiceID(libsignalgo.NewPNIServiceID(recipient.PNI))
|
pniServiceID := libsignalgo.NewPNIServiceID(recipient.PNI)
|
||||||
|
recipientResp.Chat = &bridgev2.CreateChatResponse{
|
||||||
|
PortalKey: s.makeDMPortalKey(pniServiceID),
|
||||||
|
}
|
||||||
|
recipientResp.UserID = signalid.MakeUserIDFromServiceID(pniServiceID)
|
||||||
}
|
}
|
||||||
recipientResp.UserInfo, err = s.contactToUserInfo(ctx, recipient, username, false)
|
recipientResp.UserInfo, err = s.contactToUserInfo(ctx, recipient, username, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -221,12 +221,18 @@ func (s *SignalClient) Connect(ctx context.Context) {
|
||||||
if s.Client == nil {
|
if s.Client == nil {
|
||||||
s.UserLogin.BridgeState.Send(status.BridgeState{StateEvent: status.StateBadCredentials, Error: "signal-not-logged-in", UserAction: status.UserActionRelogin})
|
s.UserLogin.BridgeState.Send(status.BridgeState{StateEvent: status.StateBadCredentials, Error: "signal-not-logged-in", UserAction: status.UserActionRelogin})
|
||||||
return
|
return
|
||||||
|
} else if !s.Client.IsLoggedIn() {
|
||||||
|
s.sendLoggedOutState(nil)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
s.updateRemoteProfile(ctx, false)
|
s.updateRemoteProfile(ctx, false)
|
||||||
s.tryConnect(ctx, 0, true)
|
s.tryConnect(ctx, 0, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SignalClient) ConnectBackground(ctx context.Context, _ *bridgev2.ConnectBackgroundParams) error {
|
func (s *SignalClient) ConnectBackground(ctx context.Context, _ *bridgev2.ConnectBackgroundParams) error {
|
||||||
|
if !s.IsLoggedIn() {
|
||||||
|
return bridgev2.ErrNotLoggedIn
|
||||||
|
}
|
||||||
s.queueEmptyWaiter.Clear()
|
s.queueEmptyWaiter.Clear()
|
||||||
ch, unauthCh, err := s.Client.StartWebsockets(ctx)
|
ch, unauthCh, err := s.Client.StartWebsockets(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@ type SignalConfig struct {
|
||||||
NoteToSelfAvatar id.ContentURIString `yaml:"note_to_self_avatar"`
|
NoteToSelfAvatar id.ContentURIString `yaml:"note_to_self_avatar"`
|
||||||
LocationFormat string `yaml:"location_format"`
|
LocationFormat string `yaml:"location_format"`
|
||||||
ExtEvPolls bool `yaml:"extev_polls"`
|
ExtEvPolls bool `yaml:"extev_polls"`
|
||||||
|
IdentityChangeNotices bool `yaml:"identity_change_notices"`
|
||||||
|
|
||||||
displaynameTemplate *template.Template `yaml:"-"`
|
displaynameTemplate *template.Template `yaml:"-"`
|
||||||
}
|
}
|
||||||
|
|
@ -106,6 +107,7 @@ func upgradeConfig(helper up.Helper) {
|
||||||
helper.Copy(up.Str, "note_to_self_avatar")
|
helper.Copy(up.Str, "note_to_self_avatar")
|
||||||
helper.Copy(up.Str, "location_format")
|
helper.Copy(up.Str, "location_format")
|
||||||
helper.Copy(up.Bool, "extev_polls")
|
helper.Copy(up.Bool, "extev_polls")
|
||||||
|
helper.Copy(up.Bool, "identity_change_notices")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SignalConnector) GetConfig() (string, any, up.Upgrader) {
|
func (s *SignalConnector) GetConfig() (string, any, up.Upgrader) {
|
||||||
|
|
|
||||||
|
|
@ -25,3 +25,5 @@ note_to_self_avatar: mxc://maunium.net/REBIVrqjZwmaWpssCZpBlmlL
|
||||||
location_format: 'https://www.google.com/maps/place/%[1]s,%[2]s'
|
location_format: 'https://www.google.com/maps/place/%[1]s,%[2]s'
|
||||||
# Should polls be sent using unstable MSC3381 event types?
|
# Should polls be sent using unstable MSC3381 event types?
|
||||||
extev_polls: false
|
extev_polls: false
|
||||||
|
# Should another user's safety number changing send a message to Matrix?
|
||||||
|
identity_change_notices: false
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ import (
|
||||||
|
|
||||||
var defaultPL = 0
|
var defaultPL = 0
|
||||||
var moderatorPL = 50
|
var moderatorPL = 50
|
||||||
|
var terminatedPL = 100
|
||||||
|
|
||||||
func roleToPL(role signalmeow.GroupMemberRole) *int {
|
func roleToPL(role signalmeow.GroupMemberRole) *int {
|
||||||
switch role {
|
switch role {
|
||||||
|
|
@ -70,6 +71,21 @@ func applyAttributesAccess(plc *bridgev2.PowerLevelOverrides, attributeAccess si
|
||||||
plc.Events[event.StateBeeperDisappearingTimer] = attributePL
|
plc.Events[event.StateBeeperDisappearingTimer] = attributePL
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func applyTerminated(plc *bridgev2.PowerLevelOverrides) {
|
||||||
|
plc.EventsDefault = &terminatedPL
|
||||||
|
plc.StateDefault = &terminatedPL
|
||||||
|
plc.Invite = &terminatedPL
|
||||||
|
plc.Kick = &terminatedPL
|
||||||
|
plc.Ban = &terminatedPL
|
||||||
|
plc.Redact = &terminatedPL
|
||||||
|
for _, evtType := range []event.Type{
|
||||||
|
event.EventReaction, event.EventRedaction, event.StatePowerLevels, event.StateRoomName,
|
||||||
|
event.StateRoomAvatar, event.StateTopic, event.StateBeeperDisappearingTimer,
|
||||||
|
} {
|
||||||
|
plc.Events[evtType] = terminatedPL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func applyMembersAccess(plc *bridgev2.PowerLevelOverrides, memberAccess signalmeow.AccessControl) {
|
func applyMembersAccess(plc *bridgev2.PowerLevelOverrides, memberAccess signalmeow.AccessControl) {
|
||||||
if memberAccess == signalmeow.AccessControl_ADMINISTRATOR {
|
if memberAccess == signalmeow.AccessControl_ADMINISTRATOR {
|
||||||
plc.Invite = &moderatorPL
|
plc.Invite = &moderatorPL
|
||||||
|
|
@ -125,6 +141,9 @@ func (s *SignalClient) wrapGroupInfo(ctx context.Context, groupInfo *signalmeow.
|
||||||
applyMembersAccess(members.PowerLevels, groupInfo.AccessControl.Members)
|
applyMembersAccess(members.PowerLevels, groupInfo.AccessControl.Members)
|
||||||
joinRule = inviteLinkToJoinRule(groupInfo.AccessControl.AddFromInviteLink)
|
joinRule = inviteLinkToJoinRule(groupInfo.AccessControl.AddFromInviteLink)
|
||||||
}
|
}
|
||||||
|
if groupInfo.Terminated {
|
||||||
|
applyTerminated(members.PowerLevels)
|
||||||
|
}
|
||||||
for _, member := range groupInfo.RequestingMembers {
|
for _, member := range groupInfo.RequestingMembers {
|
||||||
members.MemberMap.Set(bridgev2.ChatMember{
|
members.MemberMap.Set(bridgev2.ChatMember{
|
||||||
EventSender: s.makeEventSender(member.ACI),
|
EventSender: s.makeEventSender(member.ACI),
|
||||||
|
|
@ -265,7 +284,8 @@ func (s *SignalClient) groupChangeToChatInfoChange(ctx context.Context, groupID
|
||||||
var pls *bridgev2.PowerLevelOverrides
|
var pls *bridgev2.PowerLevelOverrides
|
||||||
if groupChange.ModifyAnnouncementsOnly != nil ||
|
if groupChange.ModifyAnnouncementsOnly != nil ||
|
||||||
groupChange.ModifyAttributesAccess != nil ||
|
groupChange.ModifyAttributesAccess != nil ||
|
||||||
groupChange.ModifyMemberAccess != nil {
|
groupChange.ModifyMemberAccess != nil ||
|
||||||
|
groupChange.TerminateGroup {
|
||||||
pls = &bridgev2.PowerLevelOverrides{Events: make(map[event.Type]int)}
|
pls = &bridgev2.PowerLevelOverrides{Events: make(map[event.Type]int)}
|
||||||
if groupChange.ModifyAnnouncementsOnly != nil {
|
if groupChange.ModifyAnnouncementsOnly != nil {
|
||||||
applyAnnouncementsOnly(pls, *groupChange.ModifyAnnouncementsOnly)
|
applyAnnouncementsOnly(pls, *groupChange.ModifyAnnouncementsOnly)
|
||||||
|
|
@ -276,6 +296,9 @@ func (s *SignalClient) groupChangeToChatInfoChange(ctx context.Context, groupID
|
||||||
if groupChange.ModifyMemberAccess != nil {
|
if groupChange.ModifyMemberAccess != nil {
|
||||||
applyMembersAccess(pls, *groupChange.ModifyMemberAccess)
|
applyMembersAccess(pls, *groupChange.ModifyMemberAccess)
|
||||||
}
|
}
|
||||||
|
if groupChange.TerminateGroup {
|
||||||
|
applyTerminated(pls)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if groupChange.ModifyAddFromInviteLinkAccess != nil {
|
if groupChange.ModifyAddFromInviteLinkAccess != nil {
|
||||||
ic.ChatInfo.JoinRule = &event.JoinRulesEventContent{
|
ic.ChatInfo.JoinRule = &event.JoinRulesEventContent{
|
||||||
|
|
|
||||||
|
|
@ -302,20 +302,48 @@ func (s *SignalClient) HandleMatrixReactionRemove(ctx context.Context, msg *brid
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SignalClient) HandleMatrixMessageRemove(ctx context.Context, msg *bridgev2.MatrixMessageRemove) error {
|
func (s *SignalClient) HandleMatrixMessageRemove(ctx context.Context, msg *bridgev2.MatrixMessageRemove) error {
|
||||||
_, targetSentTimestamp, err := signalid.ParseMessageID(msg.TargetMessage.ID)
|
targetAuthorACI, targetSentTimestamp, err := signalid.ParseMessageID(msg.TargetMessage.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to parse target message ID: %w", err)
|
return fmt.Errorf("failed to parse target message ID: %w", err)
|
||||||
} else if msg.TargetMessage.SenderID != signalid.MakeUserID(s.Client.Store.ACI) {
|
|
||||||
return errDeleteOthersMessage
|
|
||||||
}
|
}
|
||||||
ts := getTimestampForEvent(msg.InputTransactionID, msg.Event, msg.OrigSender)
|
dm := &signalpb.DataMessage{
|
||||||
err = s.sendMessage(ctx, msg.Portal.ID, signalmeow.WrapDataMessage(&signalpb.DataMessage{
|
Timestamp: proto.Uint64(getTimestampForEvent(msg.InputTransactionID, msg.Event, msg.OrigSender)),
|
||||||
Timestamp: proto.Uint64(ts),
|
}
|
||||||
Delete: &signalpb.DataMessage_Delete{
|
if msg.TargetMessage.SenderID == signalid.MakeUserID(s.Client.Store.ACI) {
|
||||||
|
dm.Delete = &signalpb.DataMessage_Delete{
|
||||||
TargetSentTimestamp: proto.Uint64(targetSentTimestamp),
|
TargetSentTimestamp: proto.Uint64(targetSentTimestamp),
|
||||||
},
|
}
|
||||||
}))
|
} else if isAdmin, err := s.isGroupAdmin(ctx, msg.Portal); err != nil {
|
||||||
return wrapSendError(err)
|
return wrapSendError(err)
|
||||||
|
} else if !isAdmin {
|
||||||
|
return errDeleteOthersMessage
|
||||||
|
} else {
|
||||||
|
dm.AdminDelete = &signalpb.DataMessage_AdminDelete{
|
||||||
|
TargetAuthorAciBinary: targetAuthorACI[:],
|
||||||
|
TargetSentTimestamp: proto.Uint64(targetSentTimestamp),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = s.sendMessage(ctx, msg.Portal.ID, signalmeow.WrapDataMessage(dm))
|
||||||
|
return wrapSendError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SignalClient) isGroupAdmin(ctx context.Context, portal *bridgev2.Portal) (bool, error) {
|
||||||
|
_, groupID, err := signalid.ParsePortalID(portal.ID)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
} else if groupID == "" {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
group, _, err := s.Client.RetrieveGroupByID(ctx, groupID, portal.Metadata.(*signalid.PortalMetadata).Revision)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("failed to get group info: %w", err)
|
||||||
|
}
|
||||||
|
for _, member := range group.Members {
|
||||||
|
if member.ACI == s.Client.Store.ACI {
|
||||||
|
return member.Role == signalmeow.GroupMember_ADMINISTRATOR, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SignalClient) HandleMatrixReadReceipt(ctx context.Context, receipt *bridgev2.MatrixReadReceipt) error {
|
func (s *SignalClient) HandleMatrixReadReceipt(ctx context.Context, receipt *bridgev2.MatrixReadReceipt) error {
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,7 @@ func (s *SignalClient) handleSignalEvent(rawEvt events.SignalEvent) bool {
|
||||||
case *events.LoggedOut:
|
case *events.LoggedOut:
|
||||||
s.sendLoggedOutState(evt.Error)
|
s.sendLoggedOutState(evt.Error)
|
||||||
case *events.IdentityChange:
|
case *events.IdentityChange:
|
||||||
// TODO send notice
|
s.handleSignalIdentityChange(evt)
|
||||||
default:
|
default:
|
||||||
s.UserLogin.Log.Warn().Type("event_type", evt).Msg("Unrecognized signalmeow event type")
|
s.UserLogin.Log.Warn().Type("event_type", evt).Msg("Unrecognized signalmeow event type")
|
||||||
}
|
}
|
||||||
|
|
@ -161,6 +161,44 @@ func convertDecryptionError(_ context.Context, _ *bridgev2.Portal, _ bridgev2.Ma
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SignalClient) handleSignalIdentityChange(evt *events.IdentityChange) {
|
||||||
|
if !s.Main.Config.IdentityChangeNotices || evt.ServiceID.Type != libsignalgo.ServiceIDTypeACI || evt.ServiceID.UUID == s.Client.Store.ACI {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.UserLogin.QueueRemoteEvent(&simplevent.Message[*events.IdentityChange]{
|
||||||
|
EventMeta: simplevent.EventMeta{
|
||||||
|
Type: bridgev2.RemoteEventMessage,
|
||||||
|
LogContext: func(c zerolog.Context) zerolog.Context {
|
||||||
|
return c.Stringer("sender_id", evt.ServiceID)
|
||||||
|
},
|
||||||
|
PortalKey: s.makeDMPortalKey(evt.ServiceID),
|
||||||
|
Sender: s.makeEventSender(evt.ServiceID.UUID),
|
||||||
|
},
|
||||||
|
Data: evt,
|
||||||
|
ID: "idchange|" + signalid.MakeMessageID(evt.ServiceID.UUID, uint64(time.Now().UnixMilli())),
|
||||||
|
ConvertMessageFunc: convertIdentityChange,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertIdentityChange(ctx context.Context, portal *bridgev2.Portal, _ bridgev2.MatrixAPI, evt *events.IdentityChange) (*bridgev2.ConvertedMessage, error) {
|
||||||
|
name := evt.ServiceID.UUID.String()
|
||||||
|
ghost, err := portal.Bridge.GetGhostByID(ctx, signalid.MakeUserID(evt.ServiceID.UUID))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if ghost.Name != "" {
|
||||||
|
name = ghost.Name
|
||||||
|
}
|
||||||
|
return &bridgev2.ConvertedMessage{
|
||||||
|
Parts: []*bridgev2.ConvertedMessagePart{{
|
||||||
|
Type: event.EventMessage,
|
||||||
|
Content: &event.MessageEventContent{
|
||||||
|
MsgType: event.MsgNotice,
|
||||||
|
Body: fmt.Sprintf("Your safety number with %s has changed.", name),
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
type Bv2ChatEvent struct {
|
type Bv2ChatEvent struct {
|
||||||
*events.ChatEvent
|
*events.ChatEvent
|
||||||
s *SignalClient
|
s *SignalClient
|
||||||
|
|
@ -185,6 +223,7 @@ func (evt *Bv2ChatEvent) GetType() bridgev2.RemoteEventType {
|
||||||
switch {
|
switch {
|
||||||
case innerEvt.GetIsViewOnce(), innerEvt.Body != nil, innerEvt.Attachments != nil, innerEvt.Contact != nil, innerEvt.Sticker != nil,
|
case innerEvt.GetIsViewOnce(), innerEvt.Body != nil, innerEvt.Attachments != nil, innerEvt.Contact != nil, innerEvt.Sticker != nil,
|
||||||
innerEvt.Payment != nil, innerEvt.GiftBadge != nil, innerEvt.PollCreate != nil, innerEvt.PollVote != nil,
|
innerEvt.Payment != nil, innerEvt.GiftBadge != nil, innerEvt.PollCreate != nil, innerEvt.PollVote != nil,
|
||||||
|
innerEvt.PinMessage != nil, innerEvt.UnpinMessage != nil,
|
||||||
innerEvt.GetRequiredProtocolVersion() > uint32(signalpb.DataMessage_CURRENT),
|
innerEvt.GetRequiredProtocolVersion() > uint32(signalpb.DataMessage_CURRENT),
|
||||||
innerEvt.GetFlags()&uint32(signalpb.DataMessage_EXPIRATION_TIMER_UPDATE) != 0:
|
innerEvt.GetFlags()&uint32(signalpb.DataMessage_EXPIRATION_TIMER_UPDATE) != 0:
|
||||||
return bridgev2.RemoteEventMessage
|
return bridgev2.RemoteEventMessage
|
||||||
|
|
|
||||||
|
|
@ -153,6 +153,12 @@ func (mc *MessageConverter) ToMatrix(
|
||||||
cm.Parts = append(cm.Parts, mc.convertPollTerminateToMatrix(ctx, sender, dm.PollTerminate))
|
cm.Parts = append(cm.Parts, mc.convertPollTerminateToMatrix(ctx, sender, dm.PollTerminate))
|
||||||
return cm
|
return cm
|
||||||
}
|
}
|
||||||
|
if dm.PinMessage != nil || dm.UnpinMessage != nil {
|
||||||
|
var part *bridgev2.ConvertedMessagePart
|
||||||
|
part, cm.ReplyTo = mc.convertPinToMatrix(dm)
|
||||||
|
cm.Parts = append(cm.Parts, part)
|
||||||
|
return cm
|
||||||
|
}
|
||||||
if dm.GetIsViewOnce() {
|
if dm.GetIsViewOnce() {
|
||||||
if !mc.Bridge.Config.ViewLimitedMedia && (cm.Disappear.Timer == 0 || cm.Disappear.Timer > 5*time.Minute) {
|
if !mc.Bridge.Config.ViewLimitedMedia && (cm.Disappear.Timer == 0 || cm.Disappear.Timer > 5*time.Minute) {
|
||||||
cm.Disappear = database.DisappearingSetting{
|
cm.Disappear = database.DisappearingSetting{
|
||||||
|
|
@ -502,6 +508,11 @@ func (mc *MessageConverter) convertAttachmentToMatrix(ctx context.Context, index
|
||||||
|
|
||||||
func (mc *MessageConverter) convertStickerToMatrix(ctx context.Context, sticker *signalpb.DataMessage_Sticker, attMap AttachmentMap) *bridgev2.ConvertedMessagePart {
|
func (mc *MessageConverter) convertStickerToMatrix(ctx context.Context, sticker *signalpb.DataMessage_Sticker, attMap AttachmentMap) *bridgev2.ConvertedMessagePart {
|
||||||
converted, err := mc.reuploadAttachment(ctx, sticker.GetData(), attMap)
|
converted, err := mc.reuploadAttachment(ctx, sticker.GetData(), attMap)
|
||||||
|
hasPack := len(sticker.GetPackId()) == PackIDLength && len(sticker.GetPackKey()) == PackKeyLength && !bytes.Equal(sticker.GetPackId(), zeroPackID)
|
||||||
|
if err != nil && hasPack && (errors.Is(err, signalmeow.ErrAttachmentNotFound) || errors.Is(err, ErrAttachmentNotInBackup) || errors.Is(err, ErrBackupNotSupported)) {
|
||||||
|
zerolog.Ctx(ctx).Debug().Err(err).Msg("Sticker attachment is unavailable, downloading it from the sticker pack")
|
||||||
|
converted, err = mc.reuploadStickerFromPack(ctx, sticker)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
zerolog.Ctx(ctx).Err(err).Msg("Failed to handle sticker")
|
zerolog.Ctx(ctx).Err(err).Msg("Failed to handle sticker")
|
||||||
return &bridgev2.ConvertedMessagePart{
|
return &bridgev2.ConvertedMessagePart{
|
||||||
|
|
@ -519,7 +530,7 @@ func (mc *MessageConverter) convertStickerToMatrix(ctx context.Context, sticker
|
||||||
converted.Content.Info.Height = 200
|
converted.Content.Info.Height = 200
|
||||||
}
|
}
|
||||||
converted.Content.Body = sticker.GetEmoji()
|
converted.Content.Body = sticker.GetEmoji()
|
||||||
if len(sticker.GetPackId()) == PackIDLength && len(sticker.GetPackKey()) == PackKeyLength && !bytes.Equal(sticker.GetPackId(), zeroPackID) {
|
if hasPack {
|
||||||
converted.Content.Info.BridgedSticker = &event.BridgedSticker{
|
converted.Content.Info.BridgedSticker = &event.BridgedSticker{
|
||||||
Network: StickerSourceID,
|
Network: StickerSourceID,
|
||||||
ID: strconv.FormatUint(uint64(sticker.GetStickerId()), 10),
|
ID: strconv.FormatUint(uint64(sticker.GetStickerId()), 10),
|
||||||
|
|
@ -532,6 +543,49 @@ func (mc *MessageConverter) convertStickerToMatrix(ctx context.Context, sticker
|
||||||
return converted
|
return converted
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (mc *MessageConverter) reuploadStickerFromPack(ctx context.Context, sticker *signalpb.DataMessage_Sticker) (*bridgev2.ConvertedMessagePart, error) {
|
||||||
|
att := sticker.GetData()
|
||||||
|
content := &event.MessageEventContent{
|
||||||
|
Body: att.GetFileName(),
|
||||||
|
Info: &event.FileInfo{
|
||||||
|
MimeType: att.GetContentType(),
|
||||||
|
Width: int(att.GetWidth()),
|
||||||
|
Height: int(att.GetHeight()),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if mc.DirectMedia {
|
||||||
|
mediaID, err := signalid.DirectMediaSticker{
|
||||||
|
PackID: sticker.GetPackId(),
|
||||||
|
PackKey: sticker.GetPackKey(),
|
||||||
|
StickerID: sticker.GetStickerId(),
|
||||||
|
}.AsMediaID()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
content.URL, content.File, err = mc.Bridge.GenerateContentURI(ctx, getPortal(ctx).MXID, mediaID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
data, err := signalmeow.DownloadStickerPackItem(ctx, sticker.GetPackId(), sticker.GetPackKey(), sticker.GetStickerId())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to download sticker from pack: %w", err)
|
||||||
|
}
|
||||||
|
if content.Info.MimeType == "" {
|
||||||
|
content.Info.MimeType = http.DetectContentType(data)
|
||||||
|
}
|
||||||
|
content.Info.Size = len(data)
|
||||||
|
content.URL, content.File, err = getIntent(ctx).UploadMedia(ctx, getPortal(ctx).MXID, data, content.Body, content.Info.MimeType)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to upload sticker from pack: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &bridgev2.ConvertedMessagePart{
|
||||||
|
Type: event.EventMessage,
|
||||||
|
Content: content,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (mc *MessageConverter) downloadSignalLongText(ctx context.Context, att *signalpb.AttachmentPointer, attMap AttachmentMap) (*string, error) {
|
func (mc *MessageConverter) downloadSignalLongText(ctx context.Context, att *signalpb.AttachmentPointer, attMap AttachmentMap) (*string, error) {
|
||||||
data, err := mc.downloadAttachment(ctx, att, attMap, nil)
|
data, err := mc.downloadAttachment(ctx, att, attMap, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -793,6 +847,28 @@ func (mc *MessageConverter) convertPollTerminateToMatrix(ctx context.Context, se
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (mc *MessageConverter) convertPinToMatrix(dm *signalpb.DataMessage) (*bridgev2.ConvertedMessagePart, *networkid.MessageOptionalPartID) {
|
||||||
|
body := "Pinned a message"
|
||||||
|
targetAuthor, targetTS := dm.GetPinMessage().GetTargetAuthorAciBinary(), dm.GetPinMessage().GetTargetSentTimestamp()
|
||||||
|
if dm.PinMessage == nil {
|
||||||
|
body = "Unpinned a message"
|
||||||
|
targetAuthor, targetTS = dm.GetUnpinMessage().GetTargetAuthorAciBinary(), dm.GetUnpinMessage().GetTargetSentTimestamp()
|
||||||
|
}
|
||||||
|
var replyTo *networkid.MessageOptionalPartID
|
||||||
|
if len(targetAuthor) == 16 {
|
||||||
|
replyTo = &networkid.MessageOptionalPartID{
|
||||||
|
MessageID: signalid.MakeMessageID(uuid.UUID(targetAuthor), targetTS),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &bridgev2.ConvertedMessagePart{
|
||||||
|
Type: event.EventMessage,
|
||||||
|
Content: &event.MessageEventContent{
|
||||||
|
MsgType: event.MsgNotice,
|
||||||
|
Body: body,
|
||||||
|
},
|
||||||
|
}, replyTo
|
||||||
|
}
|
||||||
|
|
||||||
var invalidPollVote = &bridgev2.ConvertedMessagePart{
|
var invalidPollVote = &bridgev2.ConvertedMessagePart{
|
||||||
Type: event.EventUnstablePollResponse,
|
Type: event.EventUnstablePollResponse,
|
||||||
Content: &event.MessageEventContent{},
|
Content: &event.MessageEventContent{},
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,8 @@ type Client struct {
|
||||||
|
|
||||||
storageSyncLock sync.Mutex
|
storageSyncLock sync.Mutex
|
||||||
storageSyncQueued atomic.Bool
|
storageSyncQueued atomic.Bool
|
||||||
|
storageVersion uint64
|
||||||
|
storageRecords []string
|
||||||
|
|
||||||
writeCallbackCounter chan time.Time
|
writeCallbackCounter chan time.Time
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -203,6 +203,7 @@ func (gc *GroupCache) ApplyUpdate(change *GroupChange, endorsementResponse libsi
|
||||||
delete(gc.data, id)
|
delete(gc.data, id)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
prevMembers := cached.getMemberServiceIDs()
|
||||||
|
|
||||||
// Pending member adds, promotes and removes
|
// Pending member adds, promotes and removes
|
||||||
cached.PendingMembers = append(cached.PendingMembers, change.AddPendingMembers...)
|
cached.PendingMembers = append(cached.PendingMembers, change.AddPendingMembers...)
|
||||||
|
|
@ -304,6 +305,9 @@ func (gc *GroupCache) ApplyUpdate(change *GroupChange, endorsementResponse libsi
|
||||||
if change.ModifyAnnouncementsOnly != nil {
|
if change.ModifyAnnouncementsOnly != nil {
|
||||||
cached.AnnouncementsOnly = *change.ModifyAnnouncementsOnly
|
cached.AnnouncementsOnly = *change.ModifyAnnouncementsOnly
|
||||||
}
|
}
|
||||||
|
if change.TerminateGroup {
|
||||||
|
cached.Terminated = true
|
||||||
|
}
|
||||||
if change.ModifyDisappearingMessagesDuration != nil {
|
if change.ModifyDisappearingMessagesDuration != nil {
|
||||||
cached.DisappearingMessagesDuration = *change.ModifyDisappearingMessagesDuration
|
cached.DisappearingMessagesDuration = *change.ModifyDisappearingMessagesDuration
|
||||||
}
|
}
|
||||||
|
|
@ -319,6 +323,12 @@ func (gc *GroupCache) ApplyUpdate(change *GroupChange, endorsementResponse libsi
|
||||||
|
|
||||||
cached.UpdatedAt = time.Now()
|
cached.UpdatedAt = time.Now()
|
||||||
cached.Revision = change.Revision
|
cached.Revision = change.Revision
|
||||||
|
if len(endorsementResponse) == 0 {
|
||||||
|
if !slices.Equal(prevMembers, cached.getMemberServiceIDs()) {
|
||||||
|
delete(gc.data, id)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
endorsement, memberEndorsements, err := endorsementResponse.ReceiveWithServiceIDs(
|
endorsement, memberEndorsements, err := endorsementResponse.ReceiveWithServiceIDs(
|
||||||
cached.getMemberServiceIDs(),
|
cached.getMemberServiceIDs(),
|
||||||
gc.serviceID,
|
gc.serviceID,
|
||||||
|
|
|
||||||
|
|
@ -81,6 +81,7 @@ type Group struct {
|
||||||
Members []*GroupMember
|
Members []*GroupMember
|
||||||
Description string
|
Description string
|
||||||
AnnouncementsOnly bool
|
AnnouncementsOnly bool
|
||||||
|
Terminated bool
|
||||||
Revision uint32
|
Revision uint32
|
||||||
DisappearingMessagesDuration uint32
|
DisappearingMessagesDuration uint32
|
||||||
AccessControl *GroupAccessControl
|
AccessControl *GroupAccessControl
|
||||||
|
|
@ -206,6 +207,7 @@ type GroupChange struct {
|
||||||
DeleteBannedMembers []*libsignalgo.ServiceID
|
DeleteBannedMembers []*libsignalgo.ServiceID
|
||||||
PromotePendingPniAciMembers []*PromotePendingPniAciMember
|
PromotePendingPniAciMembers []*PromotePendingPniAciMember
|
||||||
ModifyInviteLinkPassword *types.SerializedInviteLinkPassword
|
ModifyInviteLinkPassword *types.SerializedInviteLinkPassword
|
||||||
|
TerminateGroup bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (groupChange *GroupChange) isEmpty() bool {
|
func (groupChange *GroupChange) isEmpty() bool {
|
||||||
|
|
@ -226,7 +228,8 @@ func (groupChange *GroupChange) isEmpty() bool {
|
||||||
len(groupChange.PromoteRequestingMembers) == 0 &&
|
len(groupChange.PromoteRequestingMembers) == 0 &&
|
||||||
groupChange.ModifyDescription == nil &&
|
groupChange.ModifyDescription == nil &&
|
||||||
groupChange.ModifyAnnouncementsOnly == nil &&
|
groupChange.ModifyAnnouncementsOnly == nil &&
|
||||||
len(groupChange.AddBannedMembers) == 0
|
len(groupChange.AddBannedMembers) == 0 &&
|
||||||
|
!groupChange.TerminateGroup
|
||||||
}
|
}
|
||||||
|
|
||||||
func (groupChange *GroupChange) resolveConflict(group *Group) {
|
func (groupChange *GroupChange) resolveConflict(group *Group) {
|
||||||
|
|
@ -497,6 +500,8 @@ func decryptGroup(ctx context.Context, encryptedGroup *signalpb.Group, groupMast
|
||||||
// These aren't encrypted
|
// These aren't encrypted
|
||||||
decryptedGroup.AvatarPath = encryptedGroup.AvatarUrl
|
decryptedGroup.AvatarPath = encryptedGroup.AvatarUrl
|
||||||
decryptedGroup.Revision = encryptedGroup.Version
|
decryptedGroup.Revision = encryptedGroup.Version
|
||||||
|
decryptedGroup.Terminated = encryptedGroup.Terminated
|
||||||
|
decryptedGroup.AnnouncementsOnly = encryptedGroup.AnnouncementsOnly
|
||||||
|
|
||||||
// Decrypt members
|
// Decrypt members
|
||||||
for _, member := range encryptedGroup.Members {
|
for _, member := range encryptedGroup.Members {
|
||||||
|
|
@ -1076,6 +1081,7 @@ func (cli *Client) decryptGroupChange(ctx context.Context, encryptedGroupChange
|
||||||
if encryptedActions.ModifyAnnouncementsOnly != nil {
|
if encryptedActions.ModifyAnnouncementsOnly != nil {
|
||||||
decryptedGroupChange.ModifyAnnouncementsOnly = &encryptedActions.ModifyAnnouncementsOnly.AnnouncementsOnly
|
decryptedGroupChange.ModifyAnnouncementsOnly = &encryptedActions.ModifyAnnouncementsOnly.AnnouncementsOnly
|
||||||
}
|
}
|
||||||
|
decryptedGroupChange.TerminateGroup = encryptedActions.TerminateGroup != nil
|
||||||
if encryptedActions.ModifyDisappearingMessageTimer != nil && len(encryptedActions.ModifyDisappearingMessageTimer.Timer) > 0 {
|
if encryptedActions.ModifyDisappearingMessageTimer != nil && len(encryptedActions.ModifyDisappearingMessageTimer.Timer) > 0 {
|
||||||
timerBlob, err := decryptGroupPropertyIntoBlob(groupSecretParams, encryptedActions.ModifyDisappearingMessageTimer.Timer)
|
timerBlob, err := decryptGroupPropertyIntoBlob(groupSecretParams, encryptedActions.ModifyDisappearingMessageTimer.Timer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -1801,8 +1807,7 @@ func (cli *Client) GetGroupHistoryPage(ctx context.Context, gid types.GroupIdent
|
||||||
"Cached-Send-Endorsements": "0",
|
"Cached-Send-Endorsements": "0",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
// highest known epoch seems to always be 5, but that may change in the future. includeLastState is always false
|
path := fmt.Sprintf("/v2/groups/logs/%d?maxSupportedChangeEpoch=%d&includeFirstState=%t&includeLastState=false", fromRevision, 7, includeFirstState)
|
||||||
path := fmt.Sprintf("/v2/groups/logs/%d?maxSupportedChangeEpoch=%d&includeFirstState=%t&includeLastState=false", fromRevision, 5, includeFirstState)
|
|
||||||
response, err := web.SendHTTPRequest(ctx, web.StorageHostname, http.MethodGet, path, opts)
|
response, err := web.SendHTTPRequest(ctx, web.StorageHostname, http.MethodGet, path, opts)
|
||||||
defer web.CloseBody(response)
|
defer web.CloseBody(response)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -378,6 +378,19 @@ func (cli *Client) incomingAPIMessageHandler(ctx context.Context, req *signalpb.
|
||||||
Str("envelope_type", signalpb.Envelope_Type_name[int32(envelope.GetType())]).
|
Str("envelope_type", signalpb.Envelope_Type_name[int32(envelope.GetType())]).
|
||||||
Msg("Received envelope")
|
Msg("Received envelope")
|
||||||
|
|
||||||
|
if envelope.GetType() == signalpb.Envelope_SERVER_DELIVERY_RECEIPT {
|
||||||
|
if sourceServiceID.Type == libsignalgo.ServiceIDTypeACI && sourceServiceID.UUID != uuid.Nil && !cli.handleEvent(&events.Receipt{
|
||||||
|
Sender: sourceServiceID.UUID,
|
||||||
|
Content: &signalpb.ReceiptMessage{
|
||||||
|
Type: signalpb.ReceiptMessage_DELIVERY.Enum(),
|
||||||
|
Timestamp: []uint64{envelope.GetClientTimestamp()},
|
||||||
|
},
|
||||||
|
}) {
|
||||||
|
return nil, ErrHandlerFailed
|
||||||
|
}
|
||||||
|
return &web.SimpleResponse{Status: 200}, nil
|
||||||
|
}
|
||||||
|
|
||||||
result := cli.decryptEnvelope(ctx, envelope, sourceServiceID, destinationServiceID)
|
result := cli.decryptEnvelope(ctx, envelope, sourceServiceID, destinationServiceID)
|
||||||
|
|
||||||
err = cli.handleDecryptedResult(ctx, result, envelope, destinationServiceID)
|
err = cli.handleDecryptedResult(ctx, result, envelope, destinationServiceID)
|
||||||
|
|
@ -909,7 +922,7 @@ func (cli *Client) handlePNISignatureMessage(ctx context.Context, sender libsign
|
||||||
Stringer("aci", sender.UUID).
|
Stringer("aci", sender.UUID).
|
||||||
Stringer("pni", pni).
|
Stringer("pni", pni).
|
||||||
Msg("Fetching PNI identity for signature verification as it wasn't found in store")
|
Msg("Fetching PNI identity for signature verification as it wasn't found in store")
|
||||||
err = cli.FetchAndProcessPreKey(ctx, pniServiceID, 0)
|
err = cli.FetchAndProcessPreKey(ctx, pniServiceID, 1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to fetch prekey for PNI %s after identity wasn't found in store: %w", pni, err)
|
return fmt.Errorf("failed to fetch prekey for PNI %s after identity wasn't found in store: %w", pni, err)
|
||||||
} else if pniIdentity, err = cli.Store.IdentityKeyStore.GetIdentityKey(ctx, pniServiceID); err != nil {
|
} else if pniIdentity, err = cli.Store.IdentityKeyStore.GetIdentityKey(ctx, pniServiceID); err != nil {
|
||||||
|
|
|
||||||
|
|
@ -107,9 +107,6 @@ func (cli *Client) decryptEnvelope(
|
||||||
Unencrypted: true,
|
Unencrypted: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
case signalpb.Envelope_SERVER_DELIVERY_RECEIPT:
|
|
||||||
return DecryptionResult{Err: fmt.Errorf("server delivery receipt envelopes are not yet supported")}
|
|
||||||
|
|
||||||
case signalpb.Envelope_UNKNOWN:
|
case signalpb.Envelope_UNKNOWN:
|
||||||
return DecryptionResult{Err: fmt.Errorf("unknown envelope type")}
|
return DecryptionResult{Err: fmt.Errorf("unknown envelope type")}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -221,8 +221,8 @@ func (cli *Client) sendToGroupWithSenderKey(
|
||||||
header.Set("Unidentified-Access-Key", xak.String())
|
header.Set("Unidentified-Access-Key", xak.String())
|
||||||
}
|
}
|
||||||
path := fmt.Sprintf(
|
path := fmt.Sprintf(
|
||||||
"/v1/messages/multi_recipient?ts=%d&urgent=%t&online=false",
|
"/v1/messages/multi_recipient?ts=%d&urgent=%t&online=%t",
|
||||||
messageTimestamp, isUrgent(content),
|
messageTimestamp, isUrgent(content), content.GetTypingMessage() != nil,
|
||||||
)
|
)
|
||||||
log.Debug().
|
log.Debug().
|
||||||
Any("recipients", ski.SharedWith).
|
Any("recipients", ski.SharedWith).
|
||||||
|
|
|
||||||
|
|
@ -964,7 +964,7 @@ func (cli *Client) sendContent(
|
||||||
|
|
||||||
outgoingMessages := MyMessages{
|
outgoingMessages := MyMessages{
|
||||||
Timestamp: messageTimestamp,
|
Timestamp: messageTimestamp,
|
||||||
Online: false,
|
Online: content.GetTypingMessage() != nil,
|
||||||
Urgent: isUrgent(content),
|
Urgent: isUrgent(content),
|
||||||
Messages: messages,
|
Messages: messages,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -55,18 +55,30 @@ func (cli *Client) StorageSync(ctx context.Context) {
|
||||||
cli.storageSyncQueued.Store(false)
|
cli.storageSyncQueued.Store(false)
|
||||||
log := cli.Log.With().Str("action", "sync storage").Logger()
|
log := cli.Log.With().Str("action", "sync storage").Logger()
|
||||||
ctx = log.WithContext(ctx)
|
ctx = log.WithContext(ctx)
|
||||||
// TODO only fetch changed entries
|
update, err := cli.FetchStorage(ctx, cli.Store.MasterKey, cli.storageVersion, cli.storageRecords)
|
||||||
update, err := cli.FetchStorage(ctx, cli.Store.MasterKey, 0, nil)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Err(err).Msg("Failed to fetch storage")
|
log.Err(err).Msg("Failed to fetch storage")
|
||||||
return
|
return
|
||||||
|
} else if update == nil {
|
||||||
|
log.Debug().Uint64("version", cli.storageVersion).Msg("Storage manifest is already up to date")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
err = cli.Store.DoContactTxn(ctx, func(ctx context.Context) error {
|
err = cli.Store.DoContactTxn(ctx, func(ctx context.Context) error {
|
||||||
return cli.processStorageInTxn(ctx, update)
|
return cli.processStorageInTxn(ctx, update)
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Err(err).Msg("Failed to process storage update")
|
log.Err(err).Msg("Failed to process storage update")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
log.Debug().
|
||||||
|
Uint64("previous_version", cli.storageVersion).
|
||||||
|
Uint64("version", update.Version).
|
||||||
|
Int("new_records", len(update.NewRecords)).
|
||||||
|
Int("removed_records", len(update.RemovedRecords)).
|
||||||
|
Int("missing_records", len(update.MissingRecords)).
|
||||||
|
Msg("Processed storage update")
|
||||||
|
cli.storageVersion = update.Version
|
||||||
|
cli.storageRecords = update.KnownRecords
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cli *Client) processStorageInTxn(ctx context.Context, update *StorageUpdate) error {
|
func (cli *Client) processStorageInTxn(ctx context.Context, update *StorageUpdate) error {
|
||||||
|
|
@ -101,8 +113,9 @@ func (cli *Client) processStorageInTxn(ctx context.Context, update *StorageUpdat
|
||||||
recipient.Profile.Name = strings.TrimSpace(fmt.Sprintf("%s %s", contact.GivenName, contact.FamilyName))
|
recipient.Profile.Name = strings.TrimSpace(fmt.Sprintf("%s %s", contact.GivenName, contact.FamilyName))
|
||||||
}
|
}
|
||||||
if contact.SystemGivenName != "" || contact.SystemFamilyName != "" {
|
if contact.SystemGivenName != "" || contact.SystemFamilyName != "" {
|
||||||
changed = true
|
newContactName := strings.TrimSpace(fmt.Sprintf("%s %s", contact.SystemGivenName, contact.SystemFamilyName))
|
||||||
recipient.ContactName = strings.TrimSpace(fmt.Sprintf("%s %s", contact.SystemGivenName, contact.SystemFamilyName))
|
changed = changed || recipient.ContactName != newContactName
|
||||||
|
recipient.ContactName = newContactName
|
||||||
}
|
}
|
||||||
newNickname := ""
|
newNickname := ""
|
||||||
if contact.Nickname != nil {
|
if contact.Nickname != nil {
|
||||||
|
|
@ -120,7 +133,7 @@ func (cli *Client) processStorageInTxn(ctx context.Context, update *StorageUpdat
|
||||||
changed = true
|
changed = true
|
||||||
recipient.Blocked = contact.Blocked
|
recipient.Blocked = contact.Blocked
|
||||||
}
|
}
|
||||||
if !ptr.Val(recipient.Whitelisted) {
|
if !ptr.Val(recipient.Whitelisted) && (recipient.Whitelisted == nil || contact.Whitelisted) {
|
||||||
changed = true
|
changed = true
|
||||||
recipient.Whitelisted = &contact.Whitelisted
|
recipient.Whitelisted = &contact.Whitelisted
|
||||||
}
|
}
|
||||||
|
|
@ -158,7 +171,9 @@ func (cli *Client) processStorageInTxn(ctx context.Context, update *StorageUpdat
|
||||||
return fmt.Errorf("failed to save device after receiving account record: %w", err)
|
return fmt.Errorf("failed to save device after receiving account record: %w", err)
|
||||||
}
|
}
|
||||||
log.Debug().Msg("Saved device after receiving account record")
|
log.Debug().Msg("Saved device after receiving account record")
|
||||||
case *signalpb.StorageRecord_GroupV1, *signalpb.StorageRecord_StoryDistributionList:
|
case *signalpb.StorageRecord_GroupV1, *signalpb.StorageRecord_StoryDistributionList, *signalpb.StorageRecord_StickerPack,
|
||||||
|
*signalpb.StorageRecord_CallLink, *signalpb.StorageRecord_ChatFolder, *signalpb.StorageRecord_NotificationProfile,
|
||||||
|
*signalpb.StorageRecord_FavoriteSticker:
|
||||||
// irrelevant data
|
// irrelevant data
|
||||||
default:
|
default:
|
||||||
log.Warn().Type("type", data).Str("item_id", record.StorageID).Msg("Unknown storage record type")
|
log.Warn().Type("type", data).Str("item_id", record.StorageID).Msg("Unknown storage record type")
|
||||||
|
|
@ -178,6 +193,7 @@ type StorageUpdate struct {
|
||||||
NewRecords []*DecryptedStorageRecord
|
NewRecords []*DecryptedStorageRecord
|
||||||
RemovedRecords []string
|
RemovedRecords []string
|
||||||
MissingRecords []string
|
MissingRecords []string
|
||||||
|
KnownRecords []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cli *Client) FetchStorage(ctx context.Context, masterKey []byte, currentVersion uint64, existingKeys []string) (*StorageUpdate, error) {
|
func (cli *Client) FetchStorage(ctx context.Context, masterKey []byte, currentVersion uint64, existingKeys []string) (*StorageUpdate, error) {
|
||||||
|
|
@ -189,6 +205,7 @@ func (cli *Client) FetchStorage(ctx context.Context, masterKey []byte, currentVe
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
removedKeys := make([]string, 0)
|
removedKeys := make([]string, 0)
|
||||||
|
knownKeys := make([]string, 0, len(manifest.GetIdentifiers()))
|
||||||
newKeys := manifestRecordToMap(manifest.GetIdentifiers())
|
newKeys := manifestRecordToMap(manifest.GetIdentifiers())
|
||||||
slices.Sort(existingKeys)
|
slices.Sort(existingKeys)
|
||||||
existingKeys = slices.Compact(existingKeys)
|
existingKeys = slices.Compact(existingKeys)
|
||||||
|
|
@ -196,6 +213,7 @@ func (cli *Client) FetchStorage(ctx context.Context, masterKey []byte, currentVe
|
||||||
_, isStillThere := newKeys[key]
|
_, isStillThere := newKeys[key]
|
||||||
if isStillThere {
|
if isStillThere {
|
||||||
delete(newKeys, key)
|
delete(newKeys, key)
|
||||||
|
knownKeys = append(knownKeys, key)
|
||||||
} else {
|
} else {
|
||||||
removedKeys = append(removedKeys, key)
|
removedKeys = append(removedKeys, key)
|
||||||
}
|
}
|
||||||
|
|
@ -205,11 +223,15 @@ func (cli *Client) FetchStorage(ctx context.Context, masterKey []byte, currentVe
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
for _, record := range newRecords {
|
||||||
|
knownKeys = append(knownKeys, record.StorageID)
|
||||||
|
}
|
||||||
return &StorageUpdate{
|
return &StorageUpdate{
|
||||||
Version: manifest.GetVersion(),
|
Version: manifest.GetVersion(),
|
||||||
NewRecords: newRecords,
|
NewRecords: newRecords,
|
||||||
RemovedRecords: removedKeys,
|
RemovedRecords: removedKeys,
|
||||||
MissingRecords: missingKeys,
|
MissingRecords: missingKeys,
|
||||||
|
KnownRecords: knownKeys,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
@ -116,6 +117,9 @@ func (s *SignalWebsocket) Close() (err error) {
|
||||||
s.closeCalled.Store(true)
|
s.closeCalled.Store(true)
|
||||||
if ws := s.ws.Swap(nil); ws != nil {
|
if ws := s.ws.Swap(nil); ws != nil {
|
||||||
err = ws.Close(websocket.StatusNormalClosure, "")
|
err = ws.Close(websocket.StatusNormalClosure, "")
|
||||||
|
if errors.Is(err, net.ErrClosed) {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if cancelLoop := s.cancel.Swap(nil); cancelLoop != nil {
|
if cancelLoop := s.cancel.Swap(nil); cancelLoop != nil {
|
||||||
(*cancelLoop)()
|
(*cancelLoop)()
|
||||||
|
|
@ -319,6 +323,11 @@ func (s *SignalWebsocket) connectLoop(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Succssfully connected
|
// Succssfully connected
|
||||||
|
if resp != nil {
|
||||||
|
if alerts := resp.Header.Values("X-Signal-Alert"); len(alerts) > 0 {
|
||||||
|
log.Warn().Strs("alerts", alerts).Msg("Signal server sent alerts when opening websocket")
|
||||||
|
}
|
||||||
|
}
|
||||||
s.pushStatus(ctx, SignalWebsocketConnectionEventConnected, nil)
|
s.pushStatus(ctx, SignalWebsocketConnectionEventConnected, nil)
|
||||||
s.ws.Store(ws)
|
s.ws.Store(ws)
|
||||||
retrying = false
|
retrying = false
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue