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

     1  package bots
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"regexp"
     7  	"sort"
     8  	"strings"
     9  
    10  	"github.com/keybase/client/go/chat/globals"
    11  	"github.com/keybase/client/go/chat/utils"
    12  	"github.com/keybase/client/go/protocol/chat1"
    13  	"github.com/keybase/client/go/protocol/gregor1"
    14  	"github.com/keybase/client/go/protocol/keybase1"
    15  )
    16  
    17  func MakeConversationCommandGroups(cmds []chat1.UserBotCommandOutput) chat1.ConversationCommandGroups {
    18  	var outCmds []chat1.ConversationCommand
    19  	for _, cmd := range cmds {
    20  		username := cmd.Username
    21  		outCmds = append(outCmds, chat1.ConversationCommand{
    22  			Name:        cmd.Name,
    23  			Description: cmd.Description,
    24  			Usage:       cmd.Usage,
    25  			HasHelpText: cmd.ExtendedDescription != nil,
    26  			Username:    &username,
    27  		})
    28  	}
    29  	return chat1.NewConversationCommandGroupsWithCustom(chat1.ConversationCommandGroupsCustom{
    30  		Commands: outCmds,
    31  	})
    32  }
    33  
    34  func SortCommandsForMatching(cmds []chat1.UserBotCommandOutput) {
    35  	// sort commands by reverse command length to prefer specificity (i.e. if
    36  	// there's a longer command that matches the prefix, we'll match that
    37  	// first.)
    38  	sort.SliceStable(cmds, func(i, j int) bool {
    39  		return len(cmds[i].Name) > len(cmds[j].Name)
    40  	})
    41  }
    42  
    43  func ApplyTeamBotSettings(ctx context.Context, g *globals.Context, botUID gregor1.UID,
    44  	botSettings keybase1.TeamBotSettings,
    45  	msg chat1.MessagePlaintext, convID *chat1.ConversationID,
    46  	mentionMap map[string]struct{}, debug utils.DebugLabeler) (bool, error) {
    47  
    48  	// First make sure bot can receive on the given conversation. This ID may
    49  	// be null if we are creating the conversation.
    50  	if convID != nil && !botSettings.ConvIDAllowed(convID.String()) {
    51  		return false, nil
    52  	}
    53  
    54  	// If the sender is the bot, always match
    55  	if msg.ClientHeader.Sender.Eq(botUID) {
    56  		return true, nil
    57  	}
    58  
    59  	switch msg.ClientHeader.MessageType {
    60  	// DELETEHISTORY messages are always keyed for bots in case they need to
    61  	// clear messages
    62  	case chat1.MessageType_DELETEHISTORY:
    63  		return true, nil
    64  	// Bots never get these
    65  	case chat1.MessageType_NONE,
    66  		chat1.MessageType_METADATA,
    67  		chat1.MessageType_TLFNAME,
    68  		chat1.MessageType_HEADLINE,
    69  		chat1.MessageType_JOIN,
    70  		chat1.MessageType_LEAVE,
    71  		chat1.MessageType_SYSTEM:
    72  		return false, nil
    73  	}
    74  
    75  	// check mentions
    76  	if _, ok := mentionMap[botUID.String()]; ok && botSettings.Mentions {
    77  		return true, nil
    78  	}
    79  
    80  	// See if any triggers match
    81  	matchText := msg.SearchableText()
    82  	for _, trigger := range botSettings.Triggers {
    83  		re, err := regexp.Compile(fmt.Sprintf("(?i)%s", trigger))
    84  		if err != nil {
    85  			debug.Debug(ctx, "unable to compile trigger regex: %v", err)
    86  			continue
    87  		}
    88  		if re.MatchString(matchText) {
    89  			return true, nil
    90  		}
    91  	}
    92  
    93  	// Check if any commands match (early out if it can't be a bot message)
    94  	if !botSettings.Cmds || !strings.HasPrefix(matchText, "!") {
    95  		return false, nil
    96  	}
    97  	unn, err := g.GetUPAKLoader().LookupUsername(ctx, keybase1.UID(botUID.String()))
    98  	if err != nil {
    99  		return false, err
   100  	}
   101  	if convID != nil {
   102  		completeCh, err := g.BotCommandManager.UpdateCommands(ctx, *convID, nil)
   103  		if err != nil {
   104  			return false, err
   105  		}
   106  		if err := <-completeCh; err != nil {
   107  			return false, err
   108  		}
   109  		cmds, _, err := g.BotCommandManager.ListCommands(ctx, *convID)
   110  		if err != nil {
   111  			return false, nil
   112  		}
   113  		SortCommandsForMatching(cmds)
   114  		for _, cmd := range cmds {
   115  			if unn.String() == cmd.Username && cmd.Matches(matchText) {
   116  				return true, nil
   117  			}
   118  		}
   119  	}
   120  	return false, nil
   121  }