1
0
Fork 0
mirror of https://github.com/mautrix/signal.git synced 2026-05-15 13:46:55 -04:00
mautrix-signal/pkg/connector/handlesignal.go

804 lines
27 KiB
Go
Raw Permalink Normal View History

// mautrix-signal - A Matrix-Signal puppeting bridge.
// Copyright (C) 2024 Tulir Asokan
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package connector
import (
"context"
"encoding/base64"
"fmt"
"strings"
"time"
"github.com/google/uuid"
"github.com/rs/zerolog"
"go.mau.fi/util/exzerolog"
"go.mau.fi/util/jsontime"
"go.mau.fi/util/ptr"
"maunium.net/go/mautrix/bridgev2"
"maunium.net/go/mautrix/bridgev2/database"
"maunium.net/go/mautrix/bridgev2/networkid"
2024-09-09 19:25:08 +03:00
"maunium.net/go/mautrix/bridgev2/simplevent"
"maunium.net/go/mautrix/bridgev2/status"
2024-06-28 19:34:18 +03:00
"maunium.net/go/mautrix/event"
"go.mau.fi/mautrix-signal/pkg/libsignalgo"
2024-08-07 02:14:39 +03:00
"go.mau.fi/mautrix-signal/pkg/signalid"
"go.mau.fi/mautrix-signal/pkg/signalmeow"
"go.mau.fi/mautrix-signal/pkg/signalmeow/events"
signalpb "go.mau.fi/mautrix-signal/pkg/signalmeow/protobuf"
"go.mau.fi/mautrix-signal/pkg/signalmeow/types"
)
func (s *SignalClient) handleSignalEvent(rawEvt events.SignalEvent) bool {
switch evt := rawEvt.(type) {
case *events.ChatEvent:
return s.Main.Bridge.QueueRemoteEvent(s.UserLogin, &Bv2ChatEvent{ChatEvent: evt, s: s}).Success
case *events.DecryptionError:
return s.Main.Bridge.QueueRemoteEvent(s.UserLogin, s.wrapDecryptionError(evt)).Success
case *events.Receipt:
return s.handleSignalReceipt(evt)
case *events.ReadSelf:
return s.handleSignalReadSelf(evt)
case *events.DeleteForMe:
return s.handleSignalDeleteForMe(evt)
case *events.MessageRequestResponse:
return s.handleSignalMessageRequestResponse(evt)
case *events.Call:
return s.Main.Bridge.QueueRemoteEvent(s.UserLogin, s.wrapCallEvent(evt)).Success
case *events.ContactList:
s.handleSignalContactList(evt)
case *events.ACIFound:
s.handleSignalACIFound(evt)
case *events.QueueEmpty:
s.queueEmptyWaiter.Set()
case *events.LoggedOut:
s.UserLogin.BridgeState.Send(status.BridgeState{StateEvent: status.StateBadCredentials, Message: evt.Error.Error()})
default:
s.UserLogin.Log.Warn().Type("event_type", evt).Msg("Unrecognized signalmeow event type")
}
return true
}
2024-06-28 19:34:18 +03:00
func (s *SignalClient) wrapCallEvent(evt *events.Call) bridgev2.RemoteMessage {
2024-09-09 19:25:08 +03:00
return &simplevent.Message[*events.Call]{
EventMeta: simplevent.EventMeta{
Type: bridgev2.RemoteEventMessage,
LogContext: func(c zerolog.Context) zerolog.Context {
c = c.Stringer("sender_id", evt.Info.Sender)
c = c.Uint64("message_ts", evt.Timestamp)
return c
},
PortalKey: s.makePortalKey(evt.Info.ChatID),
CreatePortal: true,
Sender: s.makeEventSender(evt.Info.Sender),
Timestamp: time.UnixMilli(int64(evt.Timestamp)),
2024-06-28 19:34:18 +03:00
},
2024-09-09 19:25:08 +03:00
Data: evt,
ID: signalid.MakeMessageID(evt.Info.Sender, evt.Timestamp),
2024-06-28 19:34:18 +03:00
ConvertMessageFunc: convertCallEvent,
}
}
func convertCallEvent(ctx context.Context, portal *bridgev2.Portal, intent bridgev2.MatrixAPI, data *events.Call) (*bridgev2.ConvertedMessage, error) {
content := &event.MessageEventContent{
MsgType: event.MsgNotice,
}
if data.IsRinging {
content.Body = "Incoming call"
2024-08-07 02:14:39 +03:00
if userID, _, _ := signalid.ParsePortalID(portal.ID); !userID.IsEmpty() {
2024-06-28 19:34:18 +03:00
content.MsgType = event.MsgText
}
content.BeeperActionMessage = &event.BeeperActionMessage{
Type: event.BeeperActionMessageCall,
}
2024-06-28 19:34:18 +03:00
} else {
content.Body = "Call ended"
}
return &bridgev2.ConvertedMessage{
Parts: []*bridgev2.ConvertedMessagePart{{
Type: event.EventMessage,
Content: content,
}},
}, nil
}
2024-09-09 19:25:08 +03:00
func (s *SignalClient) wrapDecryptionError(evt *events.DecryptionError) bridgev2.RemoteMessage {
return &simplevent.Message[*events.DecryptionError]{
EventMeta: simplevent.EventMeta{
Type: bridgev2.RemoteEventMessage,
LogContext: func(c zerolog.Context) zerolog.Context {
c = c.Stringer("sender_id", evt.Sender)
c = c.Uint64("message_ts", evt.Timestamp)
2024-09-09 19:25:08 +03:00
return c
},
PortalKey: s.makePortalKey(evt.Sender.String()),
CreatePortal: true,
Sender: s.makeEventSender(evt.Sender),
Timestamp: time.UnixMilli(int64(evt.Timestamp)),
StreamOrder: int64(evt.Timestamp),
2024-09-09 19:25:08 +03:00
},
Data: evt,
2024-09-09 20:55:43 +03:00
// TODO use main message id and edit it if it later becomes decryptable?
ID: "decrypterr|" + signalid.MakeMessageID(evt.Sender, evt.Timestamp),
2024-09-09 19:25:08 +03:00
ConvertMessageFunc: convertDecryptionError,
}
}
func convertDecryptionError(_ context.Context, _ *bridgev2.Portal, _ bridgev2.MatrixAPI, _ *events.DecryptionError) (*bridgev2.ConvertedMessage, error) {
return &bridgev2.ConvertedMessage{
Parts: []*bridgev2.ConvertedMessagePart{{
Type: event.EventMessage,
Content: &event.MessageEventContent{
MsgType: event.MsgNotice,
Body: "Message couldn't be decrypted. It may have been in this chat or a group chat. Please check your Signal app.",
},
}},
}, nil
}
type Bv2ChatEvent struct {
*events.ChatEvent
s *SignalClient
}
var (
_ bridgev2.RemoteMessage = (*Bv2ChatEvent)(nil)
_ bridgev2.RemoteEdit = (*Bv2ChatEvent)(nil)
_ bridgev2.RemoteEventWithTimestamp = (*Bv2ChatEvent)(nil)
_ bridgev2.RemoteReaction = (*Bv2ChatEvent)(nil)
_ bridgev2.RemoteReactionRemove = (*Bv2ChatEvent)(nil)
_ bridgev2.RemoteMessageRemove = (*Bv2ChatEvent)(nil)
_ bridgev2.RemoteTyping = (*Bv2ChatEvent)(nil)
_ bridgev2.RemotePreHandler = (*Bv2ChatEvent)(nil)
_ bridgev2.RemoteChatInfoChange = (*Bv2ChatEvent)(nil)
_ bridgev2.RemoteEventWithStreamOrder = (*Bv2ChatEvent)(nil)
)
func (evt *Bv2ChatEvent) GetType() bridgev2.RemoteEventType {
switch innerEvt := evt.Event.(type) {
case *signalpb.DataMessage:
switch {
case innerEvt.Body != nil, innerEvt.Attachments != nil, innerEvt.Contact != nil, innerEvt.Sticker != nil,
2025-11-24 16:53:26 +02:00
innerEvt.Payment != nil, innerEvt.GiftBadge != nil, innerEvt.PollCreate != nil, innerEvt.PollVote != nil,
innerEvt.GetRequiredProtocolVersion() > uint32(signalpb.DataMessage_CURRENT),
innerEvt.GetFlags()&uint32(signalpb.DataMessage_EXPIRATION_TIMER_UPDATE) != 0:
return bridgev2.RemoteEventMessage
case innerEvt.Reaction != nil:
if innerEvt.Reaction.GetRemove() {
return bridgev2.RemoteEventReactionRemove
}
return bridgev2.RemoteEventReaction
case innerEvt.Delete != nil:
return bridgev2.RemoteEventMessageRemove
2024-06-27 19:00:05 +03:00
case innerEvt.GetGroupV2().GetGroupChange() != nil:
return bridgev2.RemoteEventChatInfoChange
}
case *signalpb.EditMessage:
return bridgev2.RemoteEventEdit
case *signalpb.TypingMessage:
return bridgev2.RemoteEventTyping
}
return bridgev2.RemoteEventUnknown
}
2024-06-27 19:00:05 +03:00
func (evt *Bv2ChatEvent) GetChatInfoChange(ctx context.Context) (*bridgev2.ChatInfoChange, error) {
dm, _ := evt.Event.(*signalpb.DataMessage)
gv2 := dm.GetGroupV2()
if gv2 == nil || gv2.GroupChange == nil {
return nil, fmt.Errorf("GetChatInfoChange() called for non-GroupChange event")
}
groupChange, err := evt.s.Client.DecryptGroupChange(ctx, gv2)
if err != nil {
return nil, fmt.Errorf("failed to decrypt group change: %w", err)
}
// XXX: is this ID compatible with types.GroupIdentifier?
return evt.s.groupChangeToChatInfoChange(ctx, types.GroupIdentifier(evt.Info.ChatID), gv2.GetRevision(), groupChange)
2024-06-27 19:00:05 +03:00
}
func (evt *Bv2ChatEvent) PreHandle(ctx context.Context, portal *bridgev2.Portal) {
dataMsg, ok := evt.Event.(*signalpb.DataMessage)
if !ok || dataMsg.GroupV2 == nil {
return
}
2024-08-07 02:14:39 +03:00
portalRev := portal.Metadata.(*signalid.PortalMetadata).Revision
2024-06-27 19:00:05 +03:00
if evt.Info.GroupRevision > portalRev {
toRevision := evt.Info.GroupRevision
if dataMsg.GetGroupV2().GetGroupChange() != nil {
toRevision--
}
evt.s.catchUpGroup(ctx, portal, portalRev, toRevision, dataMsg.GetTimestamp())
}
}
func (evt *Bv2ChatEvent) GetTimeout() time.Duration {
if evt.Event.(*signalpb.TypingMessage).GetAction() == signalpb.TypingMessage_STARTED {
return 15 * time.Second
} else {
return 0
}
}
func (evt *Bv2ChatEvent) GetPortalKey() networkid.PortalKey {
return evt.s.makePortalKey(evt.Info.ChatID)
}
func (evt *Bv2ChatEvent) ShouldCreatePortal() bool {
2024-06-27 19:00:05 +03:00
return evt.GetType() == bridgev2.RemoteEventMessage || evt.GetType() == bridgev2.RemoteEventChatInfoChange
}
func (evt *Bv2ChatEvent) AddLogContext(c zerolog.Context) zerolog.Context {
c = c.Stringer("sender_id", evt.Info.Sender)
switch innerEvt := evt.Event.(type) {
case *signalpb.DataMessage:
c = c.Uint64("message_ts", innerEvt.GetTimestamp())
switch {
case innerEvt.Reaction != nil:
c = c.Uint64("reaction_target_ts", innerEvt.Reaction.GetTargetSentTimestamp())
case innerEvt.Delete != nil:
c = c.Uint64("delete_target_ts", innerEvt.Delete.GetTargetSentTimestamp())
}
case *signalpb.EditMessage:
c = c.
Uint64("edit_target_ts", innerEvt.GetTargetSentTimestamp()).
Uint64("edit_ts", innerEvt.GetDataMessage().GetTimestamp())
}
return c
}
func (evt *Bv2ChatEvent) GetSender() bridgev2.EventSender {
return evt.s.makeEventSender(evt.Info.Sender)
}
func (evt *Bv2ChatEvent) GetID() networkid.MessageID {
ts := evt.getDataMsgTimestamp()
if ts == 0 {
2025-05-31 18:47:45 +03:00
return ""
}
2024-08-07 02:14:39 +03:00
return signalid.MakeMessageID(evt.Info.Sender, ts)
}
func (evt *Bv2ChatEvent) getDataMsgTimestamp() uint64 {
switch innerEvt := evt.Event.(type) {
case *signalpb.DataMessage:
return innerEvt.GetTimestamp()
case *signalpb.EditMessage:
return innerEvt.GetDataMessage().GetTimestamp()
default:
return 0
}
}
func (evt *Bv2ChatEvent) GetTimestamp() time.Time {
ts := evt.getDataMsgTimestamp()
if ts == 0 {
return time.Now()
}
return time.UnixMilli(int64(ts))
}
func (evt *Bv2ChatEvent) GetTargetMessage() networkid.MessageID {
var targetAuthorACI uuid.UUID
var targetSentTS uint64
switch innerEvt := evt.Event.(type) {
case *signalpb.DataMessage:
switch {
case innerEvt.Reaction != nil:
targetAuthorACI, _ = signalmeow.ParseStringOrBinaryUUID(innerEvt.Reaction.GetTargetAuthorAci(), innerEvt.Reaction.GetTargetAuthorAciBinary())
targetSentTS = innerEvt.Reaction.GetTargetSentTimestamp()
case innerEvt.Delete != nil:
targetSentTS = innerEvt.Delete.GetTargetSentTimestamp()
default:
2025-05-31 18:47:45 +03:00
return ""
}
case *signalpb.EditMessage:
targetSentTS = innerEvt.GetTargetSentTimestamp()
default:
2025-05-31 18:47:45 +03:00
return ""
}
if targetAuthorACI == uuid.Nil {
targetAuthorACI = evt.Info.Sender
}
return signalid.MakeMessageID(targetAuthorACI, targetSentTS)
}
func (evt *Bv2ChatEvent) GetReactionEmoji() (string, networkid.EmojiID) {
dataMsg, ok := evt.Event.(*signalpb.DataMessage)
if !ok || dataMsg.Reaction == nil {
panic(fmt.Errorf("GetReactionEmoji() called for non-reaction event"))
}
return dataMsg.GetReaction().GetEmoji(), ""
}
func (evt *Bv2ChatEvent) GetRemovedEmojiID() networkid.EmojiID {
return ""
}
func (evt *Bv2ChatEvent) ConvertMessage(ctx context.Context, portal *bridgev2.Portal, intent bridgev2.MatrixAPI) (*bridgev2.ConvertedMessage, error) {
dataMsg, ok := evt.Event.(*signalpb.DataMessage)
if !ok {
return nil, fmt.Errorf("ConvertMessage() called for non-DataMessage event")
}
converted := evt.s.Main.MsgConv.ToMatrix(ctx, evt.s.Client, portal, evt.Info.Sender, intent, dataMsg, nil)
2024-08-07 02:14:39 +03:00
if converted.Disappear.Type != "" {
evtTS := evt.GetTimestamp()
if !dataMsg.GetIsViewOnce() {
portal.UpdateDisappearingSetting(ctx, converted.Disappear, bridgev2.UpdateDisappearingSettingOpts{
Sender: intent,
Timestamp: evtTS,
Implicit: true,
Save: true,
SendNotice: true,
})
}
if evt.Info.Sender == evt.s.Client.Store.ACI {
2024-08-07 02:14:39 +03:00
converted.Disappear.DisappearAt = evtTS.Add(converted.Disappear.Timer)
}
}
2024-08-07 02:14:39 +03:00
return converted, nil
}
func (evt *Bv2ChatEvent) ConvertEdit(ctx context.Context, portal *bridgev2.Portal, intent bridgev2.MatrixAPI, existing []*database.Message) (*bridgev2.ConvertedEdit, error) {
editMsg, ok := evt.Event.(*signalpb.EditMessage)
if !ok {
return nil, fmt.Errorf("ConvertEdit() called for non-EditMessage event")
}
// TODO tell converter about existing parts to avoid reupload?
converted := evt.s.Main.MsgConv.ToMatrix(ctx, evt.s.Client, portal, evt.Info.Sender, intent, editMsg.GetDataMessage(), nil)
// TODO can anything other than the text be edited?
2024-08-07 02:14:39 +03:00
editPart := converted.Parts[len(converted.Parts)-1].ToEditPart(existing[len(existing)-1])
editPart.Part.EditCount++
editPart.Part.ID = signalid.MakeMessageID(evt.Info.Sender, editMsg.GetDataMessage().GetTimestamp())
return &bridgev2.ConvertedEdit{
ModifiedParts: []*bridgev2.ConvertedEditPart{editPart},
}, nil
}
func (evt *Bv2ChatEvent) GetStreamOrder() int64 {
return int64(evt.Info.ServerTimestamp)
}
type Bv2Receipt struct {
Type signalpb.ReceiptMessage_Type
Chat networkid.PortalKey
Sender bridgev2.EventSender
LastTS time.Time
LastID networkid.MessageID
IDs []networkid.MessageID
}
func (b *Bv2Receipt) GetType() bridgev2.RemoteEventType {
switch b.Type {
case signalpb.ReceiptMessage_READ:
return bridgev2.RemoteEventReadReceipt
case signalpb.ReceiptMessage_DELIVERY:
return bridgev2.RemoteEventDeliveryReceipt
default:
return bridgev2.RemoteEventUnknown
}
}
func (b *Bv2Receipt) GetPortalKey() networkid.PortalKey {
return b.Chat
}
func (b *Bv2Receipt) AddLogContext(c zerolog.Context) zerolog.Context {
return c.
Str("sender_id", string(b.Sender.Sender)).
Stringer("receipt_type", b.Type).
Array("message_ids", exzerolog.ArrayOfStrs(b.IDs))
}
func (b *Bv2Receipt) GetSender() bridgev2.EventSender {
return b.Sender
}
func (b *Bv2Receipt) GetLastReceiptTarget() networkid.MessageID {
return b.LastID
}
func (b *Bv2Receipt) GetReceiptTargets() []networkid.MessageID {
return b.IDs
}
2024-07-09 21:25:42 +03:00
func (b *Bv2Receipt) GetReadUpTo() time.Time {
return time.Time{}
}
var _ bridgev2.RemoteReceipt = (*Bv2Receipt)(nil)
func convertReceipts[T any](ctx context.Context, input []T, getMessageFunc func(ctx context.Context, msgID T) (*database.Message, error)) map[networkid.PortalKey]*Bv2Receipt {
log := zerolog.Ctx(ctx)
receipts := make(map[networkid.PortalKey]*Bv2Receipt)
for _, msgID := range input {
msg, err := getMessageFunc(ctx, msgID)
if err != nil {
log.Err(err).Any("message_id", msgID).Msg("Failed to get target message for receipt")
} else if msg == nil {
log.Debug().Any("message_id", msgID).Msg("Got receipt for unknown message")
} else {
receiptEvt, ok := receipts[msg.Room]
if !ok {
receiptEvt = &Bv2Receipt{Chat: msg.Room}
receipts[msg.Room] = receiptEvt
}
receiptEvt.IDs = append(receiptEvt.IDs, msg.ID)
if receiptEvt.LastTS.Before(msg.Timestamp) {
receiptEvt.LastTS = msg.Timestamp
receiptEvt.LastID = msg.ID
}
}
}
return receipts
}
func (s *SignalClient) dispatchReceipts(sender uuid.UUID, receiptType signalpb.ReceiptMessage_Type, receipts map[networkid.PortalKey]*Bv2Receipt) bool {
evtSender := s.makeEventSender(sender)
for chat, receiptEvt := range receipts {
receiptEvt.Chat = chat
receiptEvt.Sender = evtSender
receiptEvt.Type = receiptType
if !s.Main.Bridge.QueueRemoteEvent(s.UserLogin, receiptEvt).Success {
return false
}
}
return true
}
func (s *SignalClient) handleSignalReceipt(evt *events.Receipt) bool {
log := s.UserLogin.Log.With().
Str("action", "handle signal receipt").
Stringer("sender_id", evt.Sender).
Stringer("receipt_type", evt.Content.GetType()).
Logger()
ctx := log.WithContext(context.TODO())
receipts := convertReceipts(ctx, evt.Content.Timestamp, func(ctx context.Context, msgTS uint64) (*database.Message, error) {
2024-08-07 02:14:39 +03:00
return s.Main.Bridge.DB.Message.GetFirstPartByID(ctx, s.UserLogin.ID, signalid.MakeMessageID(s.Client.Store.ACI, msgTS))
})
return s.dispatchReceipts(evt.Sender, evt.Content.GetType(), receipts)
}
func (s *SignalClient) handleSignalReadSelf(evt *events.ReadSelf) bool {
log := s.UserLogin.Log.With().
Str("action", "handle signal read self").
Logger()
ctx := log.WithContext(context.TODO())
receipts := convertReceipts(ctx, evt.Messages, func(ctx context.Context, msgInfo *signalpb.SyncMessage_Read) (*database.Message, error) {
aciUUID, err := signalmeow.ParseStringOrBinaryUUID(msgInfo.GetSenderAci(), msgInfo.GetSenderAciBinary())
if err != nil {
return nil, err
}
2024-08-07 02:14:39 +03:00
return s.Main.Bridge.DB.Message.GetFirstPartByID(ctx, s.UserLogin.ID, signalid.MakeMessageID(aciUUID, msgInfo.GetTimestamp()))
})
return s.dispatchReceipts(s.Client.Store.ACI, signalpb.ReceiptMessage_READ, receipts)
}
func (s *SignalClient) conversationIDToPortalKey(ctx context.Context, cid *signalpb.ConversationIdentifier) (networkid.PortalKey, bool) {
log := zerolog.Ctx(ctx)
switch ident := cid.GetIdentifier().(type) {
case *signalpb.ConversationIdentifier_ThreadServiceId:
serviceID, err := libsignalgo.ServiceIDFromString(ident.ThreadServiceId)
if err != nil {
log.Err(err).Str("chat_id", ident.ThreadServiceId).Msg("Failed to parse delete for me conversation ID")
return networkid.PortalKey{}, false
}
return s.makeDMPortalKey(serviceID), true
case *signalpb.ConversationIdentifier_ThreadServiceIdBinary:
serviceID, err := libsignalgo.ServiceIDFromBytes(ident.ThreadServiceIdBinary)
if err != nil {
log.Err(err).Hex("chat_id", ident.ThreadServiceIdBinary).Msg("Failed to parse delete for me conversation ID")
return networkid.PortalKey{}, false
}
return s.makeDMPortalKey(serviceID), true
case *signalpb.ConversationIdentifier_ThreadGroupId:
if len(ident.ThreadGroupId) != libsignalgo.GroupIdentifierLength {
log.Error().
Str("chat_id", base64.StdEncoding.EncodeToString(ident.ThreadGroupId)).
Msg("Invalid group ID length in delete for me conversation")
return networkid.PortalKey{}, false
}
return s.makePortalKey((*libsignalgo.GroupIdentifier)(ident.ThreadGroupId).String()), true
case *signalpb.ConversationIdentifier_ThreadE164:
log.Warn().Str("chat_id", ident.ThreadE164).Msg("Unsupported E164 conversation ID in delete for me")
return networkid.PortalKey{}, false
default:
log.Warn().
Type("chat_id_type", ident).
Msg("Unsupported conversation ID protobuf type in delete for me")
return networkid.PortalKey{}, false
}
}
func (s *SignalClient) addressableMessageToID(ctx context.Context, portalKey networkid.PortalKey, am *signalpb.AddressableMessage) networkid.MessageID {
log := zerolog.Ctx(ctx)
switch typedAuthor := am.GetAuthor().(type) {
case *signalpb.AddressableMessage_AuthorServiceId:
serviceID, err := libsignalgo.ServiceIDFromString(typedAuthor.AuthorServiceId)
if err != nil {
log.Err(err).
Object("portal_key", portalKey).
Str("author_service_id", typedAuthor.AuthorServiceId).
Msg("Failed to parse delete for me message author service ID")
return ""
} else if serviceID.Type != libsignalgo.ServiceIDTypeACI {
log.Warn().
Object("portal_key", portalKey).
Str("author_service_id", typedAuthor.AuthorServiceId).
Msg("Dropping delete for me message with unsupported service ID type")
return ""
}
return signalid.MakeMessageID(serviceID.UUID, am.GetSentTimestamp())
case *signalpb.AddressableMessage_AuthorServiceIdBinary:
serviceID, err := libsignalgo.ServiceIDFromBytes(typedAuthor.AuthorServiceIdBinary)
if err != nil {
log.Err(err).
Object("portal_key", portalKey).
Hex("author_service_id_binary", typedAuthor.AuthorServiceIdBinary).
Msg("Failed to parse delete for me message author service ID")
return ""
} else if serviceID.Type != libsignalgo.ServiceIDTypeACI {
log.Warn().
Object("portal_key", portalKey).
Hex("author_service_id_binary", typedAuthor.AuthorServiceIdBinary).
Msg("Dropping delete for me message with unsupported service ID type")
return ""
}
return signalid.MakeMessageID(serviceID.UUID, am.GetSentTimestamp())
case *signalpb.AddressableMessage_AuthorE164:
log.Warn().
Object("portal_key", portalKey).
Str("author_e164", typedAuthor.AuthorE164).
Msg("Dropping delete for me message with unsupported E164 author")
return ""
default:
log.Warn().
Object("portal_key", portalKey).
Type("author_type", typedAuthor).
Msg("Dropping delete for me message with unrecognized author protobuf type")
return ""
}
}
func (s *SignalClient) handleSignalDeleteForMe(evt *events.DeleteForMe) bool {
log := s.UserLogin.Log.With().
Str("action", "handle signal delete for me").
Logger()
ctx := log.WithContext(s.Main.Bridge.BackgroundCtx)
for _, conv := range evt.GetConversationDeletes() {
if !conv.GetIsFullDelete() {
// Non-full deletes might mean clearing chats?
continue
}
portalKey, ok := s.conversationIDToPortalKey(ctx, conv.GetConversation())
if !ok {
continue
}
res := s.UserLogin.QueueRemoteEvent(&simplevent.ChatDelete{
EventMeta: simplevent.EventMeta{
Type: bridgev2.RemoteEventChatDelete,
PortalKey: portalKey,
Timestamp: time.UnixMilli(int64(evt.Timestamp)),
StreamOrder: int64(evt.Timestamp),
},
OnlyForMe: true,
})
if !res.Success {
return false
}
}
for _, conv := range evt.GetLocalOnlyConversationDeletes() {
portalKey, ok := s.conversationIDToPortalKey(ctx, conv.GetConversation())
if !ok {
continue
}
res := s.UserLogin.QueueRemoteEvent(&simplevent.ChatDelete{
EventMeta: simplevent.EventMeta{
Type: bridgev2.RemoteEventChatDelete,
PortalKey: portalKey,
Timestamp: time.UnixMilli(int64(evt.Timestamp)),
StreamOrder: int64(evt.Timestamp),
},
OnlyForMe: true,
})
if !res.Success {
return false
}
}
for _, conv := range evt.GetMessageDeletes() {
portalKey, ok := s.conversationIDToPortalKey(ctx, conv.GetConversation())
if !ok {
continue
}
for _, msg := range conv.GetMessages() {
msgID := s.addressableMessageToID(ctx, portalKey, msg)
if msgID == "" {
continue
}
res := s.UserLogin.QueueRemoteEvent(&simplevent.MessageRemove{
EventMeta: simplevent.EventMeta{
Type: bridgev2.RemoteEventMessageRemove,
PortalKey: portalKey,
Timestamp: time.UnixMilli(int64(evt.Timestamp)),
StreamOrder: int64(evt.Timestamp),
},
OnlyForMe: true,
TargetMessage: msgID,
})
if !res.Success {
return false
}
}
}
return true
}
func (s *SignalClient) handleSignalMessageRequestResponse(evt *events.MessageRequestResponse) bool {
if evt.Type != signalpb.SyncMessage_MessageRequestResponse_ACCEPT {
// TODO do we need to do anything with blocks/deletes here or are they sent as normal delete events?
return true
}
var portalKey networkid.PortalKey
if evt.GroupID != nil {
portalKey = s.makePortalKey(evt.GroupID.String())
} else if evt.ThreadACI != uuid.Nil {
portalKey = s.makeDMPortalKey(libsignalgo.NewACIServiceID(evt.ThreadACI))
} else {
return true
}
res := s.UserLogin.QueueRemoteEvent(&simplevent.ChatInfoChange{
EventMeta: simplevent.EventMeta{
Type: bridgev2.RemoteEventChatInfoChange,
PortalKey: portalKey,
Timestamp: time.UnixMilli(int64(evt.Timestamp)),
StreamOrder: int64(evt.Timestamp),
LogContext: func(c zerolog.Context) zerolog.Context {
return c.Str("action", "unmark message request").Str("source", "sync message")
},
},
ChatInfoChange: &bridgev2.ChatInfoChange{
ChatInfo: &bridgev2.ChatInfo{
MessageRequest: ptr.Ptr(false),
},
},
})
return res.Success
}
func (s *SignalClient) handleSignalACIFound(evt *events.ACIFound) {
log := s.UserLogin.Log.With().
Str("action", "handle aci found").
Stringer("aci", evt.ACI).
Stringer("pni", evt.PNI).
Logger()
ctx := log.WithContext(context.TODO())
pniPortalKey := s.makeDMPortalKey(evt.PNI)
aciPortalKey := s.makeDMPortalKey(evt.ACI)
result, portal, err := s.Main.Bridge.ReIDPortal(ctx, pniPortalKey, aciPortalKey)
if err != nil {
log.Err(err).Msg("Failed to re-ID portal")
} else if result == bridgev2.ReIDResultSourceReIDd || result == bridgev2.ReIDResultTargetDeletedAndSourceReIDd {
// If the source portal is re-ID'd, we need to sync metadata and participants.
// If the source is deleted, then it doesn't matter, any existing target will already be correct
info, err := s.GetChatInfo(ctx, portal)
if err != nil {
log.Err(err).Msg("Failed to get chat info to update portal after re-ID")
} else {
portal.UpdateInfo(ctx, info, s.UserLogin, nil, time.Time{})
}
}
}
func (s *SignalClient) handleSignalContactList(evt *events.ContactList) {
log := s.UserLogin.Log.With().Str("action", "handle contact list").Logger()
ctx := log.WithContext(context.TODO())
for _, contact := range evt.Contacts {
if contact.ACI == uuid.Nil {
continue
}
if !evt.IsFromDB {
fullContact, err := s.Client.ContactByACI(ctx, contact.ACI)
if err != nil {
log.Err(err).Msg("Failed to get full contact info from store")
continue
}
fullContact.ContactAvatar = contact.ContactAvatar
contact = fullContact
}
2024-08-07 02:14:39 +03:00
ghost, err := s.Main.Bridge.GetGhostByID(ctx, signalid.MakeUserID(contact.ACI))
if err != nil {
log.Err(err).Msg("Failed to get ghost to update contact info")
continue
}
userInfo, err := s.contactToUserInfo(ctx, contact)
if err != nil {
log.Err(err).Msg("Failed to convert contact info")
continue
}
ghost.UpdateInfo(ctx, userInfo)
if contact.ACI == s.Client.Store.ACI {
s.updateRemoteProfile(ctx, true)
}
if ptr.Val(contact.Whitelisted) {
portal, err := s.Main.Bridge.GetExistingPortalByKey(ctx, s.makeDMPortalKey(libsignalgo.NewACIServiceID(contact.ACI)))
if err != nil {
log.Err(err).Msg("Failed to get existing portal to update contact info")
continue
} else if portal != nil && portal.MessageRequest {
s.UserLogin.QueueRemoteEvent(&simplevent.ChatInfoChange{
EventMeta: simplevent.EventMeta{
Type: bridgev2.RemoteEventChatInfoChange,
LogContext: func(c zerolog.Context) zerolog.Context {
return c.Str("action", "unmark message request").Str("source", "contact list")
},
PortalKey: portal.PortalKey,
},
ChatInfoChange: &bridgev2.ChatInfoChange{
ChatInfo: &bridgev2.ChatInfo{
MessageRequest: ptr.Ptr(false),
},
},
})
}
}
}
s.UserLogin.Metadata.(*signalid.UserLoginMetadata).LastContactSync = jsontime.UnixMilliNow()
err := s.UserLogin.Save(ctx)
if err != nil {
log.Err(err).Msg("Failed to update last contact sync time")
}
}
func (s *SignalClient) updateRemoteProfile(ctx context.Context, resendState bool) {
var err error
if s.Ghost == nil {
2024-08-07 02:14:39 +03:00
s.Ghost, err = s.Main.Bridge.GetGhostByID(ctx, signalid.MakeUserID(s.Client.Store.ACI))
if err != nil {
zerolog.Ctx(ctx).Err(err).Msg("Failed to get ghost for remote profile update")
return
}
}
changed := false
if s.UserLogin.RemoteProfile.Name != s.Ghost.Name {
s.UserLogin.RemoteProfile.Name = s.Ghost.Name
changed = true
}
if s.UserLogin.RemoteProfile.Avatar != s.Ghost.AvatarMXC {
s.UserLogin.RemoteProfile.Avatar = s.Ghost.AvatarMXC
changed = true
}
if len(s.Ghost.Identifiers) > 0 && strings.HasPrefix(s.Ghost.Identifiers[0], "tel:") {
phone := strings.TrimPrefix(s.Ghost.Identifiers[0], "tel:")
if s.UserLogin.RemoteProfile.Phone != phone {
s.UserLogin.RemoteProfile.Phone = phone
changed = true
}
}
if changed {
err = s.UserLogin.Save(ctx)
if err != nil {
zerolog.Ctx(ctx).Err(err).Msg("Failed to save updated remote profile")
}
if resendState {
// TODO this has potential race conditions
s.UserLogin.BridgeState.Send(s.UserLogin.BridgeState.GetPrevUnsent())
}
}
}