github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/chat/commands/bot.go (about) 1 package commands 2 3 import ( 4 "context" 5 "errors" 6 "strings" 7 "sync" 8 9 "github.com/keybase/client/go/chat/bots" 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 ) 15 16 type Bot struct { 17 *baseCommand 18 sync.Mutex 19 extendedDisplay bool 20 } 21 22 func NewBot(g *globals.Context) *Bot { 23 return &Bot{ 24 baseCommand: newBaseCommand(g, "bot", "", "Bot commands", true), 25 } 26 } 27 28 func (b *Bot) Execute(ctx context.Context, uid gregor1.UID, convID chat1.ConversationID, 29 tlfName, text string, replyTo *chat1.MessageID) (err error) { 30 return errors.New("bot command cannot be executed") 31 } 32 33 func (b *Bot) clearExtendedDisplayLocked(ctx context.Context, convID chat1.ConversationID) { 34 if b.extendedDisplay { 35 err := b.getChatUI().ChatCommandMarkdown(ctx, convID, nil) 36 if err != nil { 37 b.Debug(ctx, "clearExtendedDisplayLocked: error on markdown: %+v", err) 38 } 39 b.extendedDisplay = false 40 } 41 } 42 43 func (b *Bot) Preview(ctx context.Context, uid gregor1.UID, convID chat1.ConversationID, 44 tlfName, text string) { 45 defer b.Trace(ctx, nil, "Preview")() 46 b.Lock() 47 defer b.Unlock() 48 if !strings.HasPrefix(text, "!") { 49 b.clearExtendedDisplayLocked(ctx, convID) 50 return 51 } 52 if text == "!" { 53 // spawn an update if the user is attempting to see bot commands 54 go func(ctx context.Context) { 55 errCh, err := b.G().BotCommandManager.UpdateCommands(ctx, convID, nil) 56 if err != nil { 57 b.Debug(ctx, "Preview: failed to attempt to update bot commands: %s", err) 58 return 59 } 60 if err := <-errCh; err != nil { 61 b.Debug(ctx, "Preview: failed to update bot commands: %s", err) 62 } 63 }(globals.BackgroundChatCtx(ctx, b.G())) 64 } 65 66 cmds, _, err := b.G().BotCommandManager.ListCommands(ctx, convID) 67 if err != nil { 68 b.Debug(ctx, "Preview: failed to list commands: %s", err) 69 return 70 } 71 72 bots.SortCommandsForMatching(cmds) 73 74 // Since we have a list of all valid commands for this conversation, don't do any tokenizing 75 // Instead, just check if any valid bot command (followed by a space) is a prefix of this message 76 for _, cmd := range cmds { 77 // If we decide to support the !<command>@<username> syntax, we can just add another check here 78 if cmd.Matches(text) && cmd.ExtendedDescription != nil { 79 var body string 80 if b.G().IsMobileAppType() { 81 body = cmd.ExtendedDescription.MobileBody 82 } else { 83 body = cmd.ExtendedDescription.DesktopBody 84 } 85 var title *string 86 if cmd.ExtendedDescription.Title != "" { 87 title = new(string) 88 *title = utils.EscapeForDecorate(ctx, cmd.ExtendedDescription.Title) 89 } 90 err := b.getChatUI().ChatCommandMarkdown(ctx, convID, &chat1.UICommandMarkdown{ 91 Body: utils.EscapeForDecorate(ctx, body), 92 Title: title, 93 }) 94 if err != nil { 95 b.Debug(ctx, "Preview: markdown error: %+v", err) 96 } 97 b.extendedDisplay = true 98 return 99 } 100 } 101 b.clearExtendedDisplayLocked(ctx, convID) 102 }