From f747fd3a354fe7463bb9793f00c3a82ea68aaa18 Mon Sep 17 00:00:00 2001 From: Joshua Higgins Date: Tue, 9 Apr 2024 17:11:28 -0400 Subject: [PATCH 01/13] Send tapbacks in backfills --- historysync.go | 187 +++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 149 insertions(+), 38 deletions(-) diff --git a/historysync.go b/historysync.go index b1ad0565..941f4169 100644 --- a/historysync.go +++ b/historysync.go @@ -163,8 +163,7 @@ func (portal *Portal) convertBackfill(messages []*imessage.Message) ([]*event.Ev } if msg.Tapback != nil { - // TODO handle tapbacks - portal.log.Debugln("Skipping tapback", msg.GUID, "in backfill") + portal.log.Debugln("Skipping tapback", msg.GUID, "in backfill, handling later") continue } @@ -198,6 +197,65 @@ func (portal *Portal) convertBackfill(messages []*imessage.Message) ([]*event.Ev return events, metas, metaIndexes, isRead, nil } +func (portal *Portal) convertTapbacks(messages []*imessage.Message) ([]*event.Event, []messageWithIndex, map[messageIndex]int, bool, error) { + events := make([]*event.Event, 0, len(messages)) + metas := make([]messageWithIndex, 0, len(messages)) + metaIndexes := make(map[messageIndex]int, len(messages)) + unreadThreshold := time.Duration(portal.bridge.Config.Bridge.Backfill.UnreadHoursThreshold) * time.Hour + var isRead bool + for _, msg := range messages { + if msg.Tapback == nil { + portal.log.Debugln("Skipping message", msg.GUID, "in backfill, should've already handled") + continue + } + + intent := portal.getIntentForMessage(msg, nil) + if intent == nil { + portal.log.Debugln("Skipping", msg.GUID, "in backfill (didn't get an intent)") + continue + } + + dbMessage := portal.bridge.DB.Message.GetByGUID(portal.GUID, msg.Tapback.TargetGUID, msg.Tapback.TargetPart) + if dbMessage == nil { + //TODO BUG: This occurs when trying to find the target reaction for a rich link, related to #183 + portal.log.Errorfln("Failed to get target message for tabpack, %+v", msg) + continue + } + + evt := &event.Event{ + Sender: intent.UserID, + Type: event.EventReaction, + Timestamp: msg.Time.UnixMilli(), + Content: event.Content{ + Parsed: &event.ReactionEventContent{ + RelatesTo: event.RelatesTo{ + Type: event.RelAnnotation, + EventID: dbMessage.MXID, + Key: msg.Tapback.Type.Emoji(), + }, + }, + }, + } + + var err error + evt.Type, err = portal.encrypt(intent, &evt.Content, evt.Type) + if err != nil { + return nil, nil, nil, false, err + } + intent.AddDoublePuppetValue(&evt.Content) + if portal.bridge.Config.Homeserver.Software == bridgeconfig.SoftwareHungry { + evt.ID = portal.deterministicEventID(msg.GUID, 0) + } + + events = append(events, evt) + metas = append(metas, messageWithIndex{msg, intent, dbMessage, 0}) + metaIndexes[messageIndex{msg.GUID, 0}] = len(metas) + + isRead = msg.IsRead || msg.IsFromMe || (unreadThreshold >= 0 && time.Since(msg.Time) > unreadThreshold) + } + return events, metas, metaIndexes, isRead, nil +} + func (portal *Portal) sendBackfill(backfillID string, messages []*imessage.Message, forward, forwardIfNoMessages, markAsRead bool) (success bool) { idMap := make(map[string][]id.EventID, len(messages)) for _, msg := range messages { @@ -212,16 +270,90 @@ func (portal *Portal) sendBackfill(backfillID string, messages []*imessage.Messa portal.bridge.IM.SendBackfillResult(portal.GUID, backfillID, success, idMap) }() batchSending := portal.bridge.SpecVersions.Supports(mautrix.BeeperFeatureBatchSending) - events, metas, metaIndexes, isRead, err := portal.convertBackfill(messages) + + var regularMessages []*imessage.Message + var tapbackMessages []*imessage.Message + for _, message := range messages { + if message.Tapback != nil { + tapbackMessages = append(tapbackMessages, message) + } else { + regularMessages = append(regularMessages, message) + } + } + + events, metas, metaIndexes, isRead, err := portal.convertBackfill(regularMessages) if err != nil { portal.log.Errorfln("Failed to convert messages for backfill: %v", err) return false } - portal.log.Debugfln("Converted %d messages into %d events to backfill", len(messages), len(events)) + portal.log.Debugfln("Converted %d messages into %d events to backfill", len(regularMessages), len(events)) + if len(events) == 0 { + return true + } + + eventIDs, sendErr := portal.sendBackfillToMatrixServer(batchSending, forward, forwardIfNoMessages, markAsRead, isRead, events, metas, metaIndexes) + if sendErr != nil { + return false + } + + for i, meta := range metas { + idMap[meta.GUID] = append(idMap[meta.GUID], eventIDs[i]) + } + txn, err := portal.bridge.DB.Begin() + if err != nil { + portal.log.Errorln("Failed to start transaction to save batch messages:", err) + return true + } + portal.log.Debugfln("Inserting %d event IDs to database to finish backfill %s", len(eventIDs), backfillID) + portal.finishBackfill(txn, eventIDs, metas) + portal.Update(txn) + err = txn.Commit() + if err != nil { + portal.log.Errorln("Failed to commit transaction to save batch messages:", err) + } + + if len(tapbackMessages) == 0 { + portal.log.Infofln("Finished backfill %s", backfillID) + return true + } + + events, metas, metaIndexes, isRead, err = portal.convertTapbacks(tapbackMessages) + if err != nil { + portal.log.Errorfln("Failed to convert tapbacks for backfill: %v", err) + return false + } + portal.log.Debugfln("Converted %d tapbacks into %d events to backfill", len(tapbackMessages), len(events)) if len(events) == 0 { return true } - var eventIDs []id.EventID + + eventIDs, sendErr = portal.sendBackfillToMatrixServer(batchSending, forward, forwardIfNoMessages, markAsRead, isRead, events, metas, metaIndexes) + if sendErr != nil { + return false + } + + for i, meta := range metas { + idMap[meta.GUID] = append(idMap[meta.GUID], eventIDs[i]) + } + txn, err = portal.bridge.DB.Begin() + if err != nil { + portal.log.Errorln("Failed to start transaction to save batch messages:", err) + return true + } + portal.log.Debugfln("Inserting %d event IDs to database to finish backfill %s", len(eventIDs), backfillID) + portal.finishBackfill(txn, eventIDs, metas) + portal.Update(txn) + err = txn.Commit() + if err != nil { + portal.log.Errorln("Failed to commit transaction to save batch messages:", err) + } + + portal.log.Infofln("Finished backfill %s", backfillID) + return true +} + +func (portal *Portal) sendBackfillToMatrixServer(batchSending, forward, forwardIfNoMessages, markAsRead, isRead bool, events []*event.Event, metas []messageWithIndex, + metaIndexes map[messageIndex]int) (eventIDs []id.EventID, err error) { if batchSending { req := &mautrix.ReqBeeperBatchSend{ Events: events, @@ -234,7 +366,7 @@ func (portal *Portal) sendBackfill(backfillID string, messages []*imessage.Messa resp, err := portal.MainIntent().BeeperBatchSend(portal.MXID, req) if err != nil { portal.log.Errorln("Failed to batch send history:", err) - return false + return nil, err } eventIDs = resp.EventIDs } else { @@ -251,7 +383,7 @@ func (portal *Portal) sendBackfill(backfillID string, messages []*imessage.Messa resp, err := meta.Intent.SendMassagedMessageEvent(portal.MXID, evt.Type, &evt.Content, evt.Timestamp) if err != nil { portal.log.Errorfln("Failed to send event #%d in history: %v", i, err) - return false + return nil, err } eventIDs[i] = resp.EventID } @@ -263,42 +395,21 @@ func (portal *Portal) sendBackfill(backfillID string, messages []*imessage.Messa } } } - for i, meta := range metas { - idMap[meta.GUID] = append(idMap[meta.GUID], eventIDs[i]) - } - txn, err := portal.bridge.DB.Begin() - if err != nil { - portal.log.Errorln("Failed to start transaction to save batch messages:", err) - return true - } - portal.log.Debugfln("Inserting %d event IDs to database to finish backfill %s", len(eventIDs), backfillID) - portal.finishBackfill(txn, eventIDs, metas) - portal.Update(txn) - err = txn.Commit() - if err != nil { - portal.log.Errorln("Failed to commit transaction to save batch messages:", err) - } - portal.log.Infofln("Finished backfill %s", backfillID) - return true + return eventIDs, nil } func (portal *Portal) finishBackfill(txn dbutil.Transaction, eventIDs []id.EventID, metas []messageWithIndex) { for i, info := range metas { if info.Tapback != nil { - if info.Tapback.Remove { - // TODO handle removing tapbacks? - } else { - // TODO can existing tapbacks be modified in backfill? - dbTapback := portal.bridge.DB.Tapback.New() - dbTapback.PortalGUID = portal.GUID - dbTapback.SenderGUID = info.Sender.String() - dbTapback.MessageGUID = info.TapbackTarget.GUID - dbTapback.MessagePart = info.TapbackTarget.Part - dbTapback.GUID = info.GUID - dbTapback.Type = info.Tapback.Type - dbTapback.MXID = eventIDs[i] - dbTapback.Insert(txn) - } + dbTapback := portal.bridge.DB.Tapback.New() + dbTapback.PortalGUID = portal.GUID + dbTapback.SenderGUID = info.Sender.String() + dbTapback.MessageGUID = info.TapbackTarget.GUID + dbTapback.MessagePart = info.TapbackTarget.Part + dbTapback.GUID = info.GUID + dbTapback.Type = info.Tapback.Type + dbTapback.MXID = eventIDs[i] + dbTapback.Insert(txn) } else { dbMessage := portal.bridge.DB.Message.New() dbMessage.PortalGUID = portal.GUID From a0197307134e6306cfcfe8660044c2dcbaddaed7 Mon Sep 17 00:00:00 2001 From: Joshua Higgins Date: Tue, 9 Apr 2024 17:15:06 -0400 Subject: [PATCH 02/13] remove sorting --- historysync.go | 23 ++++------------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/historysync.go b/historysync.go index 941f4169..2dfacfcb 100644 --- a/historysync.go +++ b/historysync.go @@ -271,22 +271,12 @@ func (portal *Portal) sendBackfill(backfillID string, messages []*imessage.Messa }() batchSending := portal.bridge.SpecVersions.Supports(mautrix.BeeperFeatureBatchSending) - var regularMessages []*imessage.Message - var tapbackMessages []*imessage.Message - for _, message := range messages { - if message.Tapback != nil { - tapbackMessages = append(tapbackMessages, message) - } else { - regularMessages = append(regularMessages, message) - } - } - - events, metas, metaIndexes, isRead, err := portal.convertBackfill(regularMessages) + events, metas, metaIndexes, isRead, err := portal.convertBackfill(messages) if err != nil { portal.log.Errorfln("Failed to convert messages for backfill: %v", err) return false } - portal.log.Debugfln("Converted %d messages into %d events to backfill", len(regularMessages), len(events)) + portal.log.Debugfln("Converted %d messages into %d message events to backfill", len(messages), len(events)) if len(events) == 0 { return true } @@ -312,17 +302,12 @@ func (portal *Portal) sendBackfill(backfillID string, messages []*imessage.Messa portal.log.Errorln("Failed to commit transaction to save batch messages:", err) } - if len(tapbackMessages) == 0 { - portal.log.Infofln("Finished backfill %s", backfillID) - return true - } - - events, metas, metaIndexes, isRead, err = portal.convertTapbacks(tapbackMessages) + events, metas, metaIndexes, isRead, err = portal.convertTapbacks(messages) if err != nil { portal.log.Errorfln("Failed to convert tapbacks for backfill: %v", err) return false } - portal.log.Debugfln("Converted %d tapbacks into %d events to backfill", len(tapbackMessages), len(events)) + portal.log.Debugfln("Converted %d messages into %d tapbacks events to backfill", len(messages), len(events)) if len(events) == 0 { return true } From 1140673faa8f77ac48ae8e139b276f6e2b533c44 Mon Sep 17 00:00:00 2001 From: Joshua Higgins Date: Tue, 9 Apr 2024 23:31:54 -0400 Subject: [PATCH 03/13] skip removes to not add a reaction --- historysync.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/historysync.go b/historysync.go index 2dfacfcb..f8b4e4b9 100644 --- a/historysync.go +++ b/historysync.go @@ -215,6 +215,10 @@ func (portal *Portal) convertTapbacks(messages []*imessage.Message) ([]*event.Ev continue } + if msg.Tapback.Remove { + continue + } + dbMessage := portal.bridge.DB.Message.GetByGUID(portal.GUID, msg.Tapback.TargetGUID, msg.Tapback.TargetPart) if dbMessage == nil { //TODO BUG: This occurs when trying to find the target reaction for a rich link, related to #183 @@ -386,6 +390,9 @@ func (portal *Portal) sendBackfillToMatrixServer(batchSending, forward, forwardI func (portal *Portal) finishBackfill(txn dbutil.Transaction, eventIDs []id.EventID, metas []messageWithIndex) { for i, info := range metas { if info.Tapback != nil { + if info.Tapback.Remove { + continue + } dbTapback := portal.bridge.DB.Tapback.New() dbTapback.PortalGUID = portal.GUID dbTapback.SenderGUID = info.Sender.String() From cf1d86f391400f1dcd561e838873828ee9e08af3 Mon Sep 17 00:00:00 2001 From: Joshua Higgins Date: Tue, 9 Apr 2024 23:32:33 -0400 Subject: [PATCH 04/13] properly convert BB tapback to TapbackType --- imessage/bluebubbles/api.go | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/imessage/bluebubbles/api.go b/imessage/bluebubbles/api.go index cf227e27..1ce36051 100644 --- a/imessage/bluebubbles/api.go +++ b/imessage/bluebubbles/api.go @@ -1678,7 +1678,7 @@ func (bb *blueBubbles) convertBBMessageToiMessage(bbMessage Message) (*imessage. bbMessage.AssociatedMessageType != "" { message.Tapback = &imessage.Tapback{ TargetGUID: bbMessage.AssociatedMessageGUID, - Type: imessage.TapbackFromName(bbMessage.AssociatedMessageType), + Type: bb.convertBBTapbackToImessageTapback(bbMessage.AssociatedMessageType), } message.Tapback.Parse() } else { @@ -1713,6 +1713,27 @@ func (bb *blueBubbles) convertBBMessageToiMessage(bbMessage Message) (*imessage. return &message, nil } +func (bb *blueBubbles) convertBBTapbackToImessageTapback(associatedMessageType string) (tbType imessage.TapbackType) { + if strings.Contains(associatedMessageType, "love") { + tbType = imessage.TapbackLove + } else if strings.Contains(associatedMessageType, "like") { + tbType = imessage.TapbackLike + } else if strings.Contains(associatedMessageType, "dislike") { + tbType = imessage.TapbackDislike + } else if strings.Contains(associatedMessageType, "laugh") { + tbType = imessage.TapbackLaugh + } else if strings.Contains(associatedMessageType, "emphasize") { + tbType = imessage.TapbackEmphasis + } else if strings.Contains(associatedMessageType, "question") { + tbType = imessage.TapbackQuestion + } + + if strings.Contains(associatedMessageType, "-") { + tbType += imessage.TapbackRemoveOffset + } + return tbType +} + func (bb *blueBubbles) convertBBChatToiMessageChat(bbChat Chat) (*imessage.ChatInfo, error) { members := make([]string, len(bbChat.Participants)) From 8b71d44db74d7c6082bba06e792d4b7529b3e5ba Mon Sep 17 00:00:00 2001 From: Joshua Higgins Date: Fri, 12 Apr 2024 13:19:43 -0400 Subject: [PATCH 05/13] Send Rich Links still trying to implement recieving --- imessage/bluebubbles/api.go | 57 ++++++++++++++++++++++++++++++- imessage/bluebubbles/interface.go | 38 ++++++++++++++++++++- 2 files changed, 93 insertions(+), 2 deletions(-) diff --git a/imessage/bluebubbles/api.go b/imessage/bluebubbles/api.go index cf227e27..f7adb719 100644 --- a/imessage/bluebubbles/api.go +++ b/imessage/bluebubbles/api.go @@ -1107,6 +1107,7 @@ func (bb *blueBubbles) SendMessage(chatID, text string, replyTo string, replyToP TempGUID: fmt.Sprintf("temp-%s", RandString(8)), SelectedMessageGUID: replyTo, PartIndex: replyToPart, + DDScan: bb.usingPrivateAPI, } var res SendTextResponse @@ -1688,6 +1689,9 @@ func (bb *blueBubbles) convertBBMessageToiMessage(bbMessage Message) (*imessage. // Attachments message.Attachments = make([]*imessage.Attachment, len(bbMessage.Attachments)) for i, attachment := range bbMessage.Attachments { + if attachment.HideAttachment { + continue + } attachment, err := bb.downloadAttachment(attachment.GUID) if err != nil { bb.log.Err(err).Str("attachmentGUID", attachment.GUID).Msg("Failed to download attachment") @@ -1705,14 +1709,65 @@ func (bb *blueBubbles) convertBBMessageToiMessage(bbMessage Message) (*imessage. message.GroupActionType = imessage.GroupActionType(bbMessage.GroupActionType) message.NewGroupName = bbMessage.GroupTitle + for _, attachment := range bbMessage.Attachments { + bb.bridge.GetLog().Errorfln("%+v", attachment) + } + // TODO Richlinks // message.RichLink = + if IsUrl(bbMessage.Text) && bbMessage.HasPayloadData { + //richlinkJSON, _ := bbMessage.PayloadData.(string) + //rlJson, _ := json.Marshal(&bbMessage.PayloadData) + //bb.bridge.GetLog().Errorln(string(rlJson)) + //var payload PayloadData = bbMessage.PayloadData[0] + + bb.bridge.GetLog().Errorfln("%+v", bbMessage.PayloadData) + // err := json.Unmarshal(rlJson, &richlink) + // if err != nil { + // bb.bridge.GetLog().Errorfln("FUCK, %v", err) + // } + + bb.bridge.GetLog().Errorln("CONVERTING....") + // message.RichLink.OriginalURL = payload.UrlData[0].OriginalUrl.NSRelative + // message.RichLink.URL = payload.UrlData[0].Url.NSRelative + // message.RichLink.Title = payload.UrlData[0].Title + // message.RichLink.Summary = payload.UrlData[0].Summary + // // message.RichLink.SelectedText + // message.RichLink.SiteName = payload.UrlData[0].SiteName + // // message.RichLink.RelatedURL + // // message.RichLink.Creator + // // message.RichLink.CreatorFacebookProfile + // // message.RichLink.CreatorTwitterUsername + // message.RichLink.ItemType = payload.UrlData[0].ItemType + + // message.RichLink.Icon.OriginalURL = payload.UrlData[0].IconMetadata.Url.NSRelative + // message.RichLink.Icon.Source.URL = payload.UrlData[0].IconMetadata.Url.NSRelative + // // message.RichLink.Icon.Size //Size always seems to be 0,0 from BB + + // message.RichLink.Image.OriginalURL = payload.UrlData[0].ImageMetadata.Url.NSRelative + // message.RichLink.Image.Source.URL = payload.UrlData[0].ImageMetadata.Url.NSRelative + // // message.RichLink.Image.Size + + // message.RichLink.Video.Asset.OriginalURL = payload.UrlData[0].VideoMetadata.Url.NSRelative + // message.RichLink.Video.Asset.Source.URL = payload.UrlData[0].VideoMetadata.Url.NSRelative + // message.RichLink.Video.YouTubeURL = payload.UrlData[0].VideoMetadata.Url.NSRelative + // message.RichLink.Video.StreamingURL = payload.UrlData[0].VideoMetadata.Url.NSRelative + // message.RichLink.Video.Size + bb.bridge.GetLog().Errorln("THERE WAS AN ATTACHMENT!!! " + bbMessage.Text + " - " + strconv.Itoa(len(bbMessage.Attachments))) + //bb.bridge.GetLog().Errorln(bbMessage.) + } + message.ThreadID = bbMessage.ThreadOriginatorGUID return &message, nil } +func IsUrl(str string) bool { + u, err := url.Parse(str) + return err == nil && u.Scheme != "" && u.Host != "" +} + func (bb *blueBubbles) convertBBChatToiMessageChat(bbChat Chat) (*imessage.ChatInfo, error) { members := make([]string, len(bbChat.Participants)) @@ -1880,7 +1935,7 @@ func (bb *blueBubbles) Capabilities() imessage.ConnectorCapabilities { MessageStatusCheckpoints: false, DeliveredStatus: bb.usingPrivateAPI, ContactChatMerging: false, - RichLinks: false, + RichLinks: true, //RichLinks: bb.usingPrivateAPI, ChatBridgeResult: false, } } diff --git a/imessage/bluebubbles/interface.go b/imessage/bluebubbles/interface.go index f9c12bd4..fd3cc351 100644 --- a/imessage/bluebubbles/interface.go +++ b/imessage/bluebubbles/interface.go @@ -175,7 +175,7 @@ type Message struct { OriginalROWID int `json:"originalROWID,omitempty"` OtherHandle int `json:"otherHandle,omitempty"` PartCount int `json:"partCount,omitempty"` - PayloadData any `json:"payloadData,omitempty"` + PayloadData []PayloadData `json:"payloadData,omitempty"` ReplyToGUID string `json:"replyToGuid,omitempty"` ShareDirection int `json:"shareDirection,omitempty"` ShareStatus int `json:"shareStatus,omitempty"` @@ -205,6 +205,41 @@ type Attachment struct { Metadata any `json:"metadata,omitempty"` } +type PayloadData struct { + Version int `json:"$version,omitempty"` + Archiver string `json:"$archiver,omitempty"` + Top any `json:"$top,omitempty"` + Objects []any `json:"$objects,omitempty"` +} + + +// type PayloadData struct { +// Type int `json:"type,omitempty"` +// UrlData URLData `json:"urlData,omitempty"` +// AppData any `json:"appData,omitempty"` +// } + +// type URLData struct { +// ImageMetadata Metadata `json:"imageMetadata,omitempty"` +// VideoMetadata Metadata `json:"videoMetadata,omitempty"` +// IconMetadata Metadata `json:"iconMetadata,omitempty"` +// ItemType string `json:"itemType,omitempty"` +// OriginalUrl URL `json:"originalURL,omitempty"` +// Url URL `json:"URL,omitempty"` +// Title string `json:"title,omitempty"` +// Summary string `json:"summary,omitempty"` +// SiteName string `json:"siteName,omitempty"` +// } + +// type Metadata struct { +// Size string `json:"size,omitempty"` +// Url URL `json:"URL,omitempty"` +// } + +// type URL struct { +// NSRelative string `json:"NS.relative,omitempty"` +// } + type AttachmentResponse struct { Status int64 `json:"status"` Message string `json:"message"` @@ -242,6 +277,7 @@ type SendTextRequest struct { Subject string `json:"subject,omitempty"` SelectedMessageGUID string `json:"selectedMessageGuid,omitempty"` PartIndex int `json:"partIndex,omitempty"` + DDScan bool `json:"ddScan,omitempty"` } type UnsendMessage struct { From add11e09d8eaf4daaae7f77f91b45cba813f9a1f Mon Sep 17 00:00:00 2001 From: Joshua Higgins Date: Fri, 12 Apr 2024 15:17:09 -0400 Subject: [PATCH 06/13] Fix for inbox sorting save the last message for the end --- historysync.go | 68 ++++++++++++++++++++++++++++++++++---------------- 1 file changed, 47 insertions(+), 21 deletions(-) diff --git a/historysync.go b/historysync.go index f8b4e4b9..003c036c 100644 --- a/historysync.go +++ b/historysync.go @@ -167,6 +167,12 @@ func (portal *Portal) convertBackfill(messages []*imessage.Message) ([]*event.Ev continue } + //Skip the last message in the array, we will add it later to correct inbox sorting + if msg == messages[len(messages)-1] && len(messages) > 1 /* we call this function again with one element in the array for the last message, so we'll want to process it */ { + portal.log.Debugln("Skipping message", msg.GUID, "in backfill, last one in the convo") + continue + } + converted := portal.convertiMessage(msg, intent) for index, conv := range converted { evt := &event.Event{ @@ -204,17 +210,25 @@ func (portal *Portal) convertTapbacks(messages []*imessage.Message) ([]*event.Ev unreadThreshold := time.Duration(portal.bridge.Config.Bridge.Backfill.UnreadHoursThreshold) * time.Hour var isRead bool for _, msg := range messages { + //Only want tapbacks if msg.Tapback == nil { portal.log.Debugln("Skipping message", msg.GUID, "in backfill, should've already handled") continue } + //Skip the last message in the array, we will add it later to correct inbox sorting + if msg == messages[len(messages)-1] && len(messages) > 1 /* we call this function again with one element in the array for the last message, so we'll want to process it */ { + portal.log.Debugln("Skipping message", msg.GUID, "in backfill, last one in the convo") + continue + } + intent := portal.getIntentForMessage(msg, nil) if intent == nil { portal.log.Debugln("Skipping", msg.GUID, "in backfill (didn't get an intent)") continue } + //If we don't process it, there won't be a reaction; at least for BB, we never have to remove a reaction if msg.Tapback.Remove { continue } @@ -289,23 +303,9 @@ func (portal *Portal) sendBackfill(backfillID string, messages []*imessage.Messa if sendErr != nil { return false } + portal.addBackfillToDB(metas, eventIDs, idMap, backfillID) - for i, meta := range metas { - idMap[meta.GUID] = append(idMap[meta.GUID], eventIDs[i]) - } - txn, err := portal.bridge.DB.Begin() - if err != nil { - portal.log.Errorln("Failed to start transaction to save batch messages:", err) - return true - } - portal.log.Debugfln("Inserting %d event IDs to database to finish backfill %s", len(eventIDs), backfillID) - portal.finishBackfill(txn, eventIDs, metas) - portal.Update(txn) - err = txn.Commit() - if err != nil { - portal.log.Errorln("Failed to commit transaction to save batch messages:", err) - } - + //We have to process tapbacks after all other messages because we need texts in the DB in order to target them events, metas, metaIndexes, isRead, err = portal.convertTapbacks(messages) if err != nil { portal.log.Errorfln("Failed to convert tapbacks for backfill: %v", err) @@ -320,14 +320,43 @@ func (portal *Portal) sendBackfill(backfillID string, messages []*imessage.Messa if sendErr != nil { return false } + portal.addBackfillToDB(metas, eventIDs, idMap, backfillID) + //Process the last message in the conversation that we skipped before in converting, + // this is dumb but Beeper sorts its inbox on when the event was recieved and not the timestamp + var lastMessage *imessage.Message = messages[len(messages)-1] + var lastMessages []*imessage.Message + lastMessages = append(lastMessages, lastMessage) + if lastMessage.Tapback == nil { + events, metas, metaIndexes, isRead, err = portal.convertBackfill(lastMessages) + if err != nil { + portal.log.Errorfln("Failed to convert messages for backfill: %v", err) + return false + } + } else { + events, metas, metaIndexes, isRead, err = portal.convertTapbacks(lastMessages) + if err != nil { + portal.log.Errorfln("Failed to convert tapbacks for backfill: %v", err) + return false + } + } + eventIDs, sendErr = portal.sendBackfillToMatrixServer(batchSending, forward, forwardIfNoMessages, markAsRead, isRead, events, metas, metaIndexes) + if sendErr != nil { + return false + } + portal.addBackfillToDB(metas, eventIDs, idMap, backfillID) + + portal.log.Infofln("Finished backfill %s", backfillID) + return true +} + +func (portal *Portal) addBackfillToDB(metas []messageWithIndex, eventIDs []id.EventID, idMap map[string][]id.EventID, backfillID string) { for i, meta := range metas { idMap[meta.GUID] = append(idMap[meta.GUID], eventIDs[i]) } - txn, err = portal.bridge.DB.Begin() + txn, err := portal.bridge.DB.Begin() if err != nil { portal.log.Errorln("Failed to start transaction to save batch messages:", err) - return true } portal.log.Debugfln("Inserting %d event IDs to database to finish backfill %s", len(eventIDs), backfillID) portal.finishBackfill(txn, eventIDs, metas) @@ -336,9 +365,6 @@ func (portal *Portal) sendBackfill(backfillID string, messages []*imessage.Messa if err != nil { portal.log.Errorln("Failed to commit transaction to save batch messages:", err) } - - portal.log.Infofln("Finished backfill %s", backfillID) - return true } func (portal *Portal) sendBackfillToMatrixServer(batchSending, forward, forwardIfNoMessages, markAsRead, isRead bool, events []*event.Event, metas []messageWithIndex, From a564640861542d1123e80e1f5d0240d7b3fa245e Mon Sep 17 00:00:00 2001 From: Joshua Higgins Date: Fri, 12 Apr 2024 17:24:08 -0400 Subject: [PATCH 07/13] Fix for sometimes missing messages --- historysync.go | 94 ++++++++++++++++++++++++++------------------------ 1 file changed, 48 insertions(+), 46 deletions(-) diff --git a/historysync.go b/historysync.go index 003c036c..0d551aad 100644 --- a/historysync.go +++ b/historysync.go @@ -145,23 +145,14 @@ type messageIndex struct { Index int } -func (portal *Portal) convertBackfill(messages []*imessage.Message) ([]*event.Event, []messageWithIndex, map[messageIndex]int, bool, error) { +func (portal *Portal) convertBackfill(messages []*imessage.Message) ([]*event.Event, []messageWithIndex, map[messageIndex]int, bool, *imessage.Message, error) { events := make([]*event.Event, 0, len(messages)) metas := make([]messageWithIndex, 0, len(messages)) metaIndexes := make(map[messageIndex]int, len(messages)) unreadThreshold := time.Duration(portal.bridge.Config.Bridge.Backfill.UnreadHoursThreshold) * time.Hour var isRead bool + var lastMessage *imessage.Message for _, msg := range messages { - if msg.ItemType != imessage.ItemTypeMessage && msg.Tapback == nil { - portal.log.Debugln("Skipping", msg.GUID, "in backfill (not a message)") - continue - } - intent := portal.getIntentForMessage(msg, nil) - if intent == nil { - portal.log.Debugln("Skipping", msg.GUID, "in backfill (didn't get an intent)") - continue - } - if msg.Tapback != nil { portal.log.Debugln("Skipping tapback", msg.GUID, "in backfill, handling later") continue @@ -169,10 +160,12 @@ func (portal *Portal) convertBackfill(messages []*imessage.Message) ([]*event.Ev //Skip the last message in the array, we will add it later to correct inbox sorting if msg == messages[len(messages)-1] && len(messages) > 1 /* we call this function again with one element in the array for the last message, so we'll want to process it */ { - portal.log.Debugln("Skipping message", msg.GUID, "in backfill, last one in the convo") + portal.log.Errorln("Skipping message", msg.GUID, "in backfill, last one in the convo") + lastMessage = msg continue } + intent := portal.getIntentForMessage(msg, nil) converted := portal.convertiMessage(msg, intent) for index, conv := range converted { evt := &event.Event{ @@ -187,7 +180,7 @@ func (portal *Portal) convertBackfill(messages []*imessage.Message) ([]*event.Ev var err error evt.Type, err = portal.encrypt(intent, &evt.Content, evt.Type) if err != nil { - return nil, nil, nil, false, err + return nil, nil, nil, false, nil, err } intent.AddDoublePuppetValue(&evt.Content) if portal.bridge.Config.Homeserver.Software == bridgeconfig.SoftwareHungry { @@ -200,15 +193,16 @@ func (portal *Portal) convertBackfill(messages []*imessage.Message) ([]*event.Ev } isRead = msg.IsRead || msg.IsFromMe || (unreadThreshold >= 0 && time.Since(msg.Time) > unreadThreshold) } - return events, metas, metaIndexes, isRead, nil + return events, metas, metaIndexes, isRead, lastMessage, nil } -func (portal *Portal) convertTapbacks(messages []*imessage.Message) ([]*event.Event, []messageWithIndex, map[messageIndex]int, bool, error) { +func (portal *Portal) convertTapbacks(messages []*imessage.Message) ([]*event.Event, []messageWithIndex, map[messageIndex]int, bool, *imessage.Message, error) { events := make([]*event.Event, 0, len(messages)) metas := make([]messageWithIndex, 0, len(messages)) metaIndexes := make(map[messageIndex]int, len(messages)) unreadThreshold := time.Duration(portal.bridge.Config.Bridge.Backfill.UnreadHoursThreshold) * time.Hour var isRead bool + var lastMessage *imessage.Message for _, msg := range messages { //Only want tapbacks if msg.Tapback == nil { @@ -216,23 +210,19 @@ func (portal *Portal) convertTapbacks(messages []*imessage.Message) ([]*event.Ev continue } - //Skip the last message in the array, we will add it later to correct inbox sorting - if msg == messages[len(messages)-1] && len(messages) > 1 /* we call this function again with one element in the array for the last message, so we'll want to process it */ { - portal.log.Debugln("Skipping message", msg.GUID, "in backfill, last one in the convo") - continue - } - - intent := portal.getIntentForMessage(msg, nil) - if intent == nil { - portal.log.Debugln("Skipping", msg.GUID, "in backfill (didn't get an intent)") + //If we don't process it, there won't be a reaction; at least for BB, we never have to remove a reaction + if msg.Tapback.Remove { continue } - //If we don't process it, there won't be a reaction; at least for BB, we never have to remove a reaction - if msg.Tapback.Remove { + //Skip the last message in the array, we will add it later to correct inbox sorting + if msg == messages[len(messages)-1] && len(messages) > 1 /* we call this function again with one element in the array for the last message, so we'll want to process it */ { + portal.log.Errorln("Skipping message", msg.GUID, "in backfill, last one in the convo") + lastMessage = msg continue } + intent := portal.getIntentForMessage(msg, nil) dbMessage := portal.bridge.DB.Message.GetByGUID(portal.GUID, msg.Tapback.TargetGUID, msg.Tapback.TargetPart) if dbMessage == nil { //TODO BUG: This occurs when trying to find the target reaction for a rich link, related to #183 @@ -258,7 +248,7 @@ func (portal *Portal) convertTapbacks(messages []*imessage.Message) ([]*event.Ev var err error evt.Type, err = portal.encrypt(intent, &evt.Content, evt.Type) if err != nil { - return nil, nil, nil, false, err + return nil, nil, nil, false, nil, err } intent.AddDoublePuppetValue(&evt.Content) if portal.bridge.Config.Homeserver.Software == bridgeconfig.SoftwareHungry { @@ -271,7 +261,7 @@ func (portal *Portal) convertTapbacks(messages []*imessage.Message) ([]*event.Ev isRead = msg.IsRead || msg.IsFromMe || (unreadThreshold >= 0 && time.Since(msg.Time) > unreadThreshold) } - return events, metas, metaIndexes, isRead, nil + return events, metas, metaIndexes, isRead, lastMessage, nil } func (portal *Portal) sendBackfill(backfillID string, messages []*imessage.Message, forward, forwardIfNoMessages, markAsRead bool) (success bool) { @@ -289,7 +279,22 @@ func (portal *Portal) sendBackfill(backfillID string, messages []*imessage.Messa }() batchSending := portal.bridge.SpecVersions.Supports(mautrix.BeeperFeatureBatchSending) - events, metas, metaIndexes, isRead, err := portal.convertBackfill(messages) + var validMessages []*imessage.Message + for _, msg := range messages { + if msg.ItemType != imessage.ItemTypeMessage && msg.Tapback == nil { + portal.log.Debugln("Skipping", msg.GUID, "in backfill (not a message)") + continue + } + intent := portal.getIntentForMessage(msg, nil) + if intent == nil { + portal.log.Debugln("Skipping", msg.GUID, "in backfill (didn't get an intent)") + continue + } + + validMessages = append(validMessages, msg) + } + + events, metas, metaIndexes, isRead, lastMessage, err := portal.convertBackfill(validMessages) if err != nil { portal.log.Errorfln("Failed to convert messages for backfill: %v", err) return false @@ -306,7 +311,7 @@ func (portal *Portal) sendBackfill(backfillID string, messages []*imessage.Messa portal.addBackfillToDB(metas, eventIDs, idMap, backfillID) //We have to process tapbacks after all other messages because we need texts in the DB in order to target them - events, metas, metaIndexes, isRead, err = portal.convertTapbacks(messages) + events, metas, metaIndexes, isRead, lastTapback, err := portal.convertTapbacks(validMessages) if err != nil { portal.log.Errorfln("Failed to convert tapbacks for backfill: %v", err) return false @@ -322,24 +327,21 @@ func (portal *Portal) sendBackfill(backfillID string, messages []*imessage.Messa } portal.addBackfillToDB(metas, eventIDs, idMap, backfillID) - //Process the last message in the conversation that we skipped before in converting, + //Process the last message in the conversation that we skipped before in converting, // this is dumb but Beeper sorts its inbox on when the event was recieved and not the timestamp - var lastMessage *imessage.Message = messages[len(messages)-1] - var lastMessages []*imessage.Message - lastMessages = append(lastMessages, lastMessage) - if lastMessage.Tapback == nil { - events, metas, metaIndexes, isRead, err = portal.convertBackfill(lastMessages) - if err != nil { - portal.log.Errorfln("Failed to convert messages for backfill: %v", err) - return false - } - } else { - events, metas, metaIndexes, isRead, err = portal.convertTapbacks(lastMessages) - if err != nil { - portal.log.Errorfln("Failed to convert tapbacks for backfill: %v", err) - return false - } + var lastMessageArray []*imessage.Message + if lastMessage != nil { + lastMessageArray = append(lastMessageArray, lastMessage) + events, metas, metaIndexes, isRead, _, err = portal.convertBackfill(lastMessageArray) + } else if lastTapback != nil { + lastMessageArray = append(lastMessageArray, lastTapback) + events, metas, metaIndexes, isRead, _, err = portal.convertTapbacks(lastMessageArray) + } + if err != nil { + portal.log.Errorfln("Failed to convert last message for backfill: %v", err) + return false } + eventIDs, sendErr = portal.sendBackfillToMatrixServer(batchSending, forward, forwardIfNoMessages, markAsRead, isRead, events, metas, metaIndexes) if sendErr != nil { return false From 90e85360560d2639799cefe68dd0ac7c71c40537 Mon Sep 17 00:00:00 2001 From: Joshua Higgins Date: Sat, 13 Apr 2024 23:50:05 -0400 Subject: [PATCH 08/13] Show Richlinks --- go.mod | 1 + go.sum | 8 ++ imessage/bluebubbles/api.go | 150 +++++++++++++++++++----------- imessage/bluebubbles/interface.go | 39 +------- 4 files changed, 107 insertions(+), 91 deletions(-) diff --git a/go.mod b/go.mod index 9f87f438..f9cb70d9 100644 --- a/go.mod +++ b/go.mod @@ -20,6 +20,7 @@ require ( require ( github.com/coreos/go-systemd/v22 v22.5.0 // indirect + github.com/dyatlov/go-opengraph/opengraph v0.0.0-20220524092352-606d7b1e5f8a github.com/gorilla/mux v1.8.0 // indirect github.com/gorilla/websocket v1.5.0 // indirect github.com/lib/pq v1.10.9 // indirect diff --git a/go.sum b/go.sum index bd7942b3..321fd99a 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,8 @@ github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20O github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/dyatlov/go-opengraph/opengraph v0.0.0-20220524092352-606d7b1e5f8a h1:etIrTD8BQqzColk9nKRusM9um5+1q0iOEJLqfBMIK64= +github.com/dyatlov/go-opengraph/opengraph v0.0.0-20220524092352-606d7b1e5f8a/go.mod h1:emQhSYTXqB0xxjLITTw4EaWZ+8IIQYw+kx9GqNUKdLg= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= @@ -51,13 +53,19 @@ golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 h1:hNQpMuAJe5CtcUqCXaWga3FHu golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08= golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8= golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE= +golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= diff --git a/imessage/bluebubbles/api.go b/imessage/bluebubbles/api.go index f7adb719..f89b3cd2 100644 --- a/imessage/bluebubbles/api.go +++ b/imessage/bluebubbles/api.go @@ -10,6 +10,7 @@ import ( "math" "math/rand" "mime/multipart" + "net" "net/http" "net/url" "os" @@ -19,6 +20,7 @@ import ( "time" "unicode" + "github.com/dyatlov/go-opengraph/opengraph" "github.com/gorilla/websocket" "github.com/rs/zerolog" "github.com/sahilm/fuzzy" @@ -1689,9 +1691,6 @@ func (bb *blueBubbles) convertBBMessageToiMessage(bbMessage Message) (*imessage. // Attachments message.Attachments = make([]*imessage.Attachment, len(bbMessage.Attachments)) for i, attachment := range bbMessage.Attachments { - if attachment.HideAttachment { - continue - } attachment, err := bb.downloadAttachment(attachment.GUID) if err != nil { bb.log.Err(err).Str("attachmentGUID", attachment.GUID).Msg("Failed to download attachment") @@ -1709,63 +1708,105 @@ func (bb *blueBubbles) convertBBMessageToiMessage(bbMessage Message) (*imessage. message.GroupActionType = imessage.GroupActionType(bbMessage.GroupActionType) message.NewGroupName = bbMessage.GroupTitle - for _, attachment := range bbMessage.Attachments { - bb.bridge.GetLog().Errorfln("%+v", attachment) - } - - // TODO Richlinks - // message.RichLink = - - if IsUrl(bbMessage.Text) && bbMessage.HasPayloadData { - //richlinkJSON, _ := bbMessage.PayloadData.(string) - //rlJson, _ := json.Marshal(&bbMessage.PayloadData) - //bb.bridge.GetLog().Errorln(string(rlJson)) - //var payload PayloadData = bbMessage.PayloadData[0] - - bb.bridge.GetLog().Errorfln("%+v", bbMessage.PayloadData) - // err := json.Unmarshal(rlJson, &richlink) - // if err != nil { - // bb.bridge.GetLog().Errorfln("FUCK, %v", err) - // } - - bb.bridge.GetLog().Errorln("CONVERTING....") - // message.RichLink.OriginalURL = payload.UrlData[0].OriginalUrl.NSRelative - // message.RichLink.URL = payload.UrlData[0].Url.NSRelative - // message.RichLink.Title = payload.UrlData[0].Title - // message.RichLink.Summary = payload.UrlData[0].Summary - // // message.RichLink.SelectedText - // message.RichLink.SiteName = payload.UrlData[0].SiteName - // // message.RichLink.RelatedURL - // // message.RichLink.Creator - // // message.RichLink.CreatorFacebookProfile - // // message.RichLink.CreatorTwitterUsername - // message.RichLink.ItemType = payload.UrlData[0].ItemType - - // message.RichLink.Icon.OriginalURL = payload.UrlData[0].IconMetadata.Url.NSRelative - // message.RichLink.Icon.Source.URL = payload.UrlData[0].IconMetadata.Url.NSRelative - // // message.RichLink.Icon.Size //Size always seems to be 0,0 from BB - - // message.RichLink.Image.OriginalURL = payload.UrlData[0].ImageMetadata.Url.NSRelative - // message.RichLink.Image.Source.URL = payload.UrlData[0].ImageMetadata.Url.NSRelative - // // message.RichLink.Image.Size - - // message.RichLink.Video.Asset.OriginalURL = payload.UrlData[0].VideoMetadata.Url.NSRelative - // message.RichLink.Video.Asset.Source.URL = payload.UrlData[0].VideoMetadata.Url.NSRelative - // message.RichLink.Video.YouTubeURL = payload.UrlData[0].VideoMetadata.Url.NSRelative - // message.RichLink.Video.StreamingURL = payload.UrlData[0].VideoMetadata.Url.NSRelative - // message.RichLink.Video.Size - bb.bridge.GetLog().Errorln("THERE WAS AN ATTACHMENT!!! " + bbMessage.Text + " - " + strconv.Itoa(len(bbMessage.Attachments))) - //bb.bridge.GetLog().Errorln(bbMessage.) - } - message.ThreadID = bbMessage.ThreadOriginatorGUID + if IsUrl(bbMessage.Text) && bbMessage.HasDDResults && len(message.Attachments) > 0 { + var reader io.Reader + resp, err := http.Get(bbMessage.Text) + if err != nil { + bb.bridge.GetLog().Errorfln("Error while fetching url: %v", err) + return &message, nil + } + reader = resp.Body + + og := opengraph.NewOpenGraph() + if err := og.ProcessHTML(reader); err != nil { + bb.bridge.GetLog().Errorfln("Error processing html: %v", err) + return &message, nil + } + + if og != nil && og.SiteName != "" { + message.RichLink = &imessage.RichLink{} + message.RichLink.OriginalURL = og.URL + message.RichLink.URL = og.URL + message.RichLink.SiteName = og.SiteName + message.RichLink.Title = og.Title + if og.Description != "" { + message.RichLink.Summary = og.Description + } else { + message.RichLink.Summary = og.URL + } + if og.Profile != nil { + message.RichLink.Creator = og.Profile.Username + } + + var icon []byte + var err error + if len(message.Attachments) > 0 && message.Attachments[0] != nil { + icon, err = message.Attachments[0].Read() + } + if err == nil { + message.RichLink.Icon = &imessage.RichLinkAsset{} + //message.RichLink.Icon.OriginalURL = og.URL + "/favicon.ico" + message.RichLink.Icon.Source = &imessage.RichLinkAssetSource{} + message.RichLink.Icon.Source.Data = icon + //message.RichLink.Icon.Source.URL = og.URL + "/favicon.ico" + } + + if len(og.Images) > 0 { + message.RichLink.Image = &imessage.RichLinkAsset{} + message.RichLink.Image.OriginalURL = og.Images[0].URL + var image []byte + var err error + if len(message.Attachments) > 1 && message.Attachments[1] != nil { + image, err = message.Attachments[1].Read() + } + if err == nil && image != nil { + message.RichLink.Image.Source = &imessage.RichLinkAssetSource{} + message.RichLink.Image.Source.URL = og.Images[0].URL + message.RichLink.Image.Source.Data = image + } + message.RichLink.Image.MimeType = og.Images[0].Type + message.RichLink.Image.Size = &imessage.RichLinkAssetSize{} + message.RichLink.Image.Size.Height = float64(og.Images[0].Height) + message.RichLink.Image.Size.Width = float64(og.Images[0].Width) + } + + if len(og.Videos) > 0 { + message.RichLink.Video = &imessage.RichLinkVideoAsset{} + message.RichLink.Video.StreamingURL = og.Videos[0].URL + message.RichLink.Video.YouTubeURL = og.Videos[0].URL + message.RichLink.Video.Asset.OriginalURL = og.Videos[0].URL + message.RichLink.Video.Asset.Source = &imessage.RichLinkAssetSource{} + message.RichLink.Video.Asset.Source.URL = og.Videos[0].URL + message.RichLink.Video.Asset.MimeType = og.Videos[0].Type + message.RichLink.Video.Asset.Size = &imessage.RichLinkAssetSize{} + message.RichLink.Video.Asset.Size.Height = float64(og.Videos[0].Height) + message.RichLink.Video.Asset.Size.Width = float64(og.Videos[0].Width) + } + } + resp.Body.Close() + + //Remove the attachments iMessage sent to display the link, we want to remove them even if we don't have richlink data + message.Attachments = make([]*imessage.Attachment, 0) + } + return &message, nil } func IsUrl(str string) bool { - u, err := url.Parse(str) - return err == nil && u.Scheme != "" && u.Host != "" + url, err := url.ParseRequestURI(str) + if err != nil { + return false + } + + address := net.ParseIP(url.Host) + + if address == nil { + return strings.Contains(url.Host, ".") + } + + return true } func (bb *blueBubbles) convertBBChatToiMessageChat(bbChat Chat) (*imessage.ChatInfo, error) { @@ -1823,6 +1864,7 @@ func (bb *blueBubbles) downloadAttachment(guid string) (attachment *imessage.Att PathOnDisk: tempFile.Name(), FileName: attachmentResponse.Data.TransferName, MimeType: attachmentResponse.Data.MimeType, + //HideAttachment: attachmentResponse.Data.HideAttachment, }, nil } diff --git a/imessage/bluebubbles/interface.go b/imessage/bluebubbles/interface.go index fd3cc351..08a37896 100644 --- a/imessage/bluebubbles/interface.go +++ b/imessage/bluebubbles/interface.go @@ -175,7 +175,7 @@ type Message struct { OriginalROWID int `json:"originalROWID,omitempty"` OtherHandle int `json:"otherHandle,omitempty"` PartCount int `json:"partCount,omitempty"` - PayloadData []PayloadData `json:"payloadData,omitempty"` + PayloadData any `json:"payloadData,omitempty"` ReplyToGUID string `json:"replyToGuid,omitempty"` ShareDirection int `json:"shareDirection,omitempty"` ShareStatus int `json:"shareStatus,omitempty"` @@ -205,41 +205,6 @@ type Attachment struct { Metadata any `json:"metadata,omitempty"` } -type PayloadData struct { - Version int `json:"$version,omitempty"` - Archiver string `json:"$archiver,omitempty"` - Top any `json:"$top,omitempty"` - Objects []any `json:"$objects,omitempty"` -} - - -// type PayloadData struct { -// Type int `json:"type,omitempty"` -// UrlData URLData `json:"urlData,omitempty"` -// AppData any `json:"appData,omitempty"` -// } - -// type URLData struct { -// ImageMetadata Metadata `json:"imageMetadata,omitempty"` -// VideoMetadata Metadata `json:"videoMetadata,omitempty"` -// IconMetadata Metadata `json:"iconMetadata,omitempty"` -// ItemType string `json:"itemType,omitempty"` -// OriginalUrl URL `json:"originalURL,omitempty"` -// Url URL `json:"URL,omitempty"` -// Title string `json:"title,omitempty"` -// Summary string `json:"summary,omitempty"` -// SiteName string `json:"siteName,omitempty"` -// } - -// type Metadata struct { -// Size string `json:"size,omitempty"` -// Url URL `json:"URL,omitempty"` -// } - -// type URL struct { -// NSRelative string `json:"NS.relative,omitempty"` -// } - type AttachmentResponse struct { Status int64 `json:"status"` Message string `json:"message"` @@ -277,7 +242,7 @@ type SendTextRequest struct { Subject string `json:"subject,omitempty"` SelectedMessageGUID string `json:"selectedMessageGuid,omitempty"` PartIndex int `json:"partIndex,omitempty"` - DDScan bool `json:"ddScan,omitempty"` + DDScan bool `json:"ddScan,omitempty"` } type UnsendMessage struct { From 1be94c3a71176f88f6d78c694e2a09eb7b19ee9e Mon Sep 17 00:00:00 2001 From: Joshua Higgins Date: Sun, 14 Apr 2024 00:00:57 -0400 Subject: [PATCH 09/13] Code comments --- imessage/bluebubbles/api.go | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/imessage/bluebubbles/api.go b/imessage/bluebubbles/api.go index f89b3cd2..7b411d47 100644 --- a/imessage/bluebubbles/api.go +++ b/imessage/bluebubbles/api.go @@ -1710,6 +1710,7 @@ func (bb *blueBubbles) convertBBMessageToiMessage(bbMessage Message) (*imessage. message.ThreadID = bbMessage.ThreadOriginatorGUID + // Was the text a URL, does BB have a ddScan for it, did it come with any attachements because of the scan if IsUrl(bbMessage.Text) && bbMessage.HasDDResults && len(message.Attachments) > 0 { var reader io.Reader resp, err := http.Get(bbMessage.Text) @@ -1719,12 +1720,17 @@ func (bb *blueBubbles) convertBBMessageToiMessage(bbMessage Message) (*imessage. } reader = resp.Body + // Fetch the data ourselves because the payloadData object is raw from the iMessage DB, from BB. + // That would require a ton of parsing that the BB app does client side to become something useable but it's written in Dart + // See https://github.com/BlueBubblesApp/bluebubbles-app/blob/d0257c9080e82140602340b48f85fa148721553c/lib/models/global/payload_data.dart og := opengraph.NewOpenGraph() if err := og.ProcessHTML(reader); err != nil { bb.bridge.GetLog().Errorfln("Error processing html: %v", err) return &message, nil } + // Was there any OpenGraph tags? Sometimes ddScan is able to produce data without them + // But this is the compromise for not wanting to parse payloadData if og != nil && og.SiteName != "" { message.RichLink = &imessage.RichLink{} message.RichLink.OriginalURL = og.URL @@ -1740,6 +1746,7 @@ func (bb *blueBubbles) convertBBMessageToiMessage(bbMessage Message) (*imessage. message.RichLink.Creator = og.Profile.Username } + // It's always assumed that the first attachment is the icon, the second is the banenr image var icon []byte var err error if len(message.Attachments) > 0 && message.Attachments[0] != nil { @@ -1747,8 +1754,8 @@ func (bb *blueBubbles) convertBBMessageToiMessage(bbMessage Message) (*imessage. } if err == nil { message.RichLink.Icon = &imessage.RichLinkAsset{} - //message.RichLink.Icon.OriginalURL = og.URL + "/favicon.ico" - message.RichLink.Icon.Source = &imessage.RichLinkAssetSource{} + //message.RichLink.Icon.OriginalURL = og.URL + "/favicon.ico" // Don't add URLs, if the icon is empty then it wil attempt to fetch the icon + message.RichLink.Icon.Source = &imessage.RichLinkAssetSource{} // But the library doesn't provide anything for an icon message.RichLink.Icon.Source.Data = icon //message.RichLink.Icon.Source.URL = og.URL + "/favicon.ico" } @@ -1787,7 +1794,8 @@ func (bb *blueBubbles) convertBBMessageToiMessage(bbMessage Message) (*imessage. } resp.Body.Close() - //Remove the attachments iMessage sent to display the link, we want to remove them even if we don't have richlink data + // Remove the attachments iMessage sent to display the link + // We want to remove them even if we don't have OpenGraph data because ddScan probably sent attachments message.Attachments = make([]*imessage.Attachment, 0) } From 2e76135460489106a0b3d2269aa2eb8e0826a763 Mon Sep 17 00:00:00 2001 From: Joshua Higgins Date: Sun, 14 Apr 2024 00:04:49 -0400 Subject: [PATCH 10/13] Fix for if a remove tapback was the last message --- historysync.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/historysync.go b/historysync.go index 0d551aad..e94afdc4 100644 --- a/historysync.go +++ b/historysync.go @@ -210,11 +210,6 @@ func (portal *Portal) convertTapbacks(messages []*imessage.Message) ([]*event.Ev continue } - //If we don't process it, there won't be a reaction; at least for BB, we never have to remove a reaction - if msg.Tapback.Remove { - continue - } - //Skip the last message in the array, we will add it later to correct inbox sorting if msg == messages[len(messages)-1] && len(messages) > 1 /* we call this function again with one element in the array for the last message, so we'll want to process it */ { portal.log.Errorln("Skipping message", msg.GUID, "in backfill, last one in the convo") @@ -290,6 +285,13 @@ func (portal *Portal) sendBackfill(backfillID string, messages []*imessage.Messa portal.log.Debugln("Skipping", msg.GUID, "in backfill (didn't get an intent)") continue } + if msg.Tapback != nil && msg.Tapback.Remove { + //If we don't process it, there won't be a reaction; at least for BB, we never have to remove a reaction + if msg.Tapback.Remove { + portal.log.Debugln("Skipping", msg.GUID, "in backfill (it was a remove tapback)") + continue + } + } validMessages = append(validMessages, msg) } From 1acded018c711f22ffc03bd45e27d47ab0a59c71 Mon Sep 17 00:00:00 2001 From: Joshua Higgins Date: Sun, 14 Apr 2024 01:11:41 -0400 Subject: [PATCH 11/13] Cleanup unused --- imessage/bluebubbles/api.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/imessage/bluebubbles/api.go b/imessage/bluebubbles/api.go index 7b411d47..3efa6393 100644 --- a/imessage/bluebubbles/api.go +++ b/imessage/bluebubbles/api.go @@ -1872,7 +1872,6 @@ func (bb *blueBubbles) downloadAttachment(guid string) (attachment *imessage.Att PathOnDisk: tempFile.Name(), FileName: attachmentResponse.Data.TransferName, MimeType: attachmentResponse.Data.MimeType, - //HideAttachment: attachmentResponse.Data.HideAttachment, }, nil } @@ -1985,7 +1984,7 @@ func (bb *blueBubbles) Capabilities() imessage.ConnectorCapabilities { MessageStatusCheckpoints: false, DeliveredStatus: bb.usingPrivateAPI, ContactChatMerging: false, - RichLinks: true, //RichLinks: bb.usingPrivateAPI, + RichLinks: bb.usingPrivateAPI, ChatBridgeResult: false, } } From 4a4b4fb36c7ab7b72ab79f636f08b2fc7b38b0a3 Mon Sep 17 00:00:00 2001 From: Joshua Higgins Date: Sun, 14 Apr 2024 05:24:38 +0000 Subject: [PATCH 12/13] Fix linting issues --- imessage/bluebubbles/api.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/imessage/bluebubbles/api.go b/imessage/bluebubbles/api.go index 3efa6393..302ff971 100644 --- a/imessage/bluebubbles/api.go +++ b/imessage/bluebubbles/api.go @@ -1754,8 +1754,10 @@ func (bb *blueBubbles) convertBBMessageToiMessage(bbMessage Message) (*imessage. } if err == nil { message.RichLink.Icon = &imessage.RichLinkAsset{} - //message.RichLink.Icon.OriginalURL = og.URL + "/favicon.ico" // Don't add URLs, if the icon is empty then it wil attempt to fetch the icon - message.RichLink.Icon.Source = &imessage.RichLinkAssetSource{} // But the library doesn't provide anything for an icon + // Don't add URLs, if the icon is empty then it wil attempt to fetch the icon with the URL + // The library used also doesn't provide anything for an icon + //message.RichLink.Icon.OriginalURL = og.URL + "/favicon.ico" + message.RichLink.Icon.Source = &imessage.RichLinkAssetSource{} message.RichLink.Icon.Source.Data = icon //message.RichLink.Icon.Source.URL = og.URL + "/favicon.ico" } From 253aaaeb7dc59a0b9658b2427387c4ea2f41326b Mon Sep 17 00:00:00 2001 From: Joshua Higgins Date: Sun, 14 Apr 2024 02:14:18 -0400 Subject: [PATCH 13/13] Revert "Merge branch 'testing-both' into rich-link-fix" This reverts commit ee5cf0e9831cb2f6e80fd0dec80d09f9cf1bd353, reversing changes made to 4a4b4fb36c7ab7b72ab79f636f08b2fc7b38b0a3. --- historysync.go | 227 ++++++++---------------------------- imessage/bluebubbles/api.go | 23 +--- 2 files changed, 48 insertions(+), 202 deletions(-) diff --git a/historysync.go b/historysync.go index e94afdc4..b1ad0565 100644 --- a/historysync.go +++ b/historysync.go @@ -145,27 +145,29 @@ type messageIndex struct { Index int } -func (portal *Portal) convertBackfill(messages []*imessage.Message) ([]*event.Event, []messageWithIndex, map[messageIndex]int, bool, *imessage.Message, error) { +func (portal *Portal) convertBackfill(messages []*imessage.Message) ([]*event.Event, []messageWithIndex, map[messageIndex]int, bool, error) { events := make([]*event.Event, 0, len(messages)) metas := make([]messageWithIndex, 0, len(messages)) metaIndexes := make(map[messageIndex]int, len(messages)) unreadThreshold := time.Duration(portal.bridge.Config.Bridge.Backfill.UnreadHoursThreshold) * time.Hour var isRead bool - var lastMessage *imessage.Message for _, msg := range messages { - if msg.Tapback != nil { - portal.log.Debugln("Skipping tapback", msg.GUID, "in backfill, handling later") + if msg.ItemType != imessage.ItemTypeMessage && msg.Tapback == nil { + portal.log.Debugln("Skipping", msg.GUID, "in backfill (not a message)") + continue + } + intent := portal.getIntentForMessage(msg, nil) + if intent == nil { + portal.log.Debugln("Skipping", msg.GUID, "in backfill (didn't get an intent)") continue } - //Skip the last message in the array, we will add it later to correct inbox sorting - if msg == messages[len(messages)-1] && len(messages) > 1 /* we call this function again with one element in the array for the last message, so we'll want to process it */ { - portal.log.Errorln("Skipping message", msg.GUID, "in backfill, last one in the convo") - lastMessage = msg + if msg.Tapback != nil { + // TODO handle tapbacks + portal.log.Debugln("Skipping tapback", msg.GUID, "in backfill") continue } - intent := portal.getIntentForMessage(msg, nil) converted := portal.convertiMessage(msg, intent) for index, conv := range converted { evt := &event.Event{ @@ -180,7 +182,7 @@ func (portal *Portal) convertBackfill(messages []*imessage.Message) ([]*event.Ev var err error evt.Type, err = portal.encrypt(intent, &evt.Content, evt.Type) if err != nil { - return nil, nil, nil, false, nil, err + return nil, nil, nil, false, err } intent.AddDoublePuppetValue(&evt.Content) if portal.bridge.Config.Homeserver.Software == bridgeconfig.SoftwareHungry { @@ -193,70 +195,7 @@ func (portal *Portal) convertBackfill(messages []*imessage.Message) ([]*event.Ev } isRead = msg.IsRead || msg.IsFromMe || (unreadThreshold >= 0 && time.Since(msg.Time) > unreadThreshold) } - return events, metas, metaIndexes, isRead, lastMessage, nil -} - -func (portal *Portal) convertTapbacks(messages []*imessage.Message) ([]*event.Event, []messageWithIndex, map[messageIndex]int, bool, *imessage.Message, error) { - events := make([]*event.Event, 0, len(messages)) - metas := make([]messageWithIndex, 0, len(messages)) - metaIndexes := make(map[messageIndex]int, len(messages)) - unreadThreshold := time.Duration(portal.bridge.Config.Bridge.Backfill.UnreadHoursThreshold) * time.Hour - var isRead bool - var lastMessage *imessage.Message - for _, msg := range messages { - //Only want tapbacks - if msg.Tapback == nil { - portal.log.Debugln("Skipping message", msg.GUID, "in backfill, should've already handled") - continue - } - - //Skip the last message in the array, we will add it later to correct inbox sorting - if msg == messages[len(messages)-1] && len(messages) > 1 /* we call this function again with one element in the array for the last message, so we'll want to process it */ { - portal.log.Errorln("Skipping message", msg.GUID, "in backfill, last one in the convo") - lastMessage = msg - continue - } - - intent := portal.getIntentForMessage(msg, nil) - dbMessage := portal.bridge.DB.Message.GetByGUID(portal.GUID, msg.Tapback.TargetGUID, msg.Tapback.TargetPart) - if dbMessage == nil { - //TODO BUG: This occurs when trying to find the target reaction for a rich link, related to #183 - portal.log.Errorfln("Failed to get target message for tabpack, %+v", msg) - continue - } - - evt := &event.Event{ - Sender: intent.UserID, - Type: event.EventReaction, - Timestamp: msg.Time.UnixMilli(), - Content: event.Content{ - Parsed: &event.ReactionEventContent{ - RelatesTo: event.RelatesTo{ - Type: event.RelAnnotation, - EventID: dbMessage.MXID, - Key: msg.Tapback.Type.Emoji(), - }, - }, - }, - } - - var err error - evt.Type, err = portal.encrypt(intent, &evt.Content, evt.Type) - if err != nil { - return nil, nil, nil, false, nil, err - } - intent.AddDoublePuppetValue(&evt.Content) - if portal.bridge.Config.Homeserver.Software == bridgeconfig.SoftwareHungry { - evt.ID = portal.deterministicEventID(msg.GUID, 0) - } - - events = append(events, evt) - metas = append(metas, messageWithIndex{msg, intent, dbMessage, 0}) - metaIndexes[messageIndex{msg.GUID, 0}] = len(metas) - - isRead = msg.IsRead || msg.IsFromMe || (unreadThreshold >= 0 && time.Since(msg.Time) > unreadThreshold) - } - return events, metas, metaIndexes, isRead, lastMessage, nil + return events, metas, metaIndexes, isRead, nil } func (portal *Portal) sendBackfill(backfillID string, messages []*imessage.Message, forward, forwardIfNoMessages, markAsRead bool) (success bool) { @@ -273,106 +212,16 @@ func (portal *Portal) sendBackfill(backfillID string, messages []*imessage.Messa portal.bridge.IM.SendBackfillResult(portal.GUID, backfillID, success, idMap) }() batchSending := portal.bridge.SpecVersions.Supports(mautrix.BeeperFeatureBatchSending) - - var validMessages []*imessage.Message - for _, msg := range messages { - if msg.ItemType != imessage.ItemTypeMessage && msg.Tapback == nil { - portal.log.Debugln("Skipping", msg.GUID, "in backfill (not a message)") - continue - } - intent := portal.getIntentForMessage(msg, nil) - if intent == nil { - portal.log.Debugln("Skipping", msg.GUID, "in backfill (didn't get an intent)") - continue - } - if msg.Tapback != nil && msg.Tapback.Remove { - //If we don't process it, there won't be a reaction; at least for BB, we never have to remove a reaction - if msg.Tapback.Remove { - portal.log.Debugln("Skipping", msg.GUID, "in backfill (it was a remove tapback)") - continue - } - } - - validMessages = append(validMessages, msg) - } - - events, metas, metaIndexes, isRead, lastMessage, err := portal.convertBackfill(validMessages) + events, metas, metaIndexes, isRead, err := portal.convertBackfill(messages) if err != nil { portal.log.Errorfln("Failed to convert messages for backfill: %v", err) return false } - portal.log.Debugfln("Converted %d messages into %d message events to backfill", len(messages), len(events)) - if len(events) == 0 { - return true - } - - eventIDs, sendErr := portal.sendBackfillToMatrixServer(batchSending, forward, forwardIfNoMessages, markAsRead, isRead, events, metas, metaIndexes) - if sendErr != nil { - return false - } - portal.addBackfillToDB(metas, eventIDs, idMap, backfillID) - - //We have to process tapbacks after all other messages because we need texts in the DB in order to target them - events, metas, metaIndexes, isRead, lastTapback, err := portal.convertTapbacks(validMessages) - if err != nil { - portal.log.Errorfln("Failed to convert tapbacks for backfill: %v", err) - return false - } - portal.log.Debugfln("Converted %d messages into %d tapbacks events to backfill", len(messages), len(events)) + portal.log.Debugfln("Converted %d messages into %d events to backfill", len(messages), len(events)) if len(events) == 0 { return true } - - eventIDs, sendErr = portal.sendBackfillToMatrixServer(batchSending, forward, forwardIfNoMessages, markAsRead, isRead, events, metas, metaIndexes) - if sendErr != nil { - return false - } - portal.addBackfillToDB(metas, eventIDs, idMap, backfillID) - - //Process the last message in the conversation that we skipped before in converting, - // this is dumb but Beeper sorts its inbox on when the event was recieved and not the timestamp - var lastMessageArray []*imessage.Message - if lastMessage != nil { - lastMessageArray = append(lastMessageArray, lastMessage) - events, metas, metaIndexes, isRead, _, err = portal.convertBackfill(lastMessageArray) - } else if lastTapback != nil { - lastMessageArray = append(lastMessageArray, lastTapback) - events, metas, metaIndexes, isRead, _, err = portal.convertTapbacks(lastMessageArray) - } - if err != nil { - portal.log.Errorfln("Failed to convert last message for backfill: %v", err) - return false - } - - eventIDs, sendErr = portal.sendBackfillToMatrixServer(batchSending, forward, forwardIfNoMessages, markAsRead, isRead, events, metas, metaIndexes) - if sendErr != nil { - return false - } - portal.addBackfillToDB(metas, eventIDs, idMap, backfillID) - - portal.log.Infofln("Finished backfill %s", backfillID) - return true -} - -func (portal *Portal) addBackfillToDB(metas []messageWithIndex, eventIDs []id.EventID, idMap map[string][]id.EventID, backfillID string) { - for i, meta := range metas { - idMap[meta.GUID] = append(idMap[meta.GUID], eventIDs[i]) - } - txn, err := portal.bridge.DB.Begin() - if err != nil { - portal.log.Errorln("Failed to start transaction to save batch messages:", err) - } - portal.log.Debugfln("Inserting %d event IDs to database to finish backfill %s", len(eventIDs), backfillID) - portal.finishBackfill(txn, eventIDs, metas) - portal.Update(txn) - err = txn.Commit() - if err != nil { - portal.log.Errorln("Failed to commit transaction to save batch messages:", err) - } -} - -func (portal *Portal) sendBackfillToMatrixServer(batchSending, forward, forwardIfNoMessages, markAsRead, isRead bool, events []*event.Event, metas []messageWithIndex, - metaIndexes map[messageIndex]int) (eventIDs []id.EventID, err error) { + var eventIDs []id.EventID if batchSending { req := &mautrix.ReqBeeperBatchSend{ Events: events, @@ -385,7 +234,7 @@ func (portal *Portal) sendBackfillToMatrixServer(batchSending, forward, forwardI resp, err := portal.MainIntent().BeeperBatchSend(portal.MXID, req) if err != nil { portal.log.Errorln("Failed to batch send history:", err) - return nil, err + return false } eventIDs = resp.EventIDs } else { @@ -402,7 +251,7 @@ func (portal *Portal) sendBackfillToMatrixServer(batchSending, forward, forwardI resp, err := meta.Intent.SendMassagedMessageEvent(portal.MXID, evt.Type, &evt.Content, evt.Timestamp) if err != nil { portal.log.Errorfln("Failed to send event #%d in history: %v", i, err) - return nil, err + return false } eventIDs[i] = resp.EventID } @@ -414,24 +263,42 @@ func (portal *Portal) sendBackfillToMatrixServer(batchSending, forward, forwardI } } } - return eventIDs, nil + for i, meta := range metas { + idMap[meta.GUID] = append(idMap[meta.GUID], eventIDs[i]) + } + txn, err := portal.bridge.DB.Begin() + if err != nil { + portal.log.Errorln("Failed to start transaction to save batch messages:", err) + return true + } + portal.log.Debugfln("Inserting %d event IDs to database to finish backfill %s", len(eventIDs), backfillID) + portal.finishBackfill(txn, eventIDs, metas) + portal.Update(txn) + err = txn.Commit() + if err != nil { + portal.log.Errorln("Failed to commit transaction to save batch messages:", err) + } + portal.log.Infofln("Finished backfill %s", backfillID) + return true } func (portal *Portal) finishBackfill(txn dbutil.Transaction, eventIDs []id.EventID, metas []messageWithIndex) { for i, info := range metas { if info.Tapback != nil { if info.Tapback.Remove { - continue + // TODO handle removing tapbacks? + } else { + // TODO can existing tapbacks be modified in backfill? + dbTapback := portal.bridge.DB.Tapback.New() + dbTapback.PortalGUID = portal.GUID + dbTapback.SenderGUID = info.Sender.String() + dbTapback.MessageGUID = info.TapbackTarget.GUID + dbTapback.MessagePart = info.TapbackTarget.Part + dbTapback.GUID = info.GUID + dbTapback.Type = info.Tapback.Type + dbTapback.MXID = eventIDs[i] + dbTapback.Insert(txn) } - dbTapback := portal.bridge.DB.Tapback.New() - dbTapback.PortalGUID = portal.GUID - dbTapback.SenderGUID = info.Sender.String() - dbTapback.MessageGUID = info.TapbackTarget.GUID - dbTapback.MessagePart = info.TapbackTarget.Part - dbTapback.GUID = info.GUID - dbTapback.Type = info.Tapback.Type - dbTapback.MXID = eventIDs[i] - dbTapback.Insert(txn) } else { dbMessage := portal.bridge.DB.Message.New() dbMessage.PortalGUID = portal.GUID diff --git a/imessage/bluebubbles/api.go b/imessage/bluebubbles/api.go index df67fa66..302ff971 100644 --- a/imessage/bluebubbles/api.go +++ b/imessage/bluebubbles/api.go @@ -1681,7 +1681,7 @@ func (bb *blueBubbles) convertBBMessageToiMessage(bbMessage Message) (*imessage. bbMessage.AssociatedMessageType != "" { message.Tapback = &imessage.Tapback{ TargetGUID: bbMessage.AssociatedMessageGUID, - Type: bb.convertBBTapbackToImessageTapback(bbMessage.AssociatedMessageType), + Type: imessage.TapbackFromName(bbMessage.AssociatedMessageType), } message.Tapback.Parse() } else { @@ -1819,27 +1819,6 @@ func IsUrl(str string) bool { return true } -func (bb *blueBubbles) convertBBTapbackToImessageTapback(associatedMessageType string) (tbType imessage.TapbackType) { - if strings.Contains(associatedMessageType, "love") { - tbType = imessage.TapbackLove - } else if strings.Contains(associatedMessageType, "like") { - tbType = imessage.TapbackLike - } else if strings.Contains(associatedMessageType, "dislike") { - tbType = imessage.TapbackDislike - } else if strings.Contains(associatedMessageType, "laugh") { - tbType = imessage.TapbackLaugh - } else if strings.Contains(associatedMessageType, "emphasize") { - tbType = imessage.TapbackEmphasis - } else if strings.Contains(associatedMessageType, "question") { - tbType = imessage.TapbackQuestion - } - - if strings.Contains(associatedMessageType, "-") { - tbType += imessage.TapbackRemoveOffset - } - return tbType -} - func (bb *blueBubbles) convertBBChatToiMessageChat(bbChat Chat) (*imessage.ChatInfo, error) { members := make([]string, len(bbChat.Participants))