github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/chat/supersedes.go (about)

     1  package chat
     2  
     3  import (
     4  	"fmt"
     5  	"sort"
     6  	"time"
     7  
     8  	"github.com/keybase/client/go/chat/globals"
     9  	"github.com/keybase/client/go/chat/types"
    10  	"github.com/keybase/client/go/chat/utils"
    11  	"github.com/keybase/client/go/protocol/chat1"
    12  	"github.com/keybase/client/go/protocol/gregor1"
    13  	context "golang.org/x/net/context"
    14  )
    15  
    16  type getMessagesFunc func(context.Context, chat1.ConversationID, gregor1.UID, []chat1.MessageID,
    17  	*chat1.GetThreadReason, func() chat1.RemoteInterface, bool) ([]chat1.MessageUnboxed, error)
    18  
    19  type basicSupersedesTransformOpts struct {
    20  	UseDeletePlaceholders bool
    21  }
    22  
    23  type basicSupersedesTransform struct {
    24  	globals.Contextified
    25  	utils.DebugLabeler
    26  
    27  	messagesFunc getMessagesFunc
    28  	opts         basicSupersedesTransformOpts
    29  }
    30  
    31  var _ types.SupersedesTransform = (*basicSupersedesTransform)(nil)
    32  
    33  func newBasicSupersedesTransform(g *globals.Context, opts basicSupersedesTransformOpts) *basicSupersedesTransform {
    34  	return &basicSupersedesTransform{
    35  		Contextified: globals.NewContextified(g),
    36  		DebugLabeler: utils.NewDebugLabeler(g.ExternalG(), "supersedesTransform", false),
    37  		messagesFunc: g.ConvSource.GetMessages,
    38  		opts:         opts,
    39  	}
    40  }
    41  
    42  // This is only relevant for ephemeralMessages that are deleted since we want
    43  // these to show up in the gui as "explode now"
    44  func (t *basicSupersedesTransform) transformDelete(msg chat1.MessageUnboxed, superMsg chat1.MessageUnboxed) *chat1.MessageUnboxed {
    45  	if !msg.IsEphemeral() {
    46  		return nil
    47  	}
    48  	if msg.IsValid() {
    49  		mvalid := msg.Valid()
    50  		explodedBy := superMsg.Valid().SenderUsername
    51  		mvalid.ClientHeader.EphemeralMetadata.ExplodedBy = &explodedBy
    52  		newMsg := chat1.NewMessageUnboxedWithValid(mvalid)
    53  		return &newMsg
    54  	} else if msg.IsError() {
    55  		// Transform an erred exploding message if it was exploded
    56  		merr := msg.Error()
    57  		explodedBy := superMsg.Valid().SenderUsername
    58  		merr.ExplodedBy = &explodedBy
    59  		newMsg := chat1.NewMessageUnboxedWithError(merr)
    60  		return &newMsg
    61  	}
    62  	return nil
    63  }
    64  
    65  func (t *basicSupersedesTransform) transformEdit(msg chat1.MessageUnboxed, superMsg chat1.MessageUnboxed) *chat1.MessageUnboxed {
    66  	if !msg.IsValid() {
    67  		return nil
    68  	}
    69  
    70  	mvalid := msg.Valid()
    71  	var payments []chat1.TextPayment
    72  	var replyTo *chat1.MessageID
    73  	if mvalid.MessageBody.IsType(chat1.MessageType_TEXT) {
    74  		payments = mvalid.MessageBody.Text().Payments
    75  		replyTo = mvalid.MessageBody.Text().ReplyTo
    76  		mvalid.MessageBody = chat1.NewMessageBodyWithText(chat1.MessageText{
    77  			Body:     superMsg.Valid().MessageBody.Edit().Body,
    78  			Payments: payments,
    79  			ReplyTo:  replyTo,
    80  		})
    81  	} else if mvalid.MessageBody.IsType(chat1.MessageType_ATTACHMENT) {
    82  		body := mvalid.MessageBody.Attachment()
    83  		body.Object.Title = superMsg.Valid().MessageBody.Edit().Body
    84  		mvalid.MessageBody = chat1.NewMessageBodyWithAttachment(body)
    85  	}
    86  	mvalid.AtMentions = superMsg.Valid().AtMentions
    87  	mvalid.AtMentionUsernames = superMsg.Valid().AtMentionUsernames
    88  	mvalid.ChannelMention = superMsg.Valid().ChannelMention
    89  	mvalid.ChannelNameMentions = superMsg.Valid().ChannelNameMentions
    90  	mvalid.SenderDeviceName = superMsg.Valid().SenderDeviceName
    91  	mvalid.SenderDeviceType = superMsg.Valid().SenderDeviceType
    92  	mvalid.ServerHeader.SupersededBy = superMsg.GetMessageID()
    93  	mvalid.Emojis = append(mvalid.Emojis, superMsg.Valid().Emojis...)
    94  	newMsg := chat1.NewMessageUnboxedWithValid(mvalid)
    95  	return &newMsg
    96  }
    97  
    98  func (t *basicSupersedesTransform) transformAttachment(msg chat1.MessageUnboxed, superMsg chat1.MessageUnboxed) *chat1.MessageUnboxed {
    99  	if !msg.IsValid() {
   100  		return nil
   101  	}
   102  	mvalid := msg.Valid()
   103  	uploaded := superMsg.Valid().MessageBody.Attachmentuploaded()
   104  	attachment := chat1.MessageAttachment{
   105  		Object:   uploaded.Object,
   106  		Previews: uploaded.Previews,
   107  		Metadata: uploaded.Metadata,
   108  		Uploaded: true,
   109  	}
   110  	if len(uploaded.Previews) > 0 {
   111  		attachment.Preview = &uploaded.Previews[0]
   112  	}
   113  	mvalid.MessageBody = chat1.NewMessageBodyWithAttachment(attachment)
   114  	mvalid.ServerHeader.SupersededBy = superMsg.GetMessageID()
   115  	newMsg := chat1.NewMessageUnboxedWithValid(mvalid)
   116  	return &newMsg
   117  }
   118  
   119  func (t *basicSupersedesTransform) transformReaction(msg chat1.MessageUnboxed, superMsg chat1.MessageUnboxed) *chat1.MessageUnboxed {
   120  	if !msg.IsValid() {
   121  		return nil
   122  	}
   123  	if superMsg.Valid().MessageBody.IsNil() {
   124  		return &msg
   125  	}
   126  
   127  	reactionMap := msg.Valid().Reactions
   128  	if reactionMap.Reactions == nil {
   129  		reactionMap.Reactions = map[string]map[string]chat1.Reaction{}
   130  	}
   131  
   132  	reactionText := superMsg.Valid().MessageBody.Reaction().Body
   133  	reactions, ok := reactionMap.Reactions[reactionText]
   134  	if !ok {
   135  		reactions = map[string]chat1.Reaction{}
   136  	}
   137  	reactions[superMsg.Valid().SenderUsername] = chat1.Reaction{
   138  		ReactionMsgID: superMsg.GetMessageID(),
   139  		Ctime:         superMsg.Valid().ServerHeader.Ctime,
   140  	}
   141  	reactionMap.Reactions[reactionText] = reactions
   142  
   143  	mvalid := msg.Valid()
   144  	mvalid.Emojis = append(mvalid.Emojis, superMsg.Valid().Emojis...)
   145  	mvalid.Reactions = reactionMap
   146  	newMsg := chat1.NewMessageUnboxedWithValid(mvalid)
   147  	return &newMsg
   148  }
   149  
   150  func (t *basicSupersedesTransform) transformUnfurl(msg chat1.MessageUnboxed, superMsg chat1.MessageUnboxed) *chat1.MessageUnboxed {
   151  	if !msg.IsValid() {
   152  		return nil
   153  	}
   154  	if superMsg.Valid().MessageBody.IsNil() {
   155  		return &msg
   156  	}
   157  	mvalid := msg.Valid()
   158  	utils.SetUnfurl(&mvalid, superMsg.GetMessageID(), superMsg.Valid().MessageBody.Unfurl().Unfurl)
   159  	newMsg := chat1.NewMessageUnboxedWithValid(mvalid)
   160  	return &newMsg
   161  }
   162  
   163  func (t *basicSupersedesTransform) transform(ctx context.Context, msg chat1.MessageUnboxed,
   164  	superMsgs []chat1.MessageUnboxed) (newMsg *chat1.MessageUnboxed, isDelete bool) {
   165  
   166  	newMsg = &msg
   167  	for _, superMsg := range superMsgs {
   168  		if !superMsg.IsValidFull() {
   169  			continue
   170  		} else if newMsg == nil {
   171  			return nil, true
   172  		}
   173  
   174  		switch superMsg.GetMessageType() {
   175  		case chat1.MessageType_DELETE:
   176  			newMsg = t.transformDelete(*newMsg, superMsg)
   177  			if newMsg == nil {
   178  				return nil, true
   179  			}
   180  		case chat1.MessageType_DELETEHISTORY:
   181  			return nil, true
   182  		case chat1.MessageType_EDIT:
   183  			newMsg = t.transformEdit(*newMsg, superMsg)
   184  		case chat1.MessageType_ATTACHMENTUPLOADED:
   185  			newMsg = t.transformAttachment(*newMsg, superMsg)
   186  		case chat1.MessageType_REACTION:
   187  			newMsg = t.transformReaction(*newMsg, superMsg)
   188  		case chat1.MessageType_UNFURL:
   189  			newMsg = t.transformUnfurl(*newMsg, superMsg)
   190  		}
   191  
   192  		t.Debug(ctx, "transformed: original:%v super:%v -> %v",
   193  			newMsg.DebugString(), superMsg.DebugString(), newMsg.DebugString())
   194  	}
   195  	return newMsg, false
   196  }
   197  
   198  func (t *basicSupersedesTransform) SetMessagesFunc(f getMessagesFunc) {
   199  	t.messagesFunc = f
   200  }
   201  
   202  func (t *basicSupersedesTransform) Run(ctx context.Context,
   203  	convID chat1.ConversationID, uid gregor1.UID, originalMsgs []chat1.MessageUnboxed,
   204  	maxDeletedUpTo *chat1.MessageID) (newMsgs []chat1.MessageUnboxed, err error) {
   205  	defer t.Trace(ctx, &err, fmt.Sprintf("Run(%s)", convID))()
   206  	originalMsgsMap := make(map[chat1.MessageID]chat1.MessageUnboxed, len(originalMsgs))
   207  	for _, msg := range originalMsgs {
   208  		originalMsgsMap[msg.GetMessageID()] = msg
   209  	}
   210  	// Map from a MessageID to the message that supersedes it It's possible
   211  	// that a message can be 'superseded' my multiple messages, by multiple
   212  	// reactions.
   213  	smap := make(map[chat1.MessageID][]chat1.MessageUnboxed)
   214  
   215  	// Collect all superseder messages for messages in the current thread view
   216  	superMsgsMap := make(map[chat1.MessageID]chat1.MessageUnboxed)
   217  	superMsgIDsMap := make(map[chat1.MessageID]bool)
   218  	for _, msg := range originalMsgs {
   219  		if msg.IsValid() {
   220  			supersededBy := msg.Valid().ServerHeader.SupersededBy
   221  			if supersededBy > 0 {
   222  				if msg, ok := originalMsgsMap[supersededBy]; ok {
   223  					superMsgsMap[supersededBy] = msg
   224  				} else {
   225  					superMsgIDsMap[supersededBy] = true
   226  				}
   227  			}
   228  			for _, reactID := range msg.Valid().ServerHeader.ReactionIDs {
   229  				if msg, ok := originalMsgsMap[reactID]; ok {
   230  					superMsgsMap[reactID] = msg
   231  				} else {
   232  					superMsgIDsMap[reactID] = true
   233  				}
   234  			}
   235  			for _, unfurlID := range msg.Valid().ServerHeader.UnfurlIDs {
   236  				if msg, ok := originalMsgsMap[unfurlID]; ok {
   237  					superMsgsMap[unfurlID] = msg
   238  				} else {
   239  					superMsgIDsMap[unfurlID] = true
   240  				}
   241  			}
   242  			supersedes, err := utils.GetSupersedes(msg)
   243  			if err != nil {
   244  				continue
   245  			}
   246  			if len(supersedes) > 0 {
   247  				superMsgsMap[msg.GetMessageID()] = msg
   248  			}
   249  		}
   250  	}
   251  	superMsgIDs := make([]chat1.MessageID, 0, len(superMsgIDsMap))
   252  	for superMsgID := range superMsgIDsMap {
   253  		superMsgIDs = append(superMsgIDs, superMsgID)
   254  	}
   255  
   256  	// Get superseding messages
   257  	var deleteHistoryUpto chat1.MessageID
   258  	// If there are no superseding messages we still need to run
   259  	// the bottom loop to filter out messages deleted by retention.
   260  	if len(superMsgIDs) > 0 {
   261  		msgs, err := t.messagesFunc(ctx, convID, uid, superMsgIDs, nil, nil, false)
   262  		if err != nil {
   263  			return nil, err
   264  		}
   265  		for _, msg := range msgs {
   266  			superMsgsMap[msg.GetMessageID()] = msg
   267  		}
   268  	}
   269  	for _, m := range superMsgsMap {
   270  		if m.IsValid() {
   271  			supersedes, err := utils.GetSupersedes(m)
   272  			if err != nil {
   273  				continue
   274  			}
   275  			for _, super := range supersedes {
   276  				smap[super] = append(smap[super], m)
   277  			}
   278  			delh, err := m.Valid().AsDeleteHistory()
   279  			if err == nil {
   280  				if delh.Upto > deleteHistoryUpto {
   281  					t.Debug(ctx, "found delete history: id: %v", m.GetMessageID())
   282  					deleteHistoryUpto = delh.Upto
   283  				}
   284  			}
   285  		}
   286  	}
   287  	// Sort all the super lists
   288  	for _, supers := range smap {
   289  		sort.Slice(supers, func(i, j int) bool {
   290  			return supers[i].GetMessageID() < supers[j].GetMessageID()
   291  		})
   292  	}
   293  	// Run through all messages and transform superseded messages into final state
   294  	xformDelete := func(msgID chat1.MessageID) {
   295  		if t.opts.UseDeletePlaceholders {
   296  			newMsgs = append(newMsgs, utils.CreateHiddenPlaceholder(msgID))
   297  		}
   298  	}
   299  	for i, msg := range originalMsgs {
   300  		if msg.IsValid() || msg.IsError() {
   301  			newMsg := &originalMsgs[i]
   302  			// If the message is superseded, then transform it and add that
   303  			var isDelete bool
   304  			if superMsgs, ok := smap[msg.GetMessageID()]; ok {
   305  				newMsg, isDelete = t.transform(ctx, msg, superMsgs)
   306  			}
   307  			if newMsg == nil {
   308  				if isDelete {
   309  					// Transform might return nil in case of a delete.
   310  					t.Debug(ctx, "skipping: %d because it was deleted by a delete", msg.GetMessageID())
   311  					xformDelete(msg.GetMessageID())
   312  				} else { // just use the original message
   313  					newMsgs = append(newMsgs, msg)
   314  				}
   315  				continue
   316  			}
   317  			if newMsg.GetMessageID() < deleteHistoryUpto &&
   318  				chat1.IsDeletableByDeleteHistory(newMsg.GetMessageType()) {
   319  				t.Debug(ctx, "skipping: %d because it was deleted by delete history", msg.GetMessageID())
   320  				xformDelete(msg.GetMessageID())
   321  				continue
   322  			}
   323  			if !newMsg.IsValidFull() {
   324  				// Drop the message unless it is ephemeral. It has been deleted
   325  				// locally but not superseded by anything.  Could have been
   326  				// deleted by a delete-history, retention expunge, or was an
   327  				// exploding message.
   328  				if newMsg.IsValid() && (!newMsg.IsEphemeral() ||
   329  					(maxDeletedUpTo != nil && newMsg.HideExplosion(*maxDeletedUpTo, time.Now()))) {
   330  					t.Debug(ctx, "skipping: %d because it not valid full",
   331  						msg.GetMessageID())
   332  					xformDelete(msg.GetMessageID())
   333  					continue
   334  				}
   335  			}
   336  			newMsgs = append(newMsgs, *newMsg)
   337  		} else {
   338  			newMsgs = append(newMsgs, msg)
   339  		}
   340  	}
   341  
   342  	return newMsgs, nil
   343  }