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

     1  package chat
     2  
     3  import (
     4  	"github.com/keybase/client/go/chat/types"
     5  	"github.com/keybase/client/go/kbun"
     6  	"github.com/keybase/client/go/libkb"
     7  	"github.com/keybase/client/go/protocol/chat1"
     8  	"github.com/keybase/client/go/protocol/gregor1"
     9  )
    10  
    11  type ConvTranscript struct {
    12  	Messages []ConvTranscriptMsg `json:"messages"`
    13  }
    14  
    15  type ConvTranscriptMsg struct {
    16  	SenderUsername string            `json:"senderUsername"`
    17  	Body           chat1.MessageBody `json:"body"`
    18  	Ctime          gregor1.Time      `json:"ctime_ms"`
    19  }
    20  
    21  type PullTranscriptConfig struct {
    22  	// How many messages to pull with given filters (so list of usernames, or
    23  	// all users).
    24  	messageCount int
    25  
    26  	batchSize  int // how many to pull in one batch
    27  	batchCount int // how many batches to try
    28  }
    29  
    30  func PullTranscriptConfigDefault() PullTranscriptConfig {
    31  	return PullTranscriptConfig{
    32  		messageCount: 100,
    33  		batchSize:    100,
    34  		batchCount:   5,
    35  	}
    36  }
    37  
    38  func PullTranscript(mctx libkb.MetaContext, convSource types.ConversationSource, convID chat1.ConvIDStr,
    39  	usernames []kbun.NormalizedUsername, config PullTranscriptConfig) (res ConvTranscript, err error) {
    40  
    41  	convIDBytes, err := chat1.MakeConvID(convID.String())
    42  	if err != nil {
    43  		return res, err
    44  	}
    45  
    46  	mctx.Debug("Pulling transcript for convID=%s, usernames=%v", convID, usernames)
    47  	usernameMap := make(map[string]struct{}, len(usernames))
    48  	if len(usernames) != 0 {
    49  		for _, v := range usernames {
    50  			usernameMap[v.String()] = struct{}{}
    51  		}
    52  	} else {
    53  		mctx.Debug("usernames array is empty, pulling messages for ALL usernames")
    54  	}
    55  
    56  	uidBytes := gregor1.UID(mctx.CurrentUID().ToBytes())
    57  	chatQuery := &chat1.GetThreadQuery{
    58  		MarkAsRead:   false,
    59  		MessageTypes: chat1.VisibleChatMessageTypes(),
    60  	}
    61  
    62  	var next []byte
    63  
    64  outerLoop:
    65  	for i := 0; i < config.batchCount; i++ {
    66  		pagination := &chat1.Pagination{
    67  			Num:  config.batchSize,
    68  			Next: next,
    69  		}
    70  		mctx.Debug("Pulling from ConvSource: i=%d, Pagination=%#v", i, pagination)
    71  		threadView, err := convSource.Pull(mctx.Ctx(), convIDBytes, uidBytes,
    72  			chat1.GetThreadReason_GENERAL, nil, chatQuery, pagination)
    73  		if err != nil {
    74  			return ConvTranscript{}, err
    75  		}
    76  		mctx.Debug("Got %d messages to search through", len(threadView.Messages))
    77  		for _, msg := range threadView.Messages {
    78  			if !msg.IsValid() {
    79  				continue
    80  			}
    81  			mv := msg.Valid()
    82  			// Filter by usernames
    83  			if len(usernames) != 0 {
    84  				if _, ok := usernameMap[mv.SenderUsername]; !ok {
    85  					// Skip this message
    86  					continue
    87  				}
    88  			}
    89  			tMsg := ConvTranscriptMsg{
    90  				SenderUsername: mv.SenderUsername,
    91  				Body:           mv.MessageBody,
    92  				Ctime:          mv.ServerHeader.Ctime,
    93  			}
    94  			res.Messages = append(res.Messages, tMsg)
    95  			if len(res.Messages) >= config.messageCount {
    96  				mctx.Debug("Got all messages we wanted (%d) at i=%d", config.messageCount, i)
    97  				break outerLoop
    98  			}
    99  		}
   100  
   101  		if threadView.Pagination == nil {
   102  			mctx.Debug("i=%d got no Pagination struct", i)
   103  			break
   104  		}
   105  		if threadView.Pagination.Last {
   106  			mctx.Debug("i=%d was the last page", i)
   107  			break
   108  		}
   109  		next = threadView.Pagination.Next
   110  	}
   111  
   112  	return res, nil
   113  }