253 lines
8.8 KiB
Go
253 lines
8.8 KiB
Go
// mautrix-signal - A Matrix-Signal puppeting bridge.
|
|
// Copyright (C) 2025 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 msgconv
|
|
|
|
import (
|
|
"slices"
|
|
|
|
"github.com/google/uuid"
|
|
"go.mau.fi/util/exslices"
|
|
"go.mau.fi/util/ptr"
|
|
|
|
signalpb "go.mau.fi/mautrix-signal/pkg/signalmeow/protobuf"
|
|
"go.mau.fi/mautrix-signal/pkg/signalmeow/protobuf/backuppb"
|
|
)
|
|
|
|
func boolToInt(b bool) int {
|
|
if b {
|
|
return 1
|
|
}
|
|
return 0
|
|
}
|
|
|
|
type AttachmentMap map[uuid.UUID]*backuppb.FilePointer_BackupLocator
|
|
|
|
func BackupToDataMessage(ci *backuppb.ChatItem, attMap AttachmentMap) (*signalpb.DataMessage, []*backuppb.Reaction) {
|
|
var dm signalpb.DataMessage
|
|
var reactions []*backuppb.Reaction
|
|
switch ti := ci.Item.(type) {
|
|
case *backuppb.ChatItem_StandardMessage:
|
|
reactions = ti.StandardMessage.Reactions
|
|
if text := ti.StandardMessage.Text; text != nil {
|
|
dm.Body = &text.Body
|
|
dm.BodyRanges = slices.DeleteFunc(exslices.CastFunc(text.BodyRanges, backupToSignalBodyRange), deleteNil)
|
|
}
|
|
dm.Attachments = make([]*signalpb.AttachmentPointer, 0, len(ti.StandardMessage.Attachments)+boolToInt(ti.StandardMessage.LongText != nil))
|
|
if ti.StandardMessage.LongText != nil {
|
|
randomUUID := uuid.New()
|
|
dm.Attachments = append(
|
|
dm.Attachments,
|
|
backupToSignalAttachment(ti.StandardMessage.LongText, 0, randomUUID, attMap),
|
|
)
|
|
}
|
|
for _, att := range ti.StandardMessage.Attachments {
|
|
var clientUUID uuid.UUID
|
|
if len(att.ClientUuid) == 16 {
|
|
clientUUID = uuid.UUID(att.ClientUuid)
|
|
} else {
|
|
clientUUID = uuid.New()
|
|
}
|
|
dm.Attachments = append(
|
|
dm.Attachments,
|
|
backupToSignalAttachment(att.Pointer, att.Flag, clientUUID, attMap),
|
|
)
|
|
}
|
|
dm.Preview = exslices.CastFunc(ti.StandardMessage.LinkPreview, func(from *backuppb.LinkPreview) *signalpb.Preview {
|
|
return backupToSignalLinkPreview(from, attMap)
|
|
})
|
|
case *backuppb.ChatItem_ContactMessage:
|
|
reactions = ti.ContactMessage.Reactions
|
|
dm.Contact = []*signalpb.DataMessage_Contact{backupToSignalContact(ti.ContactMessage.Contact, attMap)}
|
|
case *backuppb.ChatItem_StickerMessage:
|
|
reactions = ti.StickerMessage.Reactions
|
|
dm.Sticker = &signalpb.DataMessage_Sticker{
|
|
PackId: ti.StickerMessage.Sticker.PackId,
|
|
PackKey: ti.StickerMessage.Sticker.PackKey,
|
|
StickerId: &ti.StickerMessage.Sticker.StickerId,
|
|
Emoji: ti.StickerMessage.Sticker.Emoji,
|
|
Data: backupToSignalAttachment(ti.StickerMessage.Sticker.Data, 0, uuid.New(), attMap),
|
|
}
|
|
case *backuppb.ChatItem_RemoteDeletedMessage:
|
|
// TODO handle some other way? (also disappeared view-once messages)
|
|
return nil, nil
|
|
case *backuppb.ChatItem_PaymentNotification:
|
|
dm.Payment = &signalpb.DataMessage_Payment{
|
|
Item: &signalpb.DataMessage_Payment_Notification_{
|
|
Notification: &signalpb.DataMessage_Payment_Notification{
|
|
Transaction: nil,
|
|
Note: ti.PaymentNotification.Note,
|
|
},
|
|
},
|
|
}
|
|
case *backuppb.ChatItem_GiftBadge:
|
|
dm.GiftBadge = &signalpb.DataMessage_GiftBadge{
|
|
ReceiptCredentialPresentation: ti.GiftBadge.ReceiptCredentialPresentation,
|
|
}
|
|
case *backuppb.ChatItem_ViewOnceMessage:
|
|
reactions = ti.ViewOnceMessage.Reactions
|
|
if ti.ViewOnceMessage.Attachment == nil {
|
|
// TODO handle some other way?
|
|
return nil, reactions
|
|
}
|
|
dm.IsViewOnce = ptr.Ptr(true)
|
|
var clientUUID uuid.UUID
|
|
if len(ti.ViewOnceMessage.Attachment.ClientUuid) == 16 {
|
|
clientUUID = uuid.UUID(ti.ViewOnceMessage.Attachment.ClientUuid)
|
|
} else {
|
|
clientUUID = uuid.New()
|
|
}
|
|
dm.Attachments = []*signalpb.AttachmentPointer{backupToSignalAttachment(
|
|
ti.ViewOnceMessage.Attachment.Pointer,
|
|
ti.ViewOnceMessage.Attachment.Flag,
|
|
clientUUID,
|
|
attMap,
|
|
)}
|
|
}
|
|
return &dm, reactions
|
|
}
|
|
|
|
func backupToSignalContact(from *backuppb.ContactAttachment, attMap AttachmentMap) *signalpb.DataMessage_Contact {
|
|
var contact signalpb.DataMessage_Contact
|
|
if from.Name != nil {
|
|
contact.Name = &signalpb.DataMessage_Contact_Name{
|
|
GivenName: &from.Name.GivenName,
|
|
FamilyName: &from.Name.FamilyName,
|
|
Prefix: &from.Name.Prefix,
|
|
Suffix: &from.Name.Suffix,
|
|
MiddleName: &from.Name.MiddleName,
|
|
Nickname: &from.Name.Nickname,
|
|
}
|
|
}
|
|
contact.Number = exslices.CastFunc(from.Number, func(from *backuppb.ContactAttachment_Phone) *signalpb.DataMessage_Contact_Phone {
|
|
return &signalpb.DataMessage_Contact_Phone{
|
|
Value: &from.Value,
|
|
Type: ptr.NonZero(signalpb.DataMessage_Contact_Phone_Type(from.Type)),
|
|
Label: &from.Label,
|
|
}
|
|
})
|
|
contact.Email = exslices.CastFunc(from.Email, func(from *backuppb.ContactAttachment_Email) *signalpb.DataMessage_Contact_Email {
|
|
return &signalpb.DataMessage_Contact_Email{
|
|
Value: &from.Value,
|
|
Type: ptr.NonZero(signalpb.DataMessage_Contact_Email_Type(from.Type)),
|
|
Label: &from.Label,
|
|
}
|
|
})
|
|
contact.Address = exslices.CastFunc(from.Address, func(from *backuppb.ContactAttachment_PostalAddress) *signalpb.DataMessage_Contact_PostalAddress {
|
|
return &signalpb.DataMessage_Contact_PostalAddress{
|
|
Type: ptr.NonZero(signalpb.DataMessage_Contact_PostalAddress_Type(from.Type)),
|
|
Label: &from.Label,
|
|
Street: &from.Street,
|
|
Pobox: &from.Pobox,
|
|
Neighborhood: &from.Neighborhood,
|
|
City: &from.City,
|
|
Region: &from.Region,
|
|
Postcode: &from.Postcode,
|
|
Country: &from.Country,
|
|
}
|
|
})
|
|
if from.Avatar != nil {
|
|
contact.Avatar = &signalpb.DataMessage_Contact_Avatar{
|
|
Avatar: backupToSignalAttachment(from.Avatar, 0, uuid.New(), attMap),
|
|
}
|
|
}
|
|
contact.Organization = ptr.NonZero(from.Organization)
|
|
return &contact
|
|
}
|
|
|
|
func backupToSignalLinkPreview(from *backuppb.LinkPreview, attMap AttachmentMap) *signalpb.Preview {
|
|
var ap *signalpb.AttachmentPointer
|
|
if from.Image != nil {
|
|
ap = backupToSignalAttachment(from.Image, 0, uuid.New(), attMap)
|
|
}
|
|
return &signalpb.Preview{
|
|
Url: &from.Url,
|
|
Title: from.Title,
|
|
Image: ap,
|
|
Description: from.Description,
|
|
Date: from.Date,
|
|
}
|
|
}
|
|
|
|
func backupToSignalAttachment(
|
|
fp *backuppb.FilePointer,
|
|
flag backuppb.MessageAttachment_Flag,
|
|
clientUUID uuid.UUID,
|
|
atts map[uuid.UUID]*backuppb.FilePointer_BackupLocator,
|
|
) *signalpb.AttachmentPointer {
|
|
sig := &signalpb.AttachmentPointer{
|
|
ContentType: fp.ContentType,
|
|
IncrementalMac: fp.IncrementalMac,
|
|
IncrementalMacChunkSize: fp.IncrementalMacChunkSize,
|
|
FileName: fp.FileName,
|
|
Flags: ptr.NonZero(uint32(backupToSignalAttachmentFlag(flag))),
|
|
Width: fp.Width,
|
|
Height: fp.Height,
|
|
Caption: nil, // is this field deprecated or something?
|
|
BlurHash: fp.BlurHash,
|
|
Uuid: clientUUID[:],
|
|
}
|
|
switch loc := fp.Locator.(type) {
|
|
case *backuppb.FilePointer_AttachmentLocator_:
|
|
sig.AttachmentIdentifier = &signalpb.AttachmentPointer_CdnKey{CdnKey: loc.AttachmentLocator.CdnKey}
|
|
sig.Key = loc.AttachmentLocator.Key
|
|
sig.Size = &loc.AttachmentLocator.Size
|
|
sig.Digest = loc.AttachmentLocator.Digest
|
|
sig.CdnNumber = &loc.AttachmentLocator.CdnNumber
|
|
case *backuppb.FilePointer_BackupLocator_:
|
|
atts[clientUUID] = loc.BackupLocator
|
|
case *backuppb.FilePointer_InvalidAttachmentLocator_:
|
|
atts[clientUUID] = nil
|
|
}
|
|
return sig
|
|
}
|
|
|
|
func backupToSignalAttachmentFlag(flag backuppb.MessageAttachment_Flag) signalpb.AttachmentPointer_Flags {
|
|
switch flag {
|
|
case backuppb.MessageAttachment_VOICE_MESSAGE:
|
|
return signalpb.AttachmentPointer_VOICE_MESSAGE
|
|
case backuppb.MessageAttachment_BORDERLESS:
|
|
return signalpb.AttachmentPointer_BORDERLESS
|
|
case backuppb.MessageAttachment_GIF:
|
|
return compatFlagGIF
|
|
case backuppb.MessageAttachment_NONE:
|
|
fallthrough
|
|
default:
|
|
return 0
|
|
}
|
|
}
|
|
|
|
func deleteNil(bodyRange *signalpb.BodyRange) bool {
|
|
return bodyRange == nil
|
|
}
|
|
|
|
func backupToSignalBodyRange(from *backuppb.BodyRange) *signalpb.BodyRange {
|
|
var out signalpb.BodyRange
|
|
out.Start = &from.Start
|
|
out.Length = &from.Length
|
|
switch av := from.AssociatedValue.(type) {
|
|
case *backuppb.BodyRange_MentionAci:
|
|
// TODO confirm this is correct
|
|
if len(av.MentionAci) != 16 {
|
|
return nil
|
|
}
|
|
out.AssociatedValue = &signalpb.BodyRange_MentionAci{MentionAci: uuid.UUID(av.MentionAci).String()}
|
|
case *backuppb.BodyRange_Style_:
|
|
out.AssociatedValue = &signalpb.BodyRange_Style_{Style: signalpb.BodyRange_Style(av.Style)}
|
|
}
|
|
return &out
|
|
}
|