github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/chat/replyto.go (about) 1 package chat 2 3 import ( 4 "context" 5 6 "github.com/keybase/client/go/chat/globals" 7 "github.com/keybase/client/go/chat/storage" 8 "github.com/keybase/client/go/chat/utils" 9 "github.com/keybase/client/go/protocol/chat1" 10 "github.com/keybase/client/go/protocol/gregor1" 11 ) 12 13 type fillerReplyMsgs struct { 14 msgIDs map[chat1.MessageID]bool 15 fetcher getMessagesFunc 16 } 17 18 func newFillerReplyMsgs(fetcher getMessagesFunc) *fillerReplyMsgs { 19 return &fillerReplyMsgs{ 20 msgIDs: make(map[chat1.MessageID]bool), 21 fetcher: fetcher, 22 } 23 } 24 25 func (f *fillerReplyMsgs) add(msgID chat1.MessageID) { 26 f.msgIDs[msgID] = true 27 } 28 29 func (f *fillerReplyMsgs) fill(ctx context.Context, uid gregor1.UID, convID chat1.ConversationID) (res []chat1.MessageUnboxed, err error) { 30 var msgIDs []chat1.MessageID 31 for msgID := range f.msgIDs { 32 msgIDs = append(msgIDs, msgID) 33 } 34 if len(msgIDs) == 0 { 35 return nil, nil 36 } 37 return f.fetcher(ctx, convID, uid, msgIDs, nil, nil, false) 38 } 39 40 func LocalOnlyReplyFill(enabled bool) func(*ReplyFiller) { 41 return func(f *ReplyFiller) { 42 f.SetLocalOnlyReplyFill(enabled) 43 } 44 } 45 46 type ReplyFiller struct { 47 globals.Contextified 48 utils.DebugLabeler 49 50 localOnly bool 51 } 52 53 func NewReplyFiller(g *globals.Context, config ...func(*ReplyFiller)) *ReplyFiller { 54 f := &ReplyFiller{ 55 Contextified: globals.NewContextified(g), 56 DebugLabeler: utils.NewDebugLabeler(g.ExternalG(), "ReplyFiller", false), 57 } 58 for _, c := range config { 59 c(f) 60 } 61 return f 62 } 63 64 func (f *ReplyFiller) SetLocalOnlyReplyFill(enabled bool) { 65 f.localOnly = enabled 66 } 67 68 func (f *ReplyFiller) localFetcher(ctx context.Context, convID chat1.ConversationID, 69 uid gregor1.UID, msgIDs []chat1.MessageID, _ *chat1.GetThreadReason, _ func() chat1.RemoteInterface, 70 _ bool) (res []chat1.MessageUnboxed, err error) { 71 msgs, err := storage.New(f.G(), nil).FetchMessages(ctx, convID, uid, msgIDs) 72 if err != nil { 73 return nil, err 74 } 75 for _, msg := range msgs { 76 if msg != nil { 77 res = append(res, *msg) 78 } 79 } 80 return res, nil 81 } 82 83 func (f *ReplyFiller) validReplyTo(msgID *chat1.MessageID) bool { 84 return msgID != nil && *msgID > 0 85 } 86 87 func (f *ReplyFiller) getReplyTo(msg chat1.MessageUnboxed) *chat1.MessageID { 88 st, err := msg.State() 89 if err != nil { 90 return nil 91 } 92 switch st { 93 case chat1.MessageUnboxedState_VALID: 94 if msg.Valid().MessageBody.IsType(chat1.MessageType_TEXT) && 95 f.validReplyTo(msg.Valid().MessageBody.Text().ReplyTo) { 96 return msg.Valid().MessageBody.Text().ReplyTo 97 } 98 case chat1.MessageUnboxedState_OUTBOX: 99 if msg.Outbox().PrepareOpts != nil && f.validReplyTo(msg.Outbox().PrepareOpts.ReplyTo) { 100 return msg.Outbox().PrepareOpts.ReplyTo 101 } 102 } 103 return nil 104 } 105 106 func (f *ReplyFiller) FillSingle(ctx context.Context, uid gregor1.UID, convID chat1.ConversationID, 107 msg chat1.MessageUnboxed) (res chat1.MessageUnboxed, err error) { 108 msgs, err := f.Fill(ctx, uid, convID, []chat1.MessageUnboxed{msg}) 109 if err != nil { 110 return res, err 111 } 112 if len(msgs) == 0 { 113 return msg, nil 114 } 115 return msgs[0], nil 116 } 117 118 func (f *ReplyFiller) Fill(ctx context.Context, uid gregor1.UID, convID chat1.ConversationID, 119 msgs []chat1.MessageUnboxed) (res []chat1.MessageUnboxed, err error) { 120 defer f.Trace(ctx, &err, "Fill: %s", convID)() 121 // Gather up the message IDs we need 122 repliedToMsgIDsLocal := newFillerReplyMsgs(f.localFetcher) 123 repliedToMsgIDsRemote := newFillerReplyMsgs(f.G().ConvSource.GetMessages) 124 for _, msg := range msgs { 125 st, err := msg.State() 126 if err != nil { 127 continue 128 } 129 switch st { 130 case chat1.MessageUnboxedState_VALID: 131 if msgID := f.getReplyTo(msg); msgID != nil { 132 if f.localOnly { 133 repliedToMsgIDsLocal.add(*msgID) 134 } else { 135 repliedToMsgIDsRemote.add(*msgID) 136 } 137 } 138 case chat1.MessageUnboxedState_OUTBOX: 139 if msgID := f.getReplyTo(msg); msgID != nil { 140 repliedToMsgIDsLocal.add(*msgID) 141 } 142 } 143 } 144 145 // Fetch messages 146 localMsgs, err := repliedToMsgIDsLocal.fill(ctx, uid, convID) 147 if err != nil { 148 f.Debug(ctx, "Fill: failed to get local messages: %s", err) 149 return res, err 150 } 151 remoteMsgs, err := repliedToMsgIDsRemote.fill(ctx, uid, convID) 152 if err != nil { 153 f.Debug(ctx, "Fill: failed to get remote messages: %s", err) 154 return res, err 155 } 156 origMsgs := localMsgs 157 origMsgs = append(origMsgs, remoteMsgs...) 158 transform := newBasicSupersedesTransform(f.G(), basicSupersedesTransformOpts{ 159 UseDeletePlaceholders: true, 160 }) 161 allMsgs, err := transform.Run(ctx, convID, uid, origMsgs, nil) 162 if err != nil { 163 f.Debug(ctx, "Fill: failed to supersede replies: %s", err) 164 return res, err 165 } 166 replyMap := make(map[chat1.MessageID]chat1.MessageUnboxed) 167 for _, msg := range allMsgs { 168 replyMap[msg.GetMessageID()] = msg 169 } 170 171 // Modify messages 172 res = make([]chat1.MessageUnboxed, len(msgs)) 173 for index, msg := range msgs { 174 res[index] = msg 175 if replyToID := f.getReplyTo(msg); replyToID != nil { 176 st, err := msg.State() 177 if err != nil { 178 continue 179 } 180 replyTo, found := replyMap[*replyToID] 181 if !found { 182 continue 183 } 184 switch st { 185 case chat1.MessageUnboxedState_VALID: 186 mvalid := msg.Valid() 187 mvalid.ReplyTo = &replyTo 188 res[index] = chat1.NewMessageUnboxedWithValid(mvalid) 189 case chat1.MessageUnboxedState_OUTBOX: 190 obr := msg.Outbox() 191 obr.ReplyTo = &replyTo 192 res[index] = chat1.NewMessageUnboxedWithOutbox(obr) 193 } 194 } 195 } 196 return res, nil 197 }