diff --git a/v2/chat.go b/v2/chat.go index bf8a428..36eb650 100644 --- a/v2/chat.go +++ b/v2/chat.go @@ -2,47 +2,19 @@ package keybase import ( "bufio" - "encoding/base64" - "encoding/binary" "encoding/json" "errors" + "fmt" "os/exec" - "strings" "time" -) - -// Returns a string representation of a message id suitable for use in a -// pagination struct -func getID(id uint) string { - var b []byte - switch { - case id < 128: - // 7-bit int - b = make([]byte, 1) - b = []byte{byte(id)} - - case id <= 255: - // uint8 - b = make([]byte, 2) - b = []byte{204, byte(id)} - - case id > 255 && id <= 65535: - // uint16 - b = make([]byte, 2) - binary.BigEndian.PutUint16(b, uint16(id)) - b = append([]byte{205}, b...) - case id > 65535 && id <= 4294967295: - // uint32 - b = make([]byte, 4) - binary.BigEndian.PutUint32(b, uint32(id)) - b = append([]byte{206}, b...) - } - return base64.StdEncoding.EncodeToString(b) -} + "samhofi.us/x/keybase/types/chat1" + "samhofi.us/x/keybase/types/keybase1" + "samhofi.us/x/keybase/types/stellar1" +) // Creates a string of a json-encoded channel to pass to keybase chat api-listen --filter-channel -func createFilterString(channel Channel) string { +func createFilterString(channel chat1.ChatChannel) string { if channel.Name == "" { return "" } @@ -51,7 +23,7 @@ func createFilterString(channel Channel) string { } // Creates a string of json-encoded channels to pass to keybase chat api-listen --filter-channels -func createFiltersString(channels []Channel) string { +func createFiltersString(channels []chat1.ChatChannel) string { if len(channels) == 0 { return "" } @@ -60,7 +32,7 @@ func createFiltersString(channels []Channel) string { } // Run `keybase chat api-listen` to get new messages coming into keybase and send them into the channel -func getNewMessages(k *Keybase, c chan<- ChatAPI, execOptions []string) { +func getNewMessages(k *Keybase, subs *subscriptionChannels, execOptions []string) { execString := []string{"chat", "api-listen"} if len(execOptions) > 0 { execString = append(execString, execOptions...) @@ -70,75 +42,128 @@ func getNewMessages(k *Keybase, c chan<- ChatAPI, execOptions []string) { stdOut, _ := execCmd.StdoutPipe() execCmd.Start() scanner := bufio.NewScanner(stdOut) - go func(scanner *bufio.Scanner, c chan<- ChatAPI) { - for scanner.Scan() { - var jsonData ChatAPI - json.Unmarshal([]byte(scanner.Text()), &jsonData) - if jsonData.ErrorRaw != nil { - var errorListen = string(*jsonData.ErrorRaw) - jsonData.ErrorListen = &errorListen + go func(scanner *bufio.Scanner, subs *subscriptionChannels) { + for { + scanner.Scan() + var subType subscriptionType + t := scanner.Text() + json.Unmarshal([]byte(t), &subType) + switch subType.Type { + case "chat": + var notification chat1.MsgNotification + if err := json.Unmarshal([]byte(t), ¬ification); err != nil { + subs.error <- err + break + } + if notification.Msg != nil { + subs.chat <- *notification.Msg + } + case "chat_conv": + var notification chat1.ConvNotification + if err := json.Unmarshal([]byte(t), ¬ification); err != nil { + subs.error <- err + break + } + if notification.Conv != nil { + subs.conversation <- *notification.Conv + } + case "wallet": + var holder paymentHolder + if err := json.Unmarshal([]byte(t), &holder); err != nil { + subs.error <- err + break + } + subs.wallet <- holder.Payment + default: + continue } - c <- jsonData } - }(scanner, c) + }(scanner, subs) execCmd.Wait() } } // Run runs `keybase chat api-listen`, and passes incoming messages to the message handler func -func (k *Keybase) Run(handler func(ChatAPI), options ...RunOptions) { - var heartbeatFreq int64 +func (k *Keybase) Run(handlers Handlers, options *RunOptions) { var channelCapacity = 100 runOptions := make([]string, 0) - if len(options) > 0 { - if options[0].Capacity > 0 { - channelCapacity = options[0].Capacity - } - if options[0].Heartbeat > 0 { - heartbeatFreq = options[0].Heartbeat + if handlers.WalletHandler != nil { + runOptions = append(runOptions, "--wallet") + } + if handlers.ConversationHandler != nil { + runOptions = append(runOptions, "--convs") + } + + if options != nil { + if options.Capacity > 0 { + channelCapacity = options.Capacity } - if options[0].Local { + if options.Local { runOptions = append(runOptions, "--local") } - if options[0].HideExploding { + if options.HideExploding { runOptions = append(runOptions, "--hide-exploding") } - if options[0].Dev { + if options.Dev { runOptions = append(runOptions, "--dev") } - if len(options[0].FilterChannels) > 0 { + if len(options.FilterChannels) > 0 { runOptions = append(runOptions, "--filter-channels") - runOptions = append(runOptions, createFiltersString(options[0].FilterChannels)) + runOptions = append(runOptions, createFiltersString(options.FilterChannels)) } - if options[0].FilterChannel.Name != "" { + if options.FilterChannel.Name != "" { runOptions = append(runOptions, "--filter-channel") - runOptions = append(runOptions, createFilterString(options[0].FilterChannel)) + runOptions = append(runOptions, createFilterString(options.FilterChannel)) } } - c := make(chan ChatAPI, channelCapacity) - defer close(c) - if heartbeatFreq > 0 { - go heartbeat(c, time.Duration(heartbeatFreq)*time.Minute) - } - go getNewMessages(k, c, runOptions) - for { - go handler(<-c) - } -} -// heartbeat sends a message through the channel with a message type of `heartbeat` -func heartbeat(c chan<- ChatAPI, freq time.Duration) { - m := ChatAPI{ - Type: "heartbeat", + chatCh := make(chan chat1.MsgSummary, channelCapacity) + convCh := make(chan chat1.ConvSummary, channelCapacity) + walletCh := make(chan stellar1.PaymentDetailsLocal, channelCapacity) + errorCh := make(chan error, channelCapacity) + + subs := &subscriptionChannels{ + chat: chatCh, + conversation: convCh, + wallet: walletCh, + error: errorCh, } - count := 0 + + defer close(subs.chat) + defer close(subs.conversation) + defer close(subs.wallet) + defer close(subs.error) + + go getNewMessages(k, subs, runOptions) for { - time.Sleep(freq) - m.Msg.ID = count - c <- m - count++ + select { + case chatMsg := <-subs.chat: + if handlers.ChatHandler == nil { + continue + } + chatHandler := *handlers.ChatHandler + go chatHandler(chatMsg) + case walletMsg := <-subs.wallet: + if handlers.WalletHandler == nil { + continue + } + walletHandler := *handlers.WalletHandler + go walletHandler(walletMsg) + case newConv := <-subs.conversation: + if handlers.ConversationHandler == nil { + continue + } + convHandler := *handlers.ConversationHandler + go convHandler(newConv) + case errMsg := <-subs.error: + if handlers.ErrorHandler == nil { + continue + } + errHandler := *handlers.ErrorHandler + go errHandler(errMsg) + } } } @@ -165,289 +190,391 @@ func chatAPIOut(k *Keybase, c ChatAPI) (ChatAPI, error) { return r, nil } -// Send sends a chat message -func (c Chat) Send(message ...string) (ChatAPI, error) { - m := ChatAPI{ - Params: ¶ms{}, - } - m.Params.Options = options{ - Message: &mesg{}, +// SendMessage sends a chat message +func (k *Keybase) SendMessage(method string, options SendMessageOptions) (chat1.SendRes, error) { + type res struct { + Result chat1.SendRes `json:"result"` + Error *Error `json:"error,omitempty"` } - m.Method = "send" - m.Params.Options.Channel = &c.Channel - m.Params.Options.Message.Body = strings.Join(message, " ") + var r res - r, err := chatAPIOut(c.keybase, m) + arg := newSendMessageArg(options) + arg.Method = method + + jsonBytes, _ := json.Marshal(arg) + + cmdOut, err := k.Exec("chat", "api", "-m", string(jsonBytes)) if err != nil { - return r, err + return r.Result, err } - return r, nil + + err = json.Unmarshal(cmdOut, &r) + if err != nil { + return r.Result, err + } + + if r.Error != nil { + return r.Result, fmt.Errorf("%v", r.Error.Message) + } + + return r.Result, nil } -// SendEphemeral sends an exploding chat message, with specified duration -func (c Chat) SendEphemeral(duration time.Duration, message ...string) (ChatAPI, error) { - m := ChatAPI{ - Params: ¶ms{}, +// SendMessageByChannel sends a chat message to a channel +func (k *Keybase) SendMessageByChannel(channel chat1.ChatChannel, message string, a ...interface{}) (chat1.SendRes, error) { + opts := SendMessageOptions{ + Channel: channel, + Message: SendMessageBody{ + Body: fmt.Sprintf(message, a...), + }, } - m.Params.Options = options{ - Message: &mesg{}, + + return k.SendMessage("send", opts) +} + +// SendMessageByConvID sends a chat message to a conversation id +func (k *Keybase) SendMessageByConvID(convID chat1.ConvIDStr, message string, a ...interface{}) (chat1.SendRes, error) { + opts := SendMessageOptions{ + ConversationID: convID, + Message: SendMessageBody{ + Body: fmt.Sprintf(message, a...), + }, } - m.Params.Options.ExplodingLifetime.Duration = duration - m.Method = "send" - m.Params.Options.Channel = &c.Channel - m.Params.Options.Message.Body = strings.Join(message, " ") - r, err := chatAPIOut(c.keybase, m) - if err != nil { - return r, err + return k.SendMessage("send", opts) +} + +// SendEphemeralByChannel sends an exploding chat message to a channel +func (k *Keybase) SendEphemeralByChannel(channel chat1.ChatChannel, duration time.Duration, message string, a ...interface{}) (chat1.SendRes, error) { + opts := SendMessageOptions{ + Channel: channel, + Message: SendMessageBody{ + Body: fmt.Sprintf(message, a...), + }, + ExplodingLifetime: &ExplodingLifetime{duration}, } - return r, nil + + return k.SendMessage("send", opts) } -// Reply sends a reply to a chat message -func (c Chat) Reply(replyTo int, message ...string) (ChatAPI, error) { - m := ChatAPI{ - Params: ¶ms{}, +// SendEphemeralByConvID sends an exploding chat message to a conversation id +func (k *Keybase) SendEphemeralByConvID(convID chat1.ConvIDStr, duration time.Duration, message string, a ...interface{}) (chat1.SendRes, error) { + opts := SendMessageOptions{ + ConversationID: convID, + Message: SendMessageBody{ + Body: fmt.Sprintf(message, a...), + }, + ExplodingLifetime: &ExplodingLifetime{duration}, } - m.Params.Options = options{ - Message: &mesg{}, + + return k.SendMessage("send", opts) +} + +// ReplyByChannel sends a reply message to a channel +func (k *Keybase) ReplyByChannel(channel chat1.ChatChannel, replyTo chat1.MessageID, message string, a ...interface{}) (chat1.SendRes, error) { + opts := SendMessageOptions{ + Channel: channel, + Message: SendMessageBody{ + Body: fmt.Sprintf(message, a...), + }, + ReplyTo: &replyTo, } - m.Method = "send" - m.Params.Options.Channel = &c.Channel - m.Params.Options.ReplyTo = replyTo - m.Params.Options.Message.Body = strings.Join(message, " ") + return k.SendMessage("send", opts) +} - r, err := chatAPIOut(c.keybase, m) - if err != nil { - return r, err +// ReplyByConvID sends a reply message to a conversation id +func (k *Keybase) ReplyByConvID(convID chat1.ConvIDStr, replyTo chat1.MessageID, message string, a ...interface{}) (chat1.SendRes, error) { + opts := SendMessageOptions{ + ConversationID: convID, + Message: SendMessageBody{ + Body: fmt.Sprintf(message, a...), + }, + ReplyTo: &replyTo, } - return r, nil + + return k.SendMessage("send", opts) } -// Edit edits a previously sent chat message -func (c Chat) Edit(messageID int, message ...string) (ChatAPI, error) { - m := ChatAPI{ - Params: ¶ms{}, +// EditByChannel sends an edit message to a channel +func (k *Keybase) EditByChannel(channel chat1.ChatChannel, msgID chat1.MessageID, message string, a ...interface{}) (chat1.SendRes, error) { + opts := SendMessageOptions{ + Channel: channel, + Message: SendMessageBody{ + Body: fmt.Sprintf(message, a...), + }, + MessageID: msgID, } - m.Params.Options = options{ - Message: &mesg{}, + + return k.SendMessage("edit", opts) +} + +// EditByConvID sends an edit message to a conversation id +func (k *Keybase) EditByConvID(convID chat1.ConvIDStr, msgID chat1.MessageID, message string, a ...interface{}) (chat1.SendRes, error) { + opts := SendMessageOptions{ + ConversationID: convID, + Message: SendMessageBody{ + Body: fmt.Sprintf(message, a...), + }, + MessageID: msgID, } - m.Method = "edit" - m.Params.Options.Channel = &c.Channel - m.Params.Options.Message.Body = strings.Join(message, " ") - m.Params.Options.MessageID = messageID - r, err := chatAPIOut(c.keybase, m) - if err != nil { - return r, err + return k.SendMessage("edit", opts) +} + +// ReactByChannel reacts to a message in a channel +func (k *Keybase) ReactByChannel(channel chat1.ChatChannel, msgID chat1.MessageID, message string, a ...interface{}) (chat1.SendRes, error) { + opts := SendMessageOptions{ + Channel: channel, + Message: SendMessageBody{ + Body: fmt.Sprintf(message, a...), + }, + MessageID: msgID, } - return r, nil + + return k.SendMessage("reaction", opts) } -// React sends a reaction to a message. -func (c Chat) React(messageID int, reaction string) (ChatAPI, error) { - m := ChatAPI{ - Params: ¶ms{}, +// ReactByConvID reacts to a message in a conversation id +func (k *Keybase) ReactByConvID(convID chat1.ConvIDStr, msgID chat1.MessageID, message string, a ...interface{}) (chat1.SendRes, error) { + opts := SendMessageOptions{ + ConversationID: convID, + Message: SendMessageBody{ + Body: fmt.Sprintf(message, a...), + }, + MessageID: msgID, } - m.Params.Options = options{ - Message: &mesg{}, + + return k.SendMessage("reaction", opts) +} + +// DeleteByChannel reacts to a message in a channel +func (k *Keybase) DeleteByChannel(channel chat1.ChatChannel, msgID chat1.MessageID) (chat1.SendRes, error) { + opts := SendMessageOptions{ + Channel: channel, + MessageID: msgID, } - m.Method = "reaction" - m.Params.Options.Channel = &c.Channel - m.Params.Options.Message.Body = reaction - m.Params.Options.MessageID = messageID - r, err := chatAPIOut(c.keybase, m) - if err != nil { - return r, err + return k.SendMessage("delete", opts) +} + +// DeleteByConvID reacts to a message in a conversation id +func (k *Keybase) DeleteByConvID(convID chat1.ConvIDStr, msgID chat1.MessageID) (chat1.SendRes, error) { + opts := SendMessageOptions{ + ConversationID: convID, + MessageID: msgID, } - return r, nil + + return k.SendMessage("delete", opts) } -// Delete deletes a chat message -func (c Chat) Delete(messageID int) (ChatAPI, error) { - m := ChatAPI{ - Params: ¶ms{}, +// GetConversations returns a list of all conversations. +func (k *Keybase) GetConversations(unreadOnly bool) ([]chat1.ConvSummary, error) { + type res struct { + Result []chat1.ConvSummary `json:"result"` + Error *Error `json:"error,omitempty"` } - m.Method = "delete" - m.Params.Options.Channel = &c.Channel - m.Params.Options.MessageID = messageID - r, err := chatAPIOut(c.keybase, m) + var r res + + opts := SendMessageOptions{ + UnreadOnly: unreadOnly, + } + + arg := newSendMessageArg(opts) + arg.Method = "list" + + jsonBytes, _ := json.Marshal(arg) + + cmdOut, err := k.Exec("chat", "api", "-m", string(jsonBytes)) if err != nil { - return r, err + return r.Result, err } - return r, nil -} -// ChatList returns a list of all conversations. -// You can pass a Channel to use as a filter here, but you'll probably want to -// leave the TopicName empty. -func (k *Keybase) ChatList(opts ...Channel) (ChatAPI, error) { - m := ChatAPI{ - Params: ¶ms{}, + err = json.Unmarshal(cmdOut, &r) + if err != nil { + return r.Result, err } - if len(opts) > 0 { - m.Params.Options.Name = opts[0].Name - m.Params.Options.Public = opts[0].Public - m.Params.Options.MembersType = opts[0].MembersType - m.Params.Options.TopicType = opts[0].TopicType - m.Params.Options.TopicName = opts[0].TopicName + if r.Error != nil { + return r.Result, fmt.Errorf("%v", r.Error.Message) } - m.Method = "list" - r, err := chatAPIOut(k, m) - return r, err + return r.Result, nil } -// ReadMessage fetches the chat message with the specified message id from a conversation. -func (c Chat) ReadMessage(messageID int) (*ChatAPI, error) { - m := ChatAPI{ - Params: ¶ms{}, - } - m.Params.Options = options{ - Pagination: &pagination{}, +// Read fetches chat messages +func (k *Keybase) Read(options ReadMessageOptions) (chat1.Thread, error) { + type res struct { + Result chat1.Thread `json:"result"` + Error *Error `json:"error"` } + var r res - m.Method = "read" - m.Params.Options.Channel = &c.Channel - m.Params.Options.Pagination.Num = 1 + arg := newReadMessageArg(options) - m.Params.Options.Pagination.Previous = getID(uint(messageID - 1)) + jsonBytes, _ := json.Marshal(arg) - r, err := chatAPIOut(c.keybase, m) + cmdOut, err := k.Exec("chat", "api", "-m", string(jsonBytes)) if err != nil { - return &r, err + return r.Result, err } - r.keybase = *c.keybase - return &r, nil -} -// Read fetches chat messages from a conversation. By default, 10 messages will -// be fetched at a time. However, if count is passed, then that is the number of -// messages that will be fetched. -func (c Chat) Read(count ...int) (*ChatAPI, error) { - m := ChatAPI{ - Params: ¶ms{}, + err = json.Unmarshal(cmdOut, &r) + if err != nil { + return r.Result, err } - m.Params.Options = options{ - Pagination: &pagination{}, + + if r.Error != nil { + return r.Result, fmt.Errorf("%v", r.Error.Message) } - m.Method = "read" - m.Params.Options.Channel = &c.Channel - if len(count) == 0 { - m.Params.Options.Pagination.Num = 10 - } else { - m.Params.Options.Pagination.Num = count[0] + return r.Result, nil +} + +// ReadChannel fetches chat messages for a channel +func (k *Keybase) ReadChannel(channel chat1.ChatChannel) (chat1.Thread, error) { + opts := ReadMessageOptions{ + Channel: channel, } + return k.Read(opts) +} - r, err := chatAPIOut(c.keybase, m) - if err != nil { - return &r, err +// ReadChannelNext fetches the next page of messages for a chat channel. +func (k *Keybase) ReadChannelNext(channel chat1.ChatChannel, next []byte, num int) (chat1.Thread, error) { + page := chat1.Pagination{ + Next: next, + Num: num, } - r.keybase = *c.keybase - return &r, nil + + opts := ReadMessageOptions{ + Channel: channel, + Pagination: &page, + } + return k.Read(opts) } -// Next fetches the next page of chat messages that were fetched with Read. By -// default, Next will fetch the same amount of messages that were originally -// fetched with Read. However, if count is passed, then that is the number of -// messages that will be fetched. -func (c *ChatAPI) Next(count ...int) (*ChatAPI, error) { - m := ChatAPI{ - Params: ¶ms{}, +// ReadChannelPrevious fetches the previous page of messages for a chat channel +func (k *Keybase) ReadChannelPrevious(channel chat1.ChatChannel, previous []byte, num int) (chat1.Thread, error) { + page := chat1.Pagination{ + Previous: previous, + Num: num, } - m.Params.Options = options{ - Pagination: &pagination{}, + + opts := ReadMessageOptions{ + Channel: channel, + Pagination: &page, } + return k.Read(opts) +} - m.Method = "read" - m.Params.Options.Channel = &c.Result.Messages[0].Msg.Channel - if len(count) == 0 { - m.Params.Options.Pagination.Num = c.Result.Pagination.Num - } else { - m.Params.Options.Pagination.Num = count[0] +// ReadConversation fetches chat messages for a conversation +func (k *Keybase) ReadConversation(conv chat1.ConvIDStr) (chat1.Thread, error) { + opts := ReadMessageOptions{ + ConversationID: conv, } - m.Params.Options.Pagination.Next = c.Result.Pagination.Next + return k.Read(opts) +} - result, err := chatAPIOut(&c.keybase, m) - if err != nil { - return &result, err +// ReadConversationNext fetches the next page of messages for a conversation. +func (k *Keybase) ReadConversationNext(conv chat1.ConvIDStr, next []byte, num int) (chat1.Thread, error) { + page := chat1.Pagination{ + Next: next, + Num: num, + } + + opts := ReadMessageOptions{ + ConversationID: conv, + Pagination: &page, } - k := c.keybase - *c = result - c.keybase = k - return c, nil + return k.Read(opts) } -// Previous fetches the previous page of chat messages that were fetched with Read. -// By default, Previous will fetch the same amount of messages that were -// originally fetched with Read. However, if count is passed, then that is the -// number of messages that will be fetched. -func (c *ChatAPI) Previous(count ...int) (*ChatAPI, error) { - m := ChatAPI{ - Params: ¶ms{}, +// ReadConversationPrevious fetches the previous page of messages for a chat channel +func (k *Keybase) ReadConversationPrevious(conv chat1.ConvIDStr, previous []byte, num int) (chat1.Thread, error) { + page := chat1.Pagination{ + Previous: previous, + Num: num, } - m.Params.Options = options{ - Pagination: &pagination{}, + + opts := ReadMessageOptions{ + ConversationID: conv, + Pagination: &page, } + return k.Read(opts) +} - m.Method = "read" - m.Params.Options.Channel = &c.Result.Messages[0].Msg.Channel - if len(count) == 0 { - m.Params.Options.Pagination.Num = c.Result.Pagination.Num - } else { - m.Params.Options.Pagination.Num = count[0] +// UploadToChannel attaches a file to a channel +// The filename must be an absolute path +func (k *Keybase) UploadToChannel(channel chat1.ChatChannel, title string, filename string) (chat1.SendRes, error) { + opts := SendMessageOptions{ + Channel: channel, + Title: title, + Filename: filename, } - m.Params.Options.Pagination.Previous = c.Result.Pagination.Previous - result, err := chatAPIOut(&c.keybase, m) - if err != nil { - return &result, err + return k.SendMessage("attach", opts) +} + +// UploadToConversation attaches a file to a conversation +// The filename must be an absolute path +func (k *Keybase) UploadToConversation(conv chat1.ConvIDStr, title string, filename string) (chat1.SendRes, error) { + opts := SendMessageOptions{ + ConversationID: conv, + Title: title, + Filename: filename, } - k := c.keybase - *c = result - c.keybase = k - return c, nil + + return k.SendMessage("attach", opts) } -// Upload attaches a file to a conversation -// The filepath must be an absolute path -func (c Chat) Upload(title string, filepath string) (ChatAPI, error) { - m := ChatAPI{ - Params: ¶ms{}, +// Download downloads a file +func (k *Keybase) Download(options DownloadOptions) error { + type res struct { + Error *Error `json:"error"` } - m.Method = "attach" - m.Params.Options.Channel = &c.Channel - m.Params.Options.Filename = filepath - m.Params.Options.Title = title + var r res - r, err := chatAPIOut(c.keybase, m) + arg := newDownloadArg(options) + + jsonBytes, _ := json.Marshal(arg) + + cmdOut, err := k.Exec("chat", "api", "-m", string(jsonBytes)) if err != nil { - return r, err + return err } - return r, nil + + err = json.Unmarshal(cmdOut, &r) + if err != nil { + return err + } + + if r.Error != nil { + return fmt.Errorf("%v", r.Error.Message) + } + + return nil } -// Download downloads a file from a conversation -func (c Chat) Download(messageID int, filepath string) (ChatAPI, error) { - m := ChatAPI{ - Params: ¶ms{}, +// DownloadFromChannel downloads a file from a channel +func (k *Keybase) DownloadFromChannel(channel chat1.ChatChannel, msgID chat1.MessageID, output string) error { + opts := DownloadOptions{ + Channel: channel, + MessageID: msgID, + Output: output, } - m.Method = "download" - m.Params.Options.Channel = &c.Channel - m.Params.Options.Output = filepath - m.Params.Options.MessageID = messageID + return k.Download(opts) +} - r, err := chatAPIOut(c.keybase, m) - if err != nil { - return r, err +// DownloadFromConversation downloads a file from a conversation +func (k *Keybase) DownloadFromConversation(conv chat1.ConvIDStr, msgID chat1.MessageID, output string) error { + opts := DownloadOptions{ + ConversationID: conv, + MessageID: msgID, + Output: output, } - return r, nil + return k.Download(opts) } // LoadFlip returns the results of a flip @@ -517,40 +644,104 @@ func (c Chat) Mark(messageID int) (ChatAPI, error) { return r, nil } +// AdvertiseCommands sends bot command advertisements. +// Valid values for the `Typ` field in chat1.AdvertiseCommandAPIParam are +// "public", "teamconvs", and "teammembers" +func (k *Keybase) AdvertiseCommands(options AdvertiseCommandsOptions) error { + type res struct { + Error *Error `json:"error,omitempty"` + } + + var r res + + arg := newAdvertiseCommandsArg(options) + + jsonBytes, _ := json.Marshal(arg) + + cmdOut, err := k.Exec("chat", "api", "-m", string(jsonBytes)) + if err != nil { + return err + } + + err = json.Unmarshal(cmdOut, &r) + if err != nil { + return err + } + + if r.Error != nil { + return fmt.Errorf("%v", r.Error.Message) + } + + return nil +} + // ClearCommands clears bot advertisements -func (k *Keybase) ClearCommands() (ChatAPI, error) { - m := ChatAPI{} - m.Method = "clearcommands" +func (k *Keybase) ClearCommands() error { + type res struct { + Error *Error `json:"error,omitempty"` + } + + var r res - r, err := chatAPIOut(k, m) + cmdOut, err := k.Exec("chat", "api", "-m", `{"method": "clearcommands"}`) if err != nil { - return r, err + return err } - return r, nil + + err = json.Unmarshal(cmdOut, &r) + if err != nil { + return err + } + + if r.Error != nil { + return fmt.Errorf("%v", r.Error.Message) + } + + return nil } -// AdvertiseCommands sets up bot command advertisements -// This method allows you to set up multiple different types of advertisements at once. -// Use this method if you have commands whose visibility differs from each other. -func (k *Keybase) AdvertiseCommands(advertisements []BotAdvertisement) (ChatAPI, error) { - m := ChatAPI{ - Params: ¶ms{}, +// ListMembers returns member information for a channel or conversation +func (k *Keybase) ListMembers(options ListMembersOptions) (keybase1.TeamDetails, error) { + type res struct { + Result keybase1.TeamDetails `json:"result"` + Error *Error `json:"error,omitempty"` + } + + var r res + + arg := newListMembersArg(options) + + jsonBytes, _ := json.Marshal(arg) + + cmdOut, err := k.Exec("chat", "api", "-m", string(jsonBytes)) + if err != nil { + return r.Result, err } - m.Method = "advertisecommands" - m.Params.Options.BotAdvertisements = advertisements - r, err := chatAPIOut(k, m) + err = json.Unmarshal(cmdOut, &r) if err != nil { - return r, err + return r.Result, err } - return r, nil + + if r.Error != nil { + return r.Result, fmt.Errorf("%v", r.Error.Message) + } + + return r.Result, nil +} + +// ListMembersOfChannel returns member information for a channel +func (k *Keybase) ListMembersOfChannel(channel chat1.ChatChannel) (keybase1.TeamDetails, error) { + opts := ListMembersOptions{ + Channel: channel, + } + return k.ListMembers(opts) } -// AdvertiseCommand sets up bot command advertisements -// This method allows you to set up one type of advertisement. -// Use this method if you have commands whose visibility should all be the same. -func (k *Keybase) AdvertiseCommand(advertisement BotAdvertisement) (ChatAPI, error) { - return k.AdvertiseCommands([]BotAdvertisement{ - advertisement, - }) +// ListMembersOfConversation returns member information for a conversation +func (k *Keybase) ListMembersOfConversation(convID chat1.ConvIDStr) (keybase1.TeamDetails, error) { + opts := ListMembersOptions{ + ConversationID: convID, + } + return k.ListMembers(opts) } diff --git a/v2/docs.go b/v2/docs.go index d6cee83..2042b0f 100644 --- a/v2/docs.go +++ b/v2/docs.go @@ -1,5 +1,5 @@ /* -The keybase package implements an interface for interacting with the Keybase Chat, Team, and Wallet APIs +Package keybase implements an interface for interacting with the Keybase Chat, Team, and Wallet APIs I've tried to follow Keybase's JSON API as closely as possible, so if you're stuck on anything, or wondering why things are organized in a certain way, it's most likely due to that. It may be helpful to look at the diff --git a/v2/docs_test.go b/v2/docs_test.go index e92ece6..fc08dd7 100644 --- a/v2/docs_test.go +++ b/v2/docs_test.go @@ -1,20 +1,35 @@ package keybase -func ExampleKeybase_AdvertiseCommand() { +import "samhofi.us/x/keybase/types/chat1" + +func ExampleKeybase_AdvertiseCommands() { var k = NewKeybase() // Clear out any previously advertised commands k.ClearCommands() // Create BotAdvertisement - c := BotAdvertisement{ - Type: "public", - BotCommands: []BotCommand{ - NewBotCommand("help", "Get help using this bot", "!help "), - NewBotCommand("hello", "Say hello", "!hello"), + ads := AdvertiseCommandsOptions{ + Alias: "RSS Bot", + Advertisements: []chat1.AdvertiseCommandAPIParam{ + { + Typ: "public", + Commands: []chat1.UserBotCommandInput{ + { + Name: "rss addfeed", + Description: "Add RSS feed", + Usage: "", + }, + { + Name: "rss delfeed", + Description: "Remove RSS feed", + Usage: "", + }, + }, + }, }, } // Send advertisement - k.AdvertiseCommand(c) + k.AdvertiseCommands(ads) } diff --git a/v2/keybase.go b/v2/keybase.go index 95c7b53..71722c0 100644 --- a/v2/keybase.go +++ b/v2/keybase.go @@ -5,6 +5,8 @@ import ( "fmt" "os/exec" "strings" + + "samhofi.us/x/keybase/types/chat1" ) // Possible MemberTypes @@ -38,30 +40,6 @@ func NewKeybase(path ...string) *Keybase { return k } -// NewBotCommand returns a new BotCommand instance -func NewBotCommand(name, description, usage string, extendedDescription ...BotCommandExtendedDescription) BotCommand { - result := BotCommand{ - Name: name, - Description: description, - Usage: usage, - } - - if len(extendedDescription) > 0 { - result.ExtendedDescription = &extendedDescription[0] - } - - return result -} - -// NewBotCommandExtendedDescription -func NewBotCommandExtendedDescription(title, desktopBody, mobileBody string) BotCommandExtendedDescription { - return BotCommandExtendedDescription{ - Title: title, - DesktopBody: desktopBody, - MobileBody: mobileBody, - } -} - // Exec executes the given Keybase command func (k *Keybase) Exec(command ...string) ([]byte, error) { out, err := exec.Command(k.Path, command...).Output() @@ -72,7 +50,7 @@ func (k *Keybase) Exec(command ...string) ([]byte, error) { } // NewChat returns a new Chat instance -func (k *Keybase) NewChat(channel Channel) Chat { +func (k *Keybase) NewChat(channel chat1.ChatChannel) Chat { return Chat{ keybase: k, Channel: channel, @@ -87,14 +65,6 @@ func (k *Keybase) NewTeam(name string) Team { } } -// NewKV returns a new KV instance -func (k *Keybase) NewKV(team string) KV { - return KV{ - keybase: k, - Team: team, - } -} - // NewWallet returns a new Wallet instance func (k *Keybase) NewWallet() Wallet { return Wallet{ diff --git a/v2/kvstore.go b/v2/kvstore.go index ac91b25..96d1ca7 100644 --- a/v2/kvstore.go +++ b/v2/kvstore.go @@ -2,136 +2,190 @@ package keybase import ( "encoding/json" - "errors" + "fmt" + + "samhofi.us/x/keybase/types/keybase1" ) -// kvAPIOut sends a JSON request to the kvstore API and returns its response. -func kvAPIOut(k *Keybase, kv KVAPI) (KVAPI, error) { - jsonBytes, _ := json.Marshal(kv) +// KVListNamespaces returns all namespaces for a team +func (k *Keybase) KVListNamespaces(team *string) (keybase1.KVListNamespaceResult, error) { + type res struct { + Result keybase1.KVListNamespaceResult `json:"result"` + Error *Error `json:"error"` + } + var r res + + arg := newKVArg("list", KVOptions{ + Team: team, + }) + + jsonBytes, _ := json.Marshal(arg) cmdOut, err := k.Exec("kvstore", "api", "-m", string(jsonBytes)) if err != nil { - return KVAPI{}, err + return r.Result, err } - var r KVAPI - if err := json.Unmarshal(cmdOut, &r); err != nil { - return KVAPI{}, err + err = json.Unmarshal(cmdOut, &r) + if err != nil { + return r.Result, err } if r.Error != nil { - return KVAPI{}, errors.New(r.Error.Message) + return r.Result, fmt.Errorf("%s", r.Error.Message) } - return r, nil + return r.Result, nil } -// Namespaces returns all namespaces for a team -func (kv KV) Namespaces() (KVAPI, error) { - m := KVAPI{ - Params: &kvParams{}, - } - m.Params.Options = kvOptions{ - Team: kv.Team, +// KVListKeys returns all non-deleted keys for a namespace +func (k *Keybase) KVListKeys(team *string, namespace string) (keybase1.KVListEntryResult, error) { + type res struct { + Result keybase1.KVListEntryResult `json:"result"` + Error *Error `json:"error"` } + var r res + + arg := newKVArg("list", KVOptions{ + Team: team, + Namespace: &namespace, + }) - m.Method = "list" + jsonBytes, _ := json.Marshal(arg) - r, err := kvAPIOut(kv.keybase, m) + cmdOut, err := k.Exec("kvstore", "api", "-m", string(jsonBytes)) if err != nil { - return r, err + return r.Result, err } - return r, nil -} -// Keys returns all non-deleted keys for a namespace -func (kv KV) Keys(namespace string) (KVAPI, error) { - m := KVAPI{ - Params: &kvParams{}, + err = json.Unmarshal(cmdOut, &r) + if err != nil { + return r.Result, err } - m.Params.Options = kvOptions{ - Team: kv.Team, - Namespace: namespace, + + if r.Error != nil { + return r.Result, fmt.Errorf("%s", r.Error.Message) } - m.Method = "list" + return r.Result, nil +} - r, err := kvAPIOut(kv.keybase, m) - if err != nil { - return r, err +// KVGet returns an entry +func (k *Keybase) KVGet(team *string, namespace string, key string) (keybase1.KVGetResult, error) { + type res struct { + Result keybase1.KVGetResult `json:"result"` + Error *Error `json:"error"` } - return r, nil -} + var r res + + arg := newKVArg("get", KVOptions{ + Team: team, + Namespace: &namespace, + EntryKey: &key, + }) -// Get returns an entry -func (kv KV) Get(namespace string, key string, revision ...uint) (KVAPI, error) { - m := KVAPI{ - Params: &kvParams{}, + jsonBytes, _ := json.Marshal(arg) + + cmdOut, err := k.Exec("kvstore", "api", "-m", string(jsonBytes)) + if err != nil { + return r.Result, err } - m.Params.Options = kvOptions{ - Team: kv.Team, - Namespace: namespace, - EntryKey: key, + + err = json.Unmarshal(cmdOut, &r) + if err != nil { + return r.Result, err } - if len(revision) > 0 { - m.Params.Options.Revision = revision[0] + if r.Error != nil { + return r.Result, fmt.Errorf("%s", r.Error.Message) } - m.Method = "get" + return r.Result, nil +} - r, err := kvAPIOut(kv.keybase, m) - if err != nil { - return r, err +// KVPutWithRevision puts an entry, specifying the revision number +func (k *Keybase) KVPutWithRevision(team *string, namespace string, key string, value string, revision int) (keybase1.KVPutResult, error) { + type res struct { + Result keybase1.KVPutResult `json:"result"` + Error *Error `json:"error"` } - return r, nil -} + var r res -// Put adds an entry -func (kv KV) Put(namespace string, key string, value string, revision ...uint) (KVAPI, error) { - m := KVAPI{ - Params: &kvParams{}, + opts := KVOptions{ + Team: team, + Namespace: &namespace, + EntryKey: &key, + EntryValue: &value, } - m.Params.Options = kvOptions{ - Team: kv.Team, - Namespace: namespace, - EntryKey: key, - EntryValue: value, + if revision != 0 { + opts.Revision = &revision } - if len(revision) > 0 { - m.Params.Options.Revision = revision[0] - } + arg := newKVArg("put", opts) - m.Method = "put" + jsonBytes, _ := json.Marshal(arg) - r, err := kvAPIOut(kv.keybase, m) + cmdOut, err := k.Exec("kvstore", "api", "-m", string(jsonBytes)) if err != nil { - return r, err + return r.Result, err + } + + err = json.Unmarshal(cmdOut, &r) + if err != nil { + return r.Result, err } - return r, nil -} -// Delete removes an entry -func (kv KV) Delete(namespace string, key string, revision ...uint) (KVAPI, error) { - m := KVAPI{ - Params: &kvParams{}, + if r.Error != nil { + return r.Result, fmt.Errorf("%s", r.Error.Message) } - m.Params.Options = kvOptions{ - Team: kv.Team, - Namespace: namespace, - EntryKey: key, + + return r.Result, nil +} + +// KVPut puts an entry +func (k *Keybase) KVPut(team *string, namespace string, key string, value string) (keybase1.KVPutResult, error) { + return k.KVPutWithRevision(team, namespace, key, value, 0) +} + +// KVDeleteWithRevision deletes an entry, specifying the revision number +func (k *Keybase) KVDeleteWithRevision(team *string, namespace string, key string, revision int) (keybase1.KVDeleteEntryResult, error) { + type res struct { + Result keybase1.KVDeleteEntryResult `json:"result"` + Error *Error `json:"error"` } + var r res - if len(revision) > 0 { - m.Params.Options.Revision = revision[0] + opts := KVOptions{ + Team: team, + Namespace: &namespace, + EntryKey: &key, } + if revision != 0 { + opts.Revision = &revision + } + + arg := newKVArg("del", opts) + + jsonBytes, _ := json.Marshal(arg) - m.Method = "del" + cmdOut, err := k.Exec("kvstore", "api", "-m", string(jsonBytes)) + if err != nil { + return r.Result, err + } - r, err := kvAPIOut(kv.keybase, m) + err = json.Unmarshal(cmdOut, &r) if err != nil { - return r, err + return r.Result, err } - return r, nil + + if r.Error != nil { + return r.Result, fmt.Errorf("%s", r.Error.Message) + } + + return r.Result, nil +} + +// KVDelete deletes an entry +func (k *Keybase) KVDelete(team *string, namespace string, key string) (keybase1.KVDeleteEntryResult, error) { + return k.KVDeleteWithRevision(team, namespace, key, 0) } diff --git a/v2/types.go b/v2/types.go index 7a10120..01819f9 100644 --- a/v2/types.go +++ b/v2/types.go @@ -5,18 +5,233 @@ import ( "fmt" "strings" "time" + + "samhofi.us/x/keybase/types/chat1" + "samhofi.us/x/keybase/types/stellar1" ) // RunOptions holds a set of options to be passed to Run type RunOptions struct { - Capacity int // Channel capacity for the buffered channel that holds messages. Defaults to 100 if not set - Heartbeat int64 // Send a heartbeat through the channel every X minutes (0 = off) - Local bool // Subscribe to local messages - HideExploding bool // Ignore exploding messages - Dev bool // Subscribe to dev channel messages - Wallet bool // Subscribe to wallet events - FilterChannel Channel // Only subscribe to messages from specified channel - FilterChannels []Channel // Only subscribe to messages from specified channels + Capacity int // Channel capacity for the buffered channel that holds messages. Defaults to 100 if not set + Local bool // Subscribe to local messages + HideExploding bool // Ignore exploding messages + Dev bool // Subscribe to dev channel messages + FilterChannel chat1.ChatChannel // Only subscribe to messages from specified channel + FilterChannels []chat1.ChatChannel // Only subscribe to messages from specified channels +} + +type subscriptionType struct { + Type string `json:"type"` +} + +type paymentHolder struct { + Payment stellar1.PaymentDetailsLocal `json:"notification"` +} + +// Handlers holds pointers to handlers that you want to implement inside the bot type +type Handlers struct { + ChatHandler *func(chat1.MsgSummary) + ConversationHandler *func(chat1.ConvSummary) + WalletHandler *func(stellar1.PaymentDetailsLocal) + ErrorHandler *func(error) +} + +// subscriptionChannels are passed to getNewMessages to return data through channels +type subscriptionChannels struct { + chat chan chat1.MsgSummary + conversation chan chat1.ConvSummary + wallet chan stellar1.PaymentDetailsLocal + error chan error +} + +// Error holds an error message returned by the API +type Error struct { + Code int `json:"code"` + Message string `json:"message"` +} + +// ExplodingLifetime holds a time duration for ephemeral messages +type ExplodingLifetime struct { + time.Duration +} + +// UnmarshalJSON unpacks exploding lifetimes from JSON +func (d *ExplodingLifetime) UnmarshalJSON(b []byte) (err error) { + d.Duration, err = time.ParseDuration(strings.Trim(string(b), `"`)) + return +} + +// MarshalJSON packs exploding lifetimes to JSON +func (d *ExplodingLifetime) MarshalJSON() (b []byte, err error) { + return []byte(fmt.Sprintf(`"%s"`, d.String())), nil +} + +// SendMessageBody holds the body string for all send and reply methods +type SendMessageBody struct { + Body string +} + +// SendMessageOptions holds a set of options to be passed to SendMessage +type SendMessageOptions struct { + Channel chat1.ChatChannel `json:"channel,omitempty"` + ConversationID chat1.ConvIDStr `json:"conversation_id,omitempty"` + Message SendMessageBody `json:",omitempty"` + Filename string `json:"filename,omitempty"` + Title string `json:"title,omitempty"` + MessageID chat1.MessageID `json:"message_id,omitempty"` + ConfirmLumenSend bool `json:"confirm_lumen_send"` + ReplyTo *chat1.MessageID `json:"reply_to,omitempty"` + ExplodingLifetime *ExplodingLifetime `json:"exploding_lifetime,omitempty"` + UnreadOnly bool `json:"unread_only,omitempty"` +} + +type sendMessageParams struct { + Options SendMessageOptions +} + +type sendMessageArg struct { + Method string + Params sendMessageParams +} + +func newSendMessageArg(options SendMessageOptions) sendMessageArg { + return sendMessageArg{ + Method: "send", + Params: sendMessageParams{ + Options: options, + }, + } +} + +// ReadMessageOptions holds a set of options to be passed to Read +type ReadMessageOptions struct { + Channel chat1.ChatChannel `json:"channel,omitempty"` + ConversationID chat1.ConvIDStr `json:"conversation_id,omitempty"` + Pagination *chat1.Pagination `json:"pagination,omitempty"` + Peek bool `json:"peek"` + UnreadOnly bool `json:"unread_only"` + FailOffline bool `json:"fail_offline"` +} + +type readMessageParams struct { + Options ReadMessageOptions +} + +type readMessageArg struct { + Method string + Params readMessageParams +} + +func newReadMessageArg(options ReadMessageOptions) readMessageArg { + return readMessageArg{ + Method: "read", + Params: readMessageParams{ + Options: options, + }, + } +} + +// AdvertiseCommandsOptions holds a set of options to be passed to AdvertiseCommands +type AdvertiseCommandsOptions struct { + Alias string + Advertisements []chat1.AdvertiseCommandAPIParam +} + +type advertiseCommandsParams struct { + Options AdvertiseCommandsOptions +} + +type advertiseCommandsArg struct { + Method string + Params advertiseCommandsParams +} + +func newAdvertiseCommandsArg(options AdvertiseCommandsOptions) advertiseCommandsArg { + return advertiseCommandsArg{ + Method: "advertisecommands", + Params: advertiseCommandsParams{ + Options: options, + }, + } +} + +// DownloadOptions holds a set of options to be passed to Download +type DownloadOptions struct { + Channel chat1.ChatChannel + ConversationID chat1.ConvIDStr `json:"conversation_id"` + MessageID chat1.MessageID `json:"message_id"` + Output string + Preview bool + NoStream bool +} + +type downloadParams struct { + Options DownloadOptions +} + +type downloadArg struct { + Method string + Params downloadParams +} + +func newDownloadArg(options DownloadOptions) downloadArg { + return downloadArg{ + Method: "download", + Params: downloadParams{ + Options: options, + }, + } +} + +// ListMembersOptions holds a set of options to be passed to ListMembers +type ListMembersOptions struct { + Channel chat1.ChatChannel + ConversationID chat1.ConvIDStr `json:"conversation_id"` +} + +type listMembersParams struct { + Options ListMembersOptions +} + +type listMembersArg struct { + Method string + Params listMembersParams +} + +func newListMembersArg(options ListMembersOptions) listMembersArg { + return listMembersArg{ + Method: "listmembers", + Params: listMembersParams{ + Options: options, + }, + } +} + +// KVOptions holds a set of options to be passed to the KV methods +type KVOptions struct { + Team *string `json:"team"` + Namespace *string `json:"namespace,omitempty"` + EntryKey *string `json:"entryKey,omitempty"` + EntryValue *string `json:"entryValue,omitempty"` + Revision *int `json:"revision,omitempty"` +} + +type kvParams struct { + Options KVOptions `json:"options"` +} + +type kvArg struct { + Method string `json:"method"` + Params kvParams `json:"params"` +} + +func newKVArg(method string, options KVOptions) kvArg { + return kvArg{ + Method: method, + Params: kvParams{ + Options: options, + }, + } } // ChatAPI holds information about a message received by the `keybase chat api-listen` command @@ -229,19 +444,19 @@ type content struct { } type msg struct { - ID int `json:"id"` - ConversationID string `json:"conversation_id"` - Channel Channel `json:"channel"` - Sender sender `json:"sender"` - SentAt int `json:"sent_at"` - SentAtMs int64 `json:"sent_at_ms"` - Content content `json:"content"` - Unread bool `json:"unread"` - AtMentionUsernames []string `json:"at_mention_usernames"` - IsEphemeral bool `json:"is_ephemeral"` - Etime int64 `json:"etime"` - HasPairwiseMacs bool `json:"has_pairwise_macs"` - ChannelMention string `json:"channel_mention"` + ID int `json:"id"` + ConversationID string `json:"conversation_id"` + Channel chat1.ChatChannel `json:"channel"` + Sender sender `json:"sender"` + SentAt int `json:"sent_at"` + SentAtMs int64 `json:"sent_at_ms"` + Content content `json:"content"` + Unread bool `json:"unread"` + AtMentionUsernames []string `json:"at_mention_usernames"` + IsEphemeral bool `json:"is_ephemeral"` + Etime int64 `json:"etime"` + HasPairwiseMacs bool `json:"has_pairwise_macs"` + ChannelMention string `json:"channel_mention"` } type summary struct { @@ -296,53 +511,12 @@ type notification struct { Details details `json:"details"` } -// Channel holds information about a conversation -type Channel struct { - Name string `json:"name,omitempty"` - Public bool `json:"public,omitempty"` - MembersType string `json:"members_type,omitempty"` - TopicType string `json:"topic_type,omitempty"` - TopicName string `json:"topic_name,omitempty"` -} - -type BotCommand struct { - Name string `json:"name"` - Description string `json:"description"` - Usage string `json:"usage"` - ExtendedDescription *BotCommandExtendedDescription `json:"extended_description,omitempty"` -} - -type BotCommandExtendedDescription struct { - Title string `json:"title"` - DesktopBody string `json:"desktop_body"` - MobileBody string `json:"mobile_body"` -} - -type BotAdvertisement struct { - Type string `json:"type"` // "public", "teamconvs", "teammembers" - TeamName string `json:"team_name,omitempty"` // required if Type is not "public" - BotCommands []BotCommand `json:"commands"` -} - type mesg struct { Body string `json:"body"` } -type duration struct { - time.Duration -} - -func (d *duration) UnmarshalJSON(b []byte) (err error) { - d.Duration, err = time.ParseDuration(strings.Trim(string(b), `"`)) - return -} - -func (d *duration) MarshalJSON() (b []byte, err error) { - return []byte(fmt.Sprintf(`"%s"`, d.String())), nil -} - type options struct { - Channel *Channel `json:"channel,omitempty"` + Channel *chat1.ChatChannel `json:"channel,omitempty"` MessageID int `json:"message_id,omitempty"` Message *mesg `json:"message,omitempty"` Pagination *pagination `json:"pagination,omitempty"` @@ -355,8 +529,7 @@ type options struct { ReplyTo int `json:"reply_to,omitempty"` GameID string `json:"game_id,omitempty"` Alias string `json:"alias,omitempty"` - BotAdvertisements []BotAdvertisement `json:"advertisements,omitempty"` - ExplodingLifetime duration `json:"exploding_lifetime,omitempty"` + //ExplodingLifetime duration `json:"exploding_lifetime,omitempty"` Name string `json:"name,omitempty"` Public bool `json:"public,omitempty"` @@ -437,12 +610,12 @@ type rateLimits struct { } type conversation struct { - ID string `json:"id"` - Channel Channel `json:"channel"` - Unread bool `json:"unread"` - ActiveAt int `json:"active_at"` - ActiveAtMs int64 `json:"active_at_ms"` - MemberStatus string `json:"member_status"` + ID string `json:"id"` + Channel chat1.ChatChannel `json:"channel"` + Unread bool `json:"unread"` + ActiveAt int `json:"active_at"` + ActiveAtMs int64 `json:"active_at_ms"` + MemberStatus string `json:"member_status"` } type SendPayment struct { @@ -602,11 +775,6 @@ type tParams struct { Options tOptions `json:"options"` } -type Error struct { - Code int `json:"code"` - Message string `json:"message"` -} - type tResult struct { ChatSent bool `json:"chatSent"` CreatorAdded bool `json:"creatorAdded"` @@ -646,41 +814,6 @@ type teamInfo struct { Implicit implicit `json:"implicit,omitempty"` } -// KVAPI holds information sent and received to/from the kvstore api -type KVAPI struct { - Method string `json:"method,omitempty"` - Params *kvParams `json:"params,omitempty"` - Result *kvResult `json:"result,omitempty"` - Error *Error `json:"error"` - keybase Keybase -} - -type kvOptions struct { - Team string `json:"team,omitempty"` - Namespace string `json:"namespace,omitempty"` - EntryKey string `json:"entryKey,omitempty"` - Revision uint `json:"revision,omitempty"` - EntryValue string `json:"entryValue,omitempty"` -} - -type kvParams struct { - Options kvOptions `json:"options,omitempty"` -} - -type entryKey struct { - EntryKey string `json:"entryKey"` - Revision uint `json:"revision"` -} - -type kvResult struct { - TeamName string `json:"teamName"` - Namespaces []string `json:"namespaces"` - EntryKeys []entryKey `json:"entryKeys"` - EntryKey string `json:"entryKey"` - EntryValue string `json:"entryValue"` - Revision uint `json:"revision"` -} - // UserAPI holds information received from the user/lookup api type UserAPI struct { Status uStatus `json:"status"` @@ -831,7 +964,7 @@ type Keybase struct { // Chat holds basic information about a specific conversation type Chat struct { keybase *Keybase - Channel Channel + Channel chat1.ChatChannel } type chat interface { @@ -883,29 +1016,12 @@ type wallet interface { TxDetail(txid string) (WalletAPI, error) } -// KV holds basic information about a KVStore -type KV struct { - keybase *Keybase - Team string -} - -type kvInterface interface { - Namespaces() (KVAPI, error) - Keys(namespace string) (KVAPI, error) - Get(namespace string, key string) (KVAPI, error) - Put(namespace string, key string, value string) (KVAPI, error) - Delete(namespace string, key string) (KVAPI, error) -} - type keybase interface { - AdvertiseCommand(advertisement BotAdvertisement) (ChatAPI, error) - AdvertiseCommands(advertisements []BotAdvertisement) (ChatAPI, error) - ChatList(opts ...Channel) (ChatAPI, error) + ChatList(opts ...chat1.ChatChannel) (ChatAPI, error) ClearCommands() (ChatAPI, error) CreateTeam(name string) (TeamAPI, error) - NewChat(channel Channel) Chat + NewChat(channel chat1.ChatChannel) Chat NewTeam(name string) Team - NewKV(team string) KV NewWallet() Wallet Run(handler func(ChatAPI), options ...RunOptions) status() status