github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/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 }