Browse Source

Version 2.0.0-alpha1 (really)

Sam 5 years ago
parent
commit
abed5bc066
  1. 797
      v2/chat.go
  2. 2
      v2/docs.go
  3. 29
      v2/docs_test.go
  4. 36
      v2/keybase.go
  5. 218
      v2/kvstore.go
  6. 378
      v2/types.go

797
v2/chat.go

@ -2,47 +2,19 @@ package keybase
import ( import (
"bufio" "bufio"
"encoding/base64"
"encoding/binary"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt"
"os/exec" "os/exec"
"strings"
"time" "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: "samhofi.us/x/keybase/types/chat1"
// uint32 "samhofi.us/x/keybase/types/keybase1"
b = make([]byte, 4) "samhofi.us/x/keybase/types/stellar1"
binary.BigEndian.PutUint32(b, uint32(id)) )
b = append([]byte{206}, b...)
}
return base64.StdEncoding.EncodeToString(b)
}
// Creates a string of a json-encoded channel to pass to keybase chat api-listen --filter-channel // 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 == "" { if channel.Name == "" {
return "" 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 // 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 { if len(channels) == 0 {
return "" 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 // 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"} execString := []string{"chat", "api-listen"}
if len(execOptions) > 0 { if len(execOptions) > 0 {
execString = append(execString, execOptions...) execString = append(execString, execOptions...)
@ -70,75 +42,128 @@ func getNewMessages(k *Keybase, c chan<- ChatAPI, execOptions []string) {
stdOut, _ := execCmd.StdoutPipe() stdOut, _ := execCmd.StdoutPipe()
execCmd.Start() execCmd.Start()
scanner := bufio.NewScanner(stdOut) scanner := bufio.NewScanner(stdOut)
go func(scanner *bufio.Scanner, c chan<- ChatAPI) { go func(scanner *bufio.Scanner, subs *subscriptionChannels) {
for scanner.Scan() { for {
var jsonData ChatAPI scanner.Scan()
json.Unmarshal([]byte(scanner.Text()), &jsonData) var subType subscriptionType
if jsonData.ErrorRaw != nil { t := scanner.Text()
var errorListen = string(*jsonData.ErrorRaw) json.Unmarshal([]byte(t), &subType)
jsonData.ErrorListen = &errorListen switch subType.Type {
case "chat":
var notification chat1.MsgNotification
if err := json.Unmarshal([]byte(t), &notification); 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), &notification); 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() execCmd.Wait()
} }
} }
// Run runs `keybase chat api-listen`, and passes incoming messages to the message handler func // Run runs `keybase chat api-listen`, and passes incoming messages to the message handler func
func (k *Keybase) Run(handler func(ChatAPI), options ...RunOptions) { func (k *Keybase) Run(handlers Handlers, options *RunOptions) {
var heartbeatFreq int64
var channelCapacity = 100 var channelCapacity = 100
runOptions := make([]string, 0) runOptions := make([]string, 0)
if len(options) > 0 { if handlers.WalletHandler != nil {
if options[0].Capacity > 0 { runOptions = append(runOptions, "--wallet")
channelCapacity = options[0].Capacity }
} if handlers.ConversationHandler != nil {
if options[0].Heartbeat > 0 { runOptions = append(runOptions, "--convs")
heartbeatFreq = options[0].Heartbeat }
if options != nil {
if options.Capacity > 0 {
channelCapacity = options.Capacity
} }
if options[0].Local { if options.Local {
runOptions = append(runOptions, "--local") runOptions = append(runOptions, "--local")
} }
if options[0].HideExploding { if options.HideExploding {
runOptions = append(runOptions, "--hide-exploding") runOptions = append(runOptions, "--hide-exploding")
} }
if options[0].Dev { if options.Dev {
runOptions = append(runOptions, "--dev") runOptions = append(runOptions, "--dev")
} }
if len(options[0].FilterChannels) > 0 { if len(options.FilterChannels) > 0 {
runOptions = append(runOptions, "--filter-channels") 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, "--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` chatCh := make(chan chat1.MsgSummary, channelCapacity)
func heartbeat(c chan<- ChatAPI, freq time.Duration) { convCh := make(chan chat1.ConvSummary, channelCapacity)
m := ChatAPI{ walletCh := make(chan stellar1.PaymentDetailsLocal, channelCapacity)
Type: "heartbeat", 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 { for {
time.Sleep(freq) select {
m.Msg.ID = count case chatMsg := <-subs.chat:
c <- m if handlers.ChatHandler == nil {
count++ 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 return r, nil
} }
// Send sends a chat message // SendMessage sends a chat message
func (c Chat) Send(message ...string) (ChatAPI, error) { func (k *Keybase) SendMessage(method string, options SendMessageOptions) (chat1.SendRes, error) {
m := ChatAPI{ type res struct {
Params: &params{}, Result chat1.SendRes `json:"result"`
} Error *Error `json:"error,omitempty"`
m.Params.Options = options{
Message: &mesg{},
} }
m.Method = "send" var r res
m.Params.Options.Channel = &c.Channel
m.Params.Options.Message.Body = strings.Join(message, " ")
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 { 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 // SendMessageByChannel sends a chat message to a channel
func (c Chat) SendEphemeral(duration time.Duration, message ...string) (ChatAPI, error) { func (k *Keybase) SendMessageByChannel(channel chat1.ChatChannel, message string, a ...interface{}) (chat1.SendRes, error) {
m := ChatAPI{ opts := SendMessageOptions{
Params: &params{}, 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) return k.SendMessage("send", opts)
if err != nil { }
return r, err
// 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 // SendEphemeralByConvID sends an exploding chat message to a conversation id
func (c Chat) Reply(replyTo int, message ...string) (ChatAPI, error) { func (k *Keybase) SendEphemeralByConvID(convID chat1.ConvIDStr, duration time.Duration, message string, a ...interface{}) (chat1.SendRes, error) {
m := ChatAPI{ opts := SendMessageOptions{
Params: &params{}, 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" return k.SendMessage("send", opts)
m.Params.Options.Channel = &c.Channel }
m.Params.Options.ReplyTo = replyTo
m.Params.Options.Message.Body = strings.Join(message, " ")
r, err := chatAPIOut(c.keybase, m) // ReplyByConvID sends a reply message to a conversation id
if err != nil { func (k *Keybase) ReplyByConvID(convID chat1.ConvIDStr, replyTo chat1.MessageID, message string, a ...interface{}) (chat1.SendRes, error) {
return r, err 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 // EditByChannel sends an edit message to a channel
func (c Chat) Edit(messageID int, message ...string) (ChatAPI, error) { func (k *Keybase) EditByChannel(channel chat1.ChatChannel, msgID chat1.MessageID, message string, a ...interface{}) (chat1.SendRes, error) {
m := ChatAPI{ opts := SendMessageOptions{
Params: &params{}, 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) return k.SendMessage("edit", opts)
if err != nil { }
return r, err
// 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. // ReactByConvID reacts to a message in a conversation id
func (c Chat) React(messageID int, reaction string) (ChatAPI, error) { func (k *Keybase) ReactByConvID(convID chat1.ConvIDStr, msgID chat1.MessageID, message string, a ...interface{}) (chat1.SendRes, error) {
m := ChatAPI{ opts := SendMessageOptions{
Params: &params{}, 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) return k.SendMessage("delete", opts)
if err != nil { }
return r, err
// 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 // GetConversations returns a list of all conversations.
func (c Chat) Delete(messageID int) (ChatAPI, error) { func (k *Keybase) GetConversations(unreadOnly bool) ([]chat1.ConvSummary, error) {
m := ChatAPI{ type res struct {
Params: &params{}, 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 { if err != nil {
return r, err return r.Result, err
} }
return r, nil
}
// ChatList returns a list of all conversations. err = json.Unmarshal(cmdOut, &r)
// You can pass a Channel to use as a filter here, but you'll probably want to if err != nil {
// leave the TopicName empty. return r.Result, err
func (k *Keybase) ChatList(opts ...Channel) (ChatAPI, error) {
m := ChatAPI{
Params: &params{},
} }
if len(opts) > 0 { if r.Error != nil {
m.Params.Options.Name = opts[0].Name return r.Result, fmt.Errorf("%v", r.Error.Message)
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
} }
m.Method = "list"
r, err := chatAPIOut(k, m) return r.Result, nil
return r, err
} }
// ReadMessage fetches the chat message with the specified message id from a conversation. // Read fetches chat messages
func (c Chat) ReadMessage(messageID int) (*ChatAPI, error) { func (k *Keybase) Read(options ReadMessageOptions) (chat1.Thread, error) {
m := ChatAPI{ type res struct {
Params: &params{}, Result chat1.Thread `json:"result"`
} Error *Error `json:"error"`
m.Params.Options = options{
Pagination: &pagination{},
} }
var r res
m.Method = "read" arg := newReadMessageArg(options)
m.Params.Options.Channel = &c.Channel
m.Params.Options.Pagination.Num = 1
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 { 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 err = json.Unmarshal(cmdOut, &r)
// be fetched at a time. However, if count is passed, then that is the number of if err != nil {
// messages that will be fetched. return r.Result, err
func (c Chat) Read(count ...int) (*ChatAPI, error) {
m := ChatAPI{
Params: &params{},
} }
m.Params.Options = options{
Pagination: &pagination{}, if r.Error != nil {
return r.Result, fmt.Errorf("%v", r.Error.Message)
} }
m.Method = "read" return r.Result, nil
m.Params.Options.Channel = &c.Channel }
if len(count) == 0 {
m.Params.Options.Pagination.Num = 10 // ReadChannel fetches chat messages for a channel
} else { func (k *Keybase) ReadChannel(channel chat1.ChatChannel) (chat1.Thread, error) {
m.Params.Options.Pagination.Num = count[0] opts := ReadMessageOptions{
Channel: channel,
} }
return k.Read(opts)
}
r, err := chatAPIOut(c.keybase, m) // ReadChannelNext fetches the next page of messages for a chat channel.
if err != nil { func (k *Keybase) ReadChannelNext(channel chat1.ChatChannel, next []byte, num int) (chat1.Thread, error) {
return &r, err 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 // ReadChannelPrevious fetches the previous page of messages for a chat channel
// default, Next will fetch the same amount of messages that were originally func (k *Keybase) ReadChannelPrevious(channel chat1.ChatChannel, previous []byte, num int) (chat1.Thread, error) {
// fetched with Read. However, if count is passed, then that is the number of page := chat1.Pagination{
// messages that will be fetched. Previous: previous,
func (c *ChatAPI) Next(count ...int) (*ChatAPI, error) { Num: num,
m := ChatAPI{
Params: &params{},
} }
m.Params.Options = options{
Pagination: &pagination{}, opts := ReadMessageOptions{
Channel: channel,
Pagination: &page,
} }
return k.Read(opts)
}
m.Method = "read" // ReadConversation fetches chat messages for a conversation
m.Params.Options.Channel = &c.Result.Messages[0].Msg.Channel func (k *Keybase) ReadConversation(conv chat1.ConvIDStr) (chat1.Thread, error) {
if len(count) == 0 { opts := ReadMessageOptions{
m.Params.Options.Pagination.Num = c.Result.Pagination.Num ConversationID: conv,
} else {
m.Params.Options.Pagination.Num = count[0]
} }
m.Params.Options.Pagination.Next = c.Result.Pagination.Next return k.Read(opts)
}
result, err := chatAPIOut(&c.keybase, m) // ReadConversationNext fetches the next page of messages for a conversation.
if err != nil { func (k *Keybase) ReadConversationNext(conv chat1.ConvIDStr, next []byte, num int) (chat1.Thread, error) {
return &result, err page := chat1.Pagination{
Next: next,
Num: num,
}
opts := ReadMessageOptions{
ConversationID: conv,
Pagination: &page,
} }
k := c.keybase return k.Read(opts)
*c = result
c.keybase = k
return c, nil
} }
// Previous fetches the previous page of chat messages that were fetched with Read. // ReadConversationPrevious fetches the previous page of messages for a chat channel
// By default, Previous will fetch the same amount of messages that were func (k *Keybase) ReadConversationPrevious(conv chat1.ConvIDStr, previous []byte, num int) (chat1.Thread, error) {
// originally fetched with Read. However, if count is passed, then that is the page := chat1.Pagination{
// number of messages that will be fetched. Previous: previous,
func (c *ChatAPI) Previous(count ...int) (*ChatAPI, error) { Num: num,
m := ChatAPI{
Params: &params{},
} }
m.Params.Options = options{
Pagination: &pagination{}, opts := ReadMessageOptions{
ConversationID: conv,
Pagination: &page,
} }
return k.Read(opts)
}
m.Method = "read" // UploadToChannel attaches a file to a channel
m.Params.Options.Channel = &c.Result.Messages[0].Msg.Channel // The filename must be an absolute path
if len(count) == 0 { func (k *Keybase) UploadToChannel(channel chat1.ChatChannel, title string, filename string) (chat1.SendRes, error) {
m.Params.Options.Pagination.Num = c.Result.Pagination.Num opts := SendMessageOptions{
} else { Channel: channel,
m.Params.Options.Pagination.Num = count[0] Title: title,
Filename: filename,
} }
m.Params.Options.Pagination.Previous = c.Result.Pagination.Previous
result, err := chatAPIOut(&c.keybase, m) return k.SendMessage("attach", opts)
if err != nil { }
return &result, err
// 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 return k.SendMessage("attach", opts)
c.keybase = k
return c, nil
} }
// Upload attaches a file to a conversation // Download downloads a file
// The filepath must be an absolute path func (k *Keybase) Download(options DownloadOptions) error {
func (c Chat) Upload(title string, filepath string) (ChatAPI, error) { type res struct {
m := ChatAPI{ Error *Error `json:"error"`
Params: &params{},
} }
m.Method = "attach" var r res
m.Params.Options.Channel = &c.Channel
m.Params.Options.Filename = filepath
m.Params.Options.Title = title
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 { 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 // DownloadFromChannel downloads a file from a channel
func (c Chat) Download(messageID int, filepath string) (ChatAPI, error) { func (k *Keybase) DownloadFromChannel(channel chat1.ChatChannel, msgID chat1.MessageID, output string) error {
m := ChatAPI{ opts := DownloadOptions{
Params: &params{}, Channel: channel,
MessageID: msgID,
Output: output,
} }
m.Method = "download" return k.Download(opts)
m.Params.Options.Channel = &c.Channel }
m.Params.Options.Output = filepath
m.Params.Options.MessageID = messageID
r, err := chatAPIOut(c.keybase, m) // DownloadFromConversation downloads a file from a conversation
if err != nil { func (k *Keybase) DownloadFromConversation(conv chat1.ConvIDStr, msgID chat1.MessageID, output string) error {
return r, err opts := DownloadOptions{
ConversationID: conv,
MessageID: msgID,
Output: output,
} }
return r, nil return k.Download(opts)
} }
// LoadFlip returns the results of a flip // LoadFlip returns the results of a flip
@ -517,40 +644,104 @@ func (c Chat) Mark(messageID int) (ChatAPI, error) {
return r, nil 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 // ClearCommands clears bot advertisements
func (k *Keybase) ClearCommands() (ChatAPI, error) { func (k *Keybase) ClearCommands() error {
m := ChatAPI{} type res struct {
m.Method = "clearcommands" 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 { 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 // ListMembers returns member information for a channel or conversation
// This method allows you to set up multiple different types of advertisements at once. func (k *Keybase) ListMembers(options ListMembersOptions) (keybase1.TeamDetails, error) {
// Use this method if you have commands whose visibility differs from each other. type res struct {
func (k *Keybase) AdvertiseCommands(advertisements []BotAdvertisement) (ChatAPI, error) { Result keybase1.TeamDetails `json:"result"`
m := ChatAPI{ Error *Error `json:"error,omitempty"`
Params: &params{}, }
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 { 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 // ListMembersOfConversation returns member information for a conversation
// This method allows you to set up one type of advertisement. func (k *Keybase) ListMembersOfConversation(convID chat1.ConvIDStr) (keybase1.TeamDetails, error) {
// Use this method if you have commands whose visibility should all be the same. opts := ListMembersOptions{
func (k *Keybase) AdvertiseCommand(advertisement BotAdvertisement) (ChatAPI, error) { ConversationID: convID,
return k.AdvertiseCommands([]BotAdvertisement{ }
advertisement, return k.ListMembers(opts)
})
} }

2
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 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 why things are organized in a certain way, it's most likely due to that. It may be helpful to look at the

29
v2/docs_test.go

@ -1,20 +1,35 @@
package keybase package keybase
func ExampleKeybase_AdvertiseCommand() { import "samhofi.us/x/keybase/types/chat1"
func ExampleKeybase_AdvertiseCommands() {
var k = NewKeybase() var k = NewKeybase()
// Clear out any previously advertised commands // Clear out any previously advertised commands
k.ClearCommands() k.ClearCommands()
// Create BotAdvertisement // Create BotAdvertisement
c := BotAdvertisement{ ads := AdvertiseCommandsOptions{
Type: "public", Alias: "RSS Bot",
BotCommands: []BotCommand{ Advertisements: []chat1.AdvertiseCommandAPIParam{
NewBotCommand("help", "Get help using this bot", "!help <command>"), {
NewBotCommand("hello", "Say hello", "!hello"), Typ: "public",
Commands: []chat1.UserBotCommandInput{
{
Name: "rss addfeed",
Description: "Add RSS feed",
Usage: "<url>",
},
{
Name: "rss delfeed",
Description: "Remove RSS feed",
Usage: "<url>",
},
},
},
}, },
} }
// Send advertisement // Send advertisement
k.AdvertiseCommand(c) k.AdvertiseCommands(ads)
} }

36
v2/keybase.go

@ -5,6 +5,8 @@ import (
"fmt" "fmt"
"os/exec" "os/exec"
"strings" "strings"
"samhofi.us/x/keybase/types/chat1"
) )
// Possible MemberTypes // Possible MemberTypes
@ -38,30 +40,6 @@ func NewKeybase(path ...string) *Keybase {
return k 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 // Exec executes the given Keybase command
func (k *Keybase) Exec(command ...string) ([]byte, error) { func (k *Keybase) Exec(command ...string) ([]byte, error) {
out, err := exec.Command(k.Path, command...).Output() 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 // NewChat returns a new Chat instance
func (k *Keybase) NewChat(channel Channel) Chat { func (k *Keybase) NewChat(channel chat1.ChatChannel) Chat {
return Chat{ return Chat{
keybase: k, keybase: k,
Channel: channel, 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 // NewWallet returns a new Wallet instance
func (k *Keybase) NewWallet() Wallet { func (k *Keybase) NewWallet() Wallet {
return Wallet{ return Wallet{

218
v2/kvstore.go

@ -2,136 +2,190 @@ package keybase
import ( import (
"encoding/json" "encoding/json"
"errors" "fmt"
"samhofi.us/x/keybase/types/keybase1"
) )
// kvAPIOut sends a JSON request to the kvstore API and returns its response. // KVListNamespaces returns all namespaces for a team
func kvAPIOut(k *Keybase, kv KVAPI) (KVAPI, error) { func (k *Keybase) KVListNamespaces(team *string) (keybase1.KVListNamespaceResult, error) {
jsonBytes, _ := json.Marshal(kv) 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)) cmdOut, err := k.Exec("kvstore", "api", "-m", string(jsonBytes))
if err != nil { if err != nil {
return KVAPI{}, err return r.Result, err
} }
var r KVAPI err = json.Unmarshal(cmdOut, &r)
if err := json.Unmarshal(cmdOut, &r); err != nil { if err != nil {
return KVAPI{}, err return r.Result, err
} }
if r.Error != nil { 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 // KVListKeys returns all non-deleted keys for a namespace
func (kv KV) Namespaces() (KVAPI, error) { func (k *Keybase) KVListKeys(team *string, namespace string) (keybase1.KVListEntryResult, error) {
m := KVAPI{ type res struct {
Params: &kvParams{}, Result keybase1.KVListEntryResult `json:"result"`
} Error *Error `json:"error"`
m.Params.Options = kvOptions{
Team: kv.Team,
} }
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 { if err != nil {
return r, err return r.Result, err
} }
return r, nil
}
// Keys returns all non-deleted keys for a namespace err = json.Unmarshal(cmdOut, &r)
func (kv KV) Keys(namespace string) (KVAPI, error) { if err != nil {
m := KVAPI{ return r.Result, err
Params: &kvParams{},
} }
m.Params.Options = kvOptions{
Team: kv.Team, if r.Error != nil {
Namespace: namespace, return r.Result, fmt.Errorf("%s", r.Error.Message)
} }
m.Method = "list" return r.Result, nil
}
r, err := kvAPIOut(kv.keybase, m) // KVGet returns an entry
if err != nil { func (k *Keybase) KVGet(team *string, namespace string, key string) (keybase1.KVGetResult, error) {
return r, err 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 jsonBytes, _ := json.Marshal(arg)
func (kv KV) Get(namespace string, key string, revision ...uint) (KVAPI, error) {
m := KVAPI{ cmdOut, err := k.Exec("kvstore", "api", "-m", string(jsonBytes))
Params: &kvParams{}, if err != nil {
return r.Result, err
} }
m.Params.Options = kvOptions{
Team: kv.Team, err = json.Unmarshal(cmdOut, &r)
Namespace: namespace, if err != nil {
EntryKey: key, return r.Result, err
} }
if len(revision) > 0 { if r.Error != nil {
m.Params.Options.Revision = revision[0] return r.Result, fmt.Errorf("%s", r.Error.Message)
} }
m.Method = "get" return r.Result, nil
}
r, err := kvAPIOut(kv.keybase, m) // KVPutWithRevision puts an entry, specifying the revision number
if err != nil { func (k *Keybase) KVPutWithRevision(team *string, namespace string, key string, value string, revision int) (keybase1.KVPutResult, error) {
return r, err type res struct {
Result keybase1.KVPutResult `json:"result"`
Error *Error `json:"error"`
} }
return r, nil var r res
}
// Put adds an entry opts := KVOptions{
func (kv KV) Put(namespace string, key string, value string, revision ...uint) (KVAPI, error) { Team: team,
m := KVAPI{ Namespace: &namespace,
Params: &kvParams{}, EntryKey: &key,
EntryValue: &value,
} }
m.Params.Options = kvOptions{ if revision != 0 {
Team: kv.Team, opts.Revision = &revision
Namespace: namespace,
EntryKey: key,
EntryValue: value,
} }
if len(revision) > 0 { arg := newKVArg("put", opts)
m.Params.Options.Revision = revision[0]
}
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 { 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 if r.Error != nil {
func (kv KV) Delete(namespace string, key string, revision ...uint) (KVAPI, error) { return r.Result, fmt.Errorf("%s", r.Error.Message)
m := KVAPI{
Params: &kvParams{},
} }
m.Params.Options = kvOptions{
Team: kv.Team, return r.Result, nil
Namespace: namespace, }
EntryKey: key,
// 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 { opts := KVOptions{
m.Params.Options.Revision = revision[0] 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 { 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)
} }

378
v2/types.go

@ -5,18 +5,233 @@ import (
"fmt" "fmt"
"strings" "strings"
"time" "time"
"samhofi.us/x/keybase/types/chat1"
"samhofi.us/x/keybase/types/stellar1"
) )
// RunOptions holds a set of options to be passed to Run // RunOptions holds a set of options to be passed to Run
type RunOptions struct { type RunOptions struct {
Capacity int // Channel capacity for the buffered channel that holds messages. Defaults to 100 if not set 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
Local bool // Subscribe to local messages HideExploding bool // Ignore exploding messages
HideExploding bool // Ignore exploding messages Dev bool // Subscribe to dev channel messages
Dev bool // Subscribe to dev channel messages FilterChannel chat1.ChatChannel // Only subscribe to messages from specified channel
Wallet bool // Subscribe to wallet events FilterChannels []chat1.ChatChannel // Only subscribe to messages from specified channels
FilterChannel Channel // Only subscribe to messages from specified channel }
FilterChannels []Channel // 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 // ChatAPI holds information about a message received by the `keybase chat api-listen` command
@ -229,19 +444,19 @@ type content struct {
} }
type msg struct { type msg struct {
ID int `json:"id"` ID int `json:"id"`
ConversationID string `json:"conversation_id"` ConversationID string `json:"conversation_id"`
Channel Channel `json:"channel"` Channel chat1.ChatChannel `json:"channel"`
Sender sender `json:"sender"` Sender sender `json:"sender"`
SentAt int `json:"sent_at"` SentAt int `json:"sent_at"`
SentAtMs int64 `json:"sent_at_ms"` SentAtMs int64 `json:"sent_at_ms"`
Content content `json:"content"` Content content `json:"content"`
Unread bool `json:"unread"` Unread bool `json:"unread"`
AtMentionUsernames []string `json:"at_mention_usernames"` AtMentionUsernames []string `json:"at_mention_usernames"`
IsEphemeral bool `json:"is_ephemeral"` IsEphemeral bool `json:"is_ephemeral"`
Etime int64 `json:"etime"` Etime int64 `json:"etime"`
HasPairwiseMacs bool `json:"has_pairwise_macs"` HasPairwiseMacs bool `json:"has_pairwise_macs"`
ChannelMention string `json:"channel_mention"` ChannelMention string `json:"channel_mention"`
} }
type summary struct { type summary struct {
@ -296,53 +511,12 @@ type notification struct {
Details details `json:"details"` 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 { type mesg struct {
Body string `json:"body"` 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 { type options struct {
Channel *Channel `json:"channel,omitempty"` Channel *chat1.ChatChannel `json:"channel,omitempty"`
MessageID int `json:"message_id,omitempty"` MessageID int `json:"message_id,omitempty"`
Message *mesg `json:"message,omitempty"` Message *mesg `json:"message,omitempty"`
Pagination *pagination `json:"pagination,omitempty"` Pagination *pagination `json:"pagination,omitempty"`
@ -355,8 +529,7 @@ type options struct {
ReplyTo int `json:"reply_to,omitempty"` ReplyTo int `json:"reply_to,omitempty"`
GameID string `json:"game_id,omitempty"` GameID string `json:"game_id,omitempty"`
Alias string `json:"alias,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"` Name string `json:"name,omitempty"`
Public bool `json:"public,omitempty"` Public bool `json:"public,omitempty"`
@ -437,12 +610,12 @@ type rateLimits struct {
} }
type conversation struct { type conversation struct {
ID string `json:"id"` ID string `json:"id"`
Channel Channel `json:"channel"` Channel chat1.ChatChannel `json:"channel"`
Unread bool `json:"unread"` Unread bool `json:"unread"`
ActiveAt int `json:"active_at"` ActiveAt int `json:"active_at"`
ActiveAtMs int64 `json:"active_at_ms"` ActiveAtMs int64 `json:"active_at_ms"`
MemberStatus string `json:"member_status"` MemberStatus string `json:"member_status"`
} }
type SendPayment struct { type SendPayment struct {
@ -602,11 +775,6 @@ type tParams struct {
Options tOptions `json:"options"` Options tOptions `json:"options"`
} }
type Error struct {
Code int `json:"code"`
Message string `json:"message"`
}
type tResult struct { type tResult struct {
ChatSent bool `json:"chatSent"` ChatSent bool `json:"chatSent"`
CreatorAdded bool `json:"creatorAdded"` CreatorAdded bool `json:"creatorAdded"`
@ -646,41 +814,6 @@ type teamInfo struct {
Implicit implicit `json:"implicit,omitempty"` 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 // UserAPI holds information received from the user/lookup api
type UserAPI struct { type UserAPI struct {
Status uStatus `json:"status"` Status uStatus `json:"status"`
@ -831,7 +964,7 @@ type Keybase struct {
// Chat holds basic information about a specific conversation // Chat holds basic information about a specific conversation
type Chat struct { type Chat struct {
keybase *Keybase keybase *Keybase
Channel Channel Channel chat1.ChatChannel
} }
type chat interface { type chat interface {
@ -883,29 +1016,12 @@ type wallet interface {
TxDetail(txid string) (WalletAPI, error) 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 { type keybase interface {
AdvertiseCommand(advertisement BotAdvertisement) (ChatAPI, error) ChatList(opts ...chat1.ChatChannel) (ChatAPI, error)
AdvertiseCommands(advertisements []BotAdvertisement) (ChatAPI, error)
ChatList(opts ...Channel) (ChatAPI, error)
ClearCommands() (ChatAPI, error) ClearCommands() (ChatAPI, error)
CreateTeam(name string) (TeamAPI, error) CreateTeam(name string) (TeamAPI, error)
NewChat(channel Channel) Chat NewChat(channel chat1.ChatChannel) Chat
NewTeam(name string) Team NewTeam(name string) Team
NewKV(team string) KV
NewWallet() Wallet NewWallet() Wallet
Run(handler func(ChatAPI), options ...RunOptions) Run(handler func(ChatAPI), options ...RunOptions)
status() status status() status

Loading…
Cancel
Save