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  }