Version 2.0.0-alpha1 (really)
This commit is contained in:
871
v2/chat.go
871
v2/chat.go
@ -2,47 +2,19 @@ package keybase
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"samhofi.us/x/keybase/types/chat1"
|
||||
"samhofi.us/x/keybase/types/keybase1"
|
||||
"samhofi.us/x/keybase/types/stellar1"
|
||||
)
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
c <- jsonData
|
||||
if notification.Msg != nil {
|
||||
subs.chat <- *notification.Msg
|
||||
}
|
||||
}(scanner, c)
|
||||
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
|
||||
}
|
||||
}
|
||||
}(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 handlers.WalletHandler != nil {
|
||||
runOptions = append(runOptions, "--wallet")
|
||||
}
|
||||
if options[0].Heartbeat > 0 {
|
||||
heartbeatFreq = options[0].Heartbeat
|
||||
if handlers.ConversationHandler != nil {
|
||||
runOptions = append(runOptions, "--convs")
|
||||
}
|
||||
if options[0].Local {
|
||||
|
||||
if options != nil {
|
||||
if options.Capacity > 0 {
|
||||
channelCapacity = options.Capacity
|
||||
}
|
||||
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))
|
||||
}
|
||||
}
|
||||
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)
|
||||
runOptions = append(runOptions, createFilterString(options.FilterChannel))
|
||||
}
|
||||
}
|
||||
|
||||
// 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, nil
|
||||
return r.Result, err
|
||||
}
|
||||
|
||||
// SendEphemeral sends an exploding chat message, with specified duration
|
||||
func (c Chat) SendEphemeral(duration time.Duration, message ...string) (ChatAPI, error) {
|
||||
m := ChatAPI{
|
||||
Params: ¶ms{},
|
||||
}
|
||||
m.Params.Options = options{
|
||||
Message: &mesg{},
|
||||
}
|
||||
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)
|
||||
err = json.Unmarshal(cmdOut, &r)
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
return r, nil
|
||||
return r.Result, err
|
||||
}
|
||||
|
||||
// Reply sends a reply to a chat message
|
||||
func (c Chat) Reply(replyTo int, message ...string) (ChatAPI, error) {
|
||||
m := ChatAPI{
|
||||
Params: ¶ms{},
|
||||
}
|
||||
m.Params.Options = options{
|
||||
Message: &mesg{},
|
||||
if r.Error != nil {
|
||||
return r.Result, fmt.Errorf("%v", r.Error.Message)
|
||||
}
|
||||
|
||||
m.Method = "send"
|
||||
m.Params.Options.Channel = &c.Channel
|
||||
m.Params.Options.ReplyTo = replyTo
|
||||
m.Params.Options.Message.Body = strings.Join(message, " ")
|
||||
return r.Result, nil
|
||||
}
|
||||
|
||||
r, err := chatAPIOut(c.keybase, m)
|
||||
// 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...),
|
||||
},
|
||||
}
|
||||
|
||||
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...),
|
||||
},
|
||||
}
|
||||
|
||||
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 k.SendMessage("send", opts)
|
||||
}
|
||||
|
||||
// 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},
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
return k.SendMessage("send", opts)
|
||||
}
|
||||
|
||||
// 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 k.SendMessage("send", opts)
|
||||
}
|
||||
|
||||
// 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,
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
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 k.SendMessage("reaction", opts)
|
||||
}
|
||||
|
||||
// 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,
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
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 k.SendMessage("delete", opts)
|
||||
}
|
||||
|
||||
// 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"`
|
||||
}
|
||||
|
||||
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, nil
|
||||
return r.Result, err
|
||||
}
|
||||
|
||||
// Edit edits a previously sent chat message
|
||||
func (c Chat) Edit(messageID int, message ...string) (ChatAPI, error) {
|
||||
m := ChatAPI{
|
||||
Params: ¶ms{},
|
||||
}
|
||||
m.Params.Options = options{
|
||||
Message: &mesg{},
|
||||
}
|
||||
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)
|
||||
err = json.Unmarshal(cmdOut, &r)
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
return r, nil
|
||||
return r.Result, err
|
||||
}
|
||||
|
||||
// React sends a reaction to a message.
|
||||
func (c Chat) React(messageID int, reaction string) (ChatAPI, error) {
|
||||
m := ChatAPI{
|
||||
Params: ¶ms{},
|
||||
if r.Error != nil {
|
||||
return r.Result, fmt.Errorf("%v", r.Error.Message)
|
||||
}
|
||||
m.Params.Options = options{
|
||||
Message: &mesg{},
|
||||
}
|
||||
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 r.Result, nil
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
arg := newReadMessageArg(options)
|
||||
|
||||
jsonBytes, _ := json.Marshal(arg)
|
||||
|
||||
cmdOut, err := k.Exec("chat", "api", "-m", string(jsonBytes))
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
return r, nil
|
||||
return r.Result, err
|
||||
}
|
||||
|
||||
// Delete deletes a chat message
|
||||
func (c Chat) Delete(messageID int) (ChatAPI, error) {
|
||||
m := ChatAPI{
|
||||
Params: ¶ms{},
|
||||
}
|
||||
m.Method = "delete"
|
||||
m.Params.Options.Channel = &c.Channel
|
||||
m.Params.Options.MessageID = messageID
|
||||
|
||||
r, err := chatAPIOut(c.keybase, m)
|
||||
err = json.Unmarshal(cmdOut, &r)
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
return r, nil
|
||||
return r.Result, err
|
||||
}
|
||||
|
||||
// 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{},
|
||||
if r.Error != nil {
|
||||
return r.Result, fmt.Errorf("%v", r.Error.Message)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
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{},
|
||||
// ReadChannel fetches chat messages for a channel
|
||||
func (k *Keybase) ReadChannel(channel chat1.ChatChannel) (chat1.Thread, error) {
|
||||
opts := ReadMessageOptions{
|
||||
Channel: channel,
|
||||
}
|
||||
m.Params.Options = options{
|
||||
Pagination: &pagination{},
|
||||
return k.Read(opts)
|
||||
}
|
||||
|
||||
m.Method = "read"
|
||||
m.Params.Options.Channel = &c.Channel
|
||||
m.Params.Options.Pagination.Num = 1
|
||||
// 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,
|
||||
}
|
||||
|
||||
m.Params.Options.Pagination.Previous = getID(uint(messageID - 1))
|
||||
opts := ReadMessageOptions{
|
||||
Channel: channel,
|
||||
Pagination: &page,
|
||||
}
|
||||
return k.Read(opts)
|
||||
}
|
||||
|
||||
r, err := chatAPIOut(c.keybase, m)
|
||||
// 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,
|
||||
}
|
||||
|
||||
opts := ReadMessageOptions{
|
||||
Channel: channel,
|
||||
Pagination: &page,
|
||||
}
|
||||
return k.Read(opts)
|
||||
}
|
||||
|
||||
// ReadConversation fetches chat messages for a conversation
|
||||
func (k *Keybase) ReadConversation(conv chat1.ConvIDStr) (chat1.Thread, error) {
|
||||
opts := ReadMessageOptions{
|
||||
ConversationID: conv,
|
||||
}
|
||||
return k.Read(opts)
|
||||
}
|
||||
|
||||
// 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,
|
||||
}
|
||||
return k.Read(opts)
|
||||
}
|
||||
|
||||
// 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,
|
||||
}
|
||||
|
||||
opts := ReadMessageOptions{
|
||||
ConversationID: conv,
|
||||
Pagination: &page,
|
||||
}
|
||||
return k.Read(opts)
|
||||
}
|
||||
|
||||
// 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,
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
return k.SendMessage("attach", opts)
|
||||
}
|
||||
|
||||
// Download downloads a file
|
||||
func (k *Keybase) Download(options DownloadOptions) error {
|
||||
type res struct {
|
||||
Error *Error `json:"error"`
|
||||
}
|
||||
var r res
|
||||
|
||||
arg := newDownloadArg(options)
|
||||
|
||||
jsonBytes, _ := json.Marshal(arg)
|
||||
|
||||
cmdOut, err := k.Exec("chat", "api", "-m", string(jsonBytes))
|
||||
if err != nil {
|
||||
return &r, err
|
||||
}
|
||||
r.keybase = *c.keybase
|
||||
return &r, nil
|
||||
return err
|
||||
}
|
||||
|
||||
// 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{},
|
||||
}
|
||||
m.Params.Options = options{
|
||||
Pagination: &pagination{},
|
||||
}
|
||||
|
||||
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]
|
||||
}
|
||||
|
||||
r, err := chatAPIOut(c.keybase, m)
|
||||
err = json.Unmarshal(cmdOut, &r)
|
||||
if err != nil {
|
||||
return &r, err
|
||||
}
|
||||
r.keybase = *c.keybase
|
||||
return &r, nil
|
||||
return err
|
||||
}
|
||||
|
||||
// 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{},
|
||||
}
|
||||
m.Params.Options = options{
|
||||
Pagination: &pagination{},
|
||||
if r.Error != nil {
|
||||
return fmt.Errorf("%v", r.Error.Message)
|
||||
}
|
||||
|
||||
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]
|
||||
}
|
||||
m.Params.Options.Pagination.Next = c.Result.Pagination.Next
|
||||
|
||||
result, err := chatAPIOut(&c.keybase, m)
|
||||
if err != nil {
|
||||
return &result, err
|
||||
}
|
||||
k := c.keybase
|
||||
*c = result
|
||||
c.keybase = k
|
||||
return c, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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{},
|
||||
// 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.Params.Options = options{
|
||||
Pagination: &pagination{},
|
||||
return k.Download(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]
|
||||
// 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,
|
||||
}
|
||||
m.Params.Options.Pagination.Previous = c.Result.Pagination.Previous
|
||||
|
||||
result, err := chatAPIOut(&c.keybase, m)
|
||||
if err != nil {
|
||||
return &result, err
|
||||
}
|
||||
k := c.keybase
|
||||
*c = result
|
||||
c.keybase = k
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// 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{},
|
||||
}
|
||||
m.Method = "attach"
|
||||
m.Params.Options.Channel = &c.Channel
|
||||
m.Params.Options.Filename = filepath
|
||||
m.Params.Options.Title = title
|
||||
|
||||
r, err := chatAPIOut(c.keybase, m)
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// Download downloads a file from a conversation
|
||||
func (c Chat) Download(messageID int, filepath string) (ChatAPI, error) {
|
||||
m := ChatAPI{
|
||||
Params: ¶ms{},
|
||||
}
|
||||
m.Method = "download"
|
||||
m.Params.Options.Channel = &c.Channel
|
||||
m.Params.Options.Output = filepath
|
||||
m.Params.Options.MessageID = messageID
|
||||
|
||||
r, err := chatAPIOut(c.keybase, m)
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
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"`
|
||||
}
|
||||
|
||||
r, err := chatAPIOut(k, m)
|
||||
var r res
|
||||
|
||||
cmdOut, err := k.Exec("chat", "api", "-m", `{"method": "clearcommands"}`)
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
return r, nil
|
||||
return err
|
||||
}
|
||||
|
||||
// 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{},
|
||||
}
|
||||
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, nil
|
||||
return err
|
||||
}
|
||||
|
||||
// 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,
|
||||
})
|
||||
if r.Error != nil {
|
||||
return fmt.Errorf("%v", r.Error.Message)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 <command>"),
|
||||
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: "<url>",
|
||||
},
|
||||
{
|
||||
Name: "rss delfeed",
|
||||
Description: "Remove RSS feed",
|
||||
Usage: "<url>",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Send advertisement
|
||||
k.AdvertiseCommand(c)
|
||||
k.AdvertiseCommands(ads)
|
||||
}
|
||||
|
||||
@ -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{
|
||||
|
||||
236
v2/kvstore.go
236
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
|
||||
|
||||
m.Method = "list"
|
||||
arg := newKVArg("list", KVOptions{
|
||||
Team: team,
|
||||
Namespace: &namespace,
|
||||
})
|
||||
|
||||
r, err := kvAPIOut(kv.keybase, m)
|
||||
jsonBytes, _ := json.Marshal(arg)
|
||||
|
||||
cmdOut, err := k.Exec("kvstore", "api", "-m", string(jsonBytes))
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
return r, nil
|
||||
return r.Result, err
|
||||
}
|
||||
|
||||
// Keys returns all non-deleted keys for a namespace
|
||||
func (kv KV) Keys(namespace string) (KVAPI, error) {
|
||||
m := KVAPI{
|
||||
Params: &kvParams{},
|
||||
}
|
||||
m.Params.Options = kvOptions{
|
||||
Team: kv.Team,
|
||||
Namespace: namespace,
|
||||
}
|
||||
|
||||
m.Method = "list"
|
||||
|
||||
r, err := kvAPIOut(kv.keybase, m)
|
||||
err = json.Unmarshal(cmdOut, &r)
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
return r, nil
|
||||
return r.Result, err
|
||||
}
|
||||
|
||||
// Get returns an entry
|
||||
func (kv KV) Get(namespace string, key string, revision ...uint) (KVAPI, error) {
|
||||
m := KVAPI{
|
||||
Params: &kvParams{},
|
||||
}
|
||||
m.Params.Options = kvOptions{
|
||||
Team: kv.Team,
|
||||
Namespace: namespace,
|
||||
EntryKey: key,
|
||||
if r.Error != nil {
|
||||
return r.Result, fmt.Errorf("%s", r.Error.Message)
|
||||
}
|
||||
|
||||
if len(revision) > 0 {
|
||||
m.Params.Options.Revision = revision[0]
|
||||
return r.Result, nil
|
||||
}
|
||||
|
||||
m.Method = "get"
|
||||
// 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"`
|
||||
}
|
||||
var r res
|
||||
|
||||
r, err := kvAPIOut(kv.keybase, m)
|
||||
arg := newKVArg("get", KVOptions{
|
||||
Team: team,
|
||||
Namespace: &namespace,
|
||||
EntryKey: &key,
|
||||
})
|
||||
|
||||
jsonBytes, _ := json.Marshal(arg)
|
||||
|
||||
cmdOut, err := k.Exec("kvstore", "api", "-m", string(jsonBytes))
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
return r, nil
|
||||
return r.Result, err
|
||||
}
|
||||
|
||||
// Put adds an entry
|
||||
func (kv KV) Put(namespace string, key string, value string, revision ...uint) (KVAPI, error) {
|
||||
m := KVAPI{
|
||||
Params: &kvParams{},
|
||||
}
|
||||
m.Params.Options = kvOptions{
|
||||
Team: kv.Team,
|
||||
Namespace: namespace,
|
||||
EntryKey: key,
|
||||
EntryValue: value,
|
||||
}
|
||||
|
||||
if len(revision) > 0 {
|
||||
m.Params.Options.Revision = revision[0]
|
||||
}
|
||||
|
||||
m.Method = "put"
|
||||
|
||||
r, err := kvAPIOut(kv.keybase, m)
|
||||
err = json.Unmarshal(cmdOut, &r)
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
return r, nil
|
||||
return r.Result, err
|
||||
}
|
||||
|
||||
// Delete removes an entry
|
||||
func (kv KV) Delete(namespace string, key string, revision ...uint) (KVAPI, error) {
|
||||
m := KVAPI{
|
||||
Params: &kvParams{},
|
||||
}
|
||||
m.Params.Options = kvOptions{
|
||||
Team: kv.Team,
|
||||
Namespace: namespace,
|
||||
EntryKey: key,
|
||||
if r.Error != nil {
|
||||
return r.Result, fmt.Errorf("%s", r.Error.Message)
|
||||
}
|
||||
|
||||
if len(revision) > 0 {
|
||||
m.Params.Options.Revision = revision[0]
|
||||
return r.Result, nil
|
||||
}
|
||||
|
||||
m.Method = "del"
|
||||
// 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"`
|
||||
}
|
||||
var r res
|
||||
|
||||
r, err := kvAPIOut(kv.keybase, m)
|
||||
opts := KVOptions{
|
||||
Team: team,
|
||||
Namespace: &namespace,
|
||||
EntryKey: &key,
|
||||
EntryValue: &value,
|
||||
}
|
||||
if revision != 0 {
|
||||
opts.Revision = &revision
|
||||
}
|
||||
|
||||
arg := newKVArg("put", opts)
|
||||
|
||||
jsonBytes, _ := json.Marshal(arg)
|
||||
|
||||
cmdOut, err := k.Exec("kvstore", "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("%s", r.Error.Message)
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
opts := KVOptions{
|
||||
Team: team,
|
||||
Namespace: &namespace,
|
||||
EntryKey: &key,
|
||||
}
|
||||
if revision != 0 {
|
||||
opts.Revision = &revision
|
||||
}
|
||||
|
||||
arg := newKVArg("del", opts)
|
||||
|
||||
jsonBytes, _ := json.Marshal(arg)
|
||||
|
||||
cmdOut, err := k.Exec("kvstore", "api", "-m", string(jsonBytes))
|
||||
if err != nil {
|
||||
return r.Result, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(cmdOut, &r)
|
||||
if err != nil {
|
||||
return r.Result, err
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
336
v2/types.go
336
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
|
||||
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
|
||||
@ -231,7 +446,7 @@ type content struct {
|
||||
type msg struct {
|
||||
ID int `json:"id"`
|
||||
ConversationID string `json:"conversation_id"`
|
||||
Channel Channel `json:"channel"`
|
||||
Channel chat1.ChatChannel `json:"channel"`
|
||||
Sender sender `json:"sender"`
|
||||
SentAt int `json:"sent_at"`
|
||||
SentAtMs int64 `json:"sent_at_ms"`
|
||||
@ -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"`
|
||||
@ -438,7 +611,7 @@ type rateLimits struct {
|
||||
|
||||
type conversation struct {
|
||||
ID string `json:"id"`
|
||||
Channel Channel `json:"channel"`
|
||||
Channel chat1.ChatChannel `json:"channel"`
|
||||
Unread bool `json:"unread"`
|
||||
ActiveAt int `json:"active_at"`
|
||||
ActiveAtMs int64 `json:"active_at_ms"`
|
||||
@ -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
|
||||
|
||||
Reference in New Issue
Block a user