package keybase import ( "bufio" "context" "fmt" "sync" "git.hugfreevikings.wtf/keybase/keybase/pkg/ctxreader" ) type ChatAPI struct { sync.Mutex toStdin chan []byte fromStdout chan msg fromListen chan []byte kbLoc string } type msg interface{} type apiMsg []byte type errMsg struct { Err error } func (e errMsg) Error() string { return e.Err.Error() } func NewChatAPI(ctx context.Context, opts Options) (api *ChatAPI, err error) { if opts.ChannelBufferSize <= 0 { opts.ChannelBufferSize = 10 } // set up the channels api = &ChatAPI{ toStdin: make(chan []byte, opts.ChannelBufferSize), fromStdout: make(chan msg, opts.ChannelBufferSize), fromListen: make(chan []byte, opts.ChannelBufferSize), } // get the basics if opts.KeybaseLocation == "" { opts.KeybaseLocation, err = locateKeybase() if err != nil { return nil, fmt.Errorf("failed to locate keybase: %v", err) } } api.kbLoc = opts.KeybaseLocation chatApiArgs, err := buildArgs(opts, "chat", "api") if err != nil { return nil, fmt.Errorf("failed to build api args: %v", err) } chatApiListenArgs, err := buildArgs(opts, "chat", "api-listen") if err != nil { return nil, fmt.Errorf("failed to build api-listen args: %v", err) } // build the commands chatApi, err := newApiCmd(ctx, api.kbLoc, chatApiArgs...) if err != nil { return nil, fmt.Errorf("failed to build api command") } chatListen, err := newApiCmd(ctx, api.kbLoc, chatApiListenArgs...) if err != nil { return nil, fmt.Errorf("failed to build api-listen command") } // create the goroutines // listen reader go func() { cr := ctxreader.NewContextReader(ctx, chatListen.Stdout()) scanner := bufio.NewScanner(cr) for scanner.Scan() { api.fromListen <- scanner.Bytes() } }() // writing to stdin go func() { for { select { case msg := <-api.toStdin: _, err := chatApi.Stdin().Write(msg) if err != nil { api.fromStdout <- errMsg{Err: err} } case <-ctx.Done(): return } } }() // reading from stdout go func() { cr := ctxreader.NewContextReader(ctx, chatApi.Stdout()) scanner := bufio.NewScanner(cr) for scanner.Scan() { api.fromStdout <- scanner.Bytes() } }() // reading from stderr go func() { cr := ctxreader.NewContextReader(ctx, chatApi.Stderr()) scanner := bufio.NewScanner(cr) for scanner.Scan() { api.fromStdout <- errMsg{Err: fmt.Errorf("%v", scanner.Text())} } }() // then start the cmds err = chatApi.Start() if err != nil { return nil, err } err = chatListen.Start() if err != nil { return nil, err } return } func (c *ChatAPI) SendRaw(ctx context.Context, msg []byte) ([]byte, error) { c.Lock() defer c.Unlock() // send message to api c.toStdin <- msg // wait for response for { select { case resp := <-c.fromStdout: switch resp := resp.(type) { case apiMsg: return resp, nil case errMsg: return nil, resp.Err } case <-ctx.Done(): return nil, ctx.Err() } } }