diff --git a/historysync.go b/historysync.go index b1ad056..7ffc238 100644 --- a/historysync.go +++ b/historysync.go @@ -152,22 +152,11 @@ func (portal *Portal) convertBackfill(messages []*imessage.Message) ([]*event.Ev unreadThreshold := time.Duration(portal.bridge.Config.Bridge.Backfill.UnreadHoursThreshold) * time.Hour var isRead bool 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 { - // 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{ @@ -198,6 +187,55 @@ 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 { + //Only want tapbacks + if msg.Tapback == nil { + 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(), + }, + }, + }, + } + + 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 +250,83 @@ 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 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 + portal.log.Debugln("Skipping", msg.GUID, "in backfill (it was a remove tapback)") + continue + } + + validMessages = append(validMessages, msg) + } + + events, metas, metaIndexes, isRead, err := portal.convertBackfill(validMessages) 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 message events to backfill", len(messages), 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 + } + 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(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)) + 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) + + 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) { if batchSending { req := &mautrix.ReqBeeperBatchSend{ Events: events, @@ -234,7 +339,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 +356,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 +368,24 @@ 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) + continue } + 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 cf227e2..3d3d86f 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, "dislike") { + tbType = imessage.TapbackDislike + } else if strings.Contains(associatedMessageType, "like") { + tbType = imessage.TapbackLike + } 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))