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  }