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 }