From ae3ed73adc0c02d31e5440a64adb1489a128c06a Mon Sep 17 00:00:00 2001 From: Sam Date: Tue, 11 Jun 2019 07:54:34 -0400 Subject: [PATCH] Add support for incoming messages. This breaks NewChat() in previous versions --- chatIn.go | 55 ++++++++++++++++++++++++++++++++++++++++++++---------- chatOut.go | 43 +++++++++++++----------------------------- keybase.go | 44 +++++++------------------------------------ 3 files changed, 65 insertions(+), 77 deletions(-) diff --git a/chatIn.go b/chatIn.go index 2995d78..ba0c460 100644 --- a/chatIn.go +++ b/chatIn.go @@ -1,19 +1,17 @@ package keybase -import () +import ( + "bufio" + "encoding/json" + "fmt" + "os/exec" +) -type chatIn struct { +type ChatIn struct { Type string `json:"type"` Source string `json:"source"` Msg chatInMsg `json:"msg"` } -type chatInChannel struct { - Name string `json:"name"` - Public bool `json:"public"` - MembersType string `json:"members_type"` - TopicType string `json:"topic_type"` - TopicName string `json:"topic_name"` -} type chatInSender struct { UID string `json:"uid"` Username string `json:"username"` @@ -84,7 +82,7 @@ type chatInContent struct { } type chatInMsg struct { ID int `json:"id"` - Channel chatInChannel `json:"channel"` + Channel Channel `json:"channel"` Sender chatInSender `json:"sender"` SentAt int `json:"sent_at"` SentAtMs int64 `json:"sent_at_ms"` @@ -96,3 +94,40 @@ type chatInMsg struct { HasPairwiseMacs bool `json:"has_pairwise_macs"` ChannelMention string `json:"channel_mention"` } + +// Creates a string of json-encoded channels to pass to keybase chat api-listen --filter-channels +func createFilterString(channelFilters ...Channel) string { + if len(channelFilters) == 0 { + return "[]" + } + + jsonBytes, _ := json.Marshal(channelFilters) + return fmt.Sprintf("%s", string(jsonBytes)) +} + +// Get new messages coming into keybase and send them into the channel +func getNewMessages(k Keybase, c chan<- ChatIn, filterString string) { + keybaseListen := exec.Command(k.Path, "chat", "api-listen", "--filter-channels", filterString) + keybaseOutput, _ := keybaseListen.StdoutPipe() + keybaseListen.Start() + scanner := bufio.NewScanner(keybaseOutput) + var jsonData ChatIn + + for scanner.Scan() { + json.Unmarshal([]byte(scanner.Text()), &jsonData) + c <- jsonData + } +} + +// Runner() runs keybase chat api-listen, and passes incoming messages to the message handler func +func (k Keybase) Runner(handler func(ChatIn), channelFilters ...Channel) { + c := make(chan ChatIn, 10) + defer close(c) + go getNewMessages(k, c, createFilterString(channelFilters...)) + for { + chat, ok := <-c + if ok { + go handler(chat) + } + } +} diff --git a/chatOut.go b/chatOut.go index 0415ce8..c76bf4a 100644 --- a/chatOut.go +++ b/chatOut.go @@ -7,21 +7,22 @@ import ( ) // ---- Struct for sending to API -type chatOut struct { // not exported +type chatOut struct { Method string `json:"method"` Params chatOutParams `json:"params"` } -type chatOutChannel struct { +type Channel struct { Name string `json:"name"` - Public bool `json:"public"` - MembersType string `json:"members_type"` - TopicName string `json:"topic_name"` + Public bool `json:"public,omitempty"` + MembersType string `json:"members_type,omitempty"` + TopicType string `json:"topic_type,omitempty"` + TopicName string `json:"topic_name,omitempty"` } type chatOutMessage struct { Body string `json:"body"` } type chatOutOptions struct { - Channel chatOutChannel `json:"channel"` + Channel Channel `json:"channel"` MessageID int `json:"message_id"` Message chatOutMessage `json:"message"` } @@ -41,22 +42,15 @@ type chatOutResultRatelimits struct { Reset int `json:"reset,omitempty"` Gas int `json:"gas,omitempty"` } -type chatOutResultChannel struct { - Name string `json:"name"` - Public bool `json:"public"` - MembersType string `json:"members_type"` - TopicType string `json:"topic_type,omitempty"` - TopicName string `json:"topic_name,omitempty"` -} type conversation struct { ID string `json:"id"` - Channel chatOutResultChannel `json:"channel"` + Channel Channel `json:"channel"` Unread bool `json:"unread"` ActiveAt int `json:"active_at"` ActiveAtMs int64 `json:"active_at_ms"` MemberStatus string `json:"member_status"` } -type ChatOut struct { // exported +type ChatOut struct { Message string `json:"message,omitempty"` ID int `json:"id,omitempty"` Ratelimits []chatOutResultRatelimits `json:"ratelimits,omitempty"` @@ -86,10 +80,7 @@ func chatAPIOut(keybasePath string, c chatOut) (chatOutResult, error) { func (c Chat) Send(message ...string) (ChatOut, error) { m := chatOut{} m.Method = "send" - m.Params.Options.Channel.Name = c.Name - m.Params.Options.Channel.Public = c.Public - m.Params.Options.Channel.MembersType = c.MembersType - m.Params.Options.Channel.TopicName = c.TopicName + m.Params.Options.Channel = c.Channel m.Params.Options.Message.Body = strings.Join(message, " ") r, err := chatAPIOut(c.keybase.Path, m) @@ -103,10 +94,7 @@ func (c Chat) Send(message ...string) (ChatOut, error) { func (c Chat) Edit(messageId int, message ...string) (ChatOut, error) { m := chatOut{} m.Method = "edit" - m.Params.Options.Channel.Name = c.Name - m.Params.Options.Channel.Public = c.Public - m.Params.Options.Channel.MembersType = c.MembersType - m.Params.Options.Channel.TopicName = c.TopicName + m.Params.Options.Channel = c.Channel m.Params.Options.Message.Body = strings.Join(message, " ") m.Params.Options.MessageID = messageId @@ -121,9 +109,7 @@ func (c Chat) Edit(messageId int, message ...string) (ChatOut, error) { func (c Chat) React(messageId int, reaction string) (ChatOut, error) { m := chatOut{} m.Method = "reaction" - m.Params.Options.Channel.Name = c.Name - m.Params.Options.Channel.MembersType = c.MembersType - m.Params.Options.Channel.TopicName = c.TopicName + m.Params.Options.Channel = c.Channel m.Params.Options.Message.Body = reaction m.Params.Options.MessageID = messageId @@ -138,10 +124,7 @@ func (c Chat) React(messageId int, reaction string) (ChatOut, error) { func (c Chat) Delete(messageId int) (ChatOut, error) { m := chatOut{} m.Method = "delete" - m.Params.Options.Channel.Name = c.Name - m.Params.Options.Channel.Public = c.Public - m.Params.Options.Channel.MembersType = c.MembersType - m.Params.Options.Channel.TopicName = c.TopicName + m.Params.Options.Channel = c.Channel m.Params.Options.MessageID = messageId r, err := chatAPIOut(c.keybase.Path, m) diff --git a/keybase.go b/keybase.go index 1919832..f358608 100644 --- a/keybase.go +++ b/keybase.go @@ -25,17 +25,10 @@ type Keybase struct { Version string } -// Channel is a map of options that can be passed to NewChat() -type Channel map[string]interface{} - // Chat holds basic information about a specific conversation type Chat struct { keybase Keybase - Name string - Public bool - MembersType string - TopicName string - TopicType string + Channel Channel } type chat interface { @@ -46,7 +39,8 @@ type chat interface { } type keybase interface { - NewChat(channel map[string]interface{}) Chat + NewChat(channel Channel) Chat + Runner(handler func(ChatIn), channelFilters ...Channel) ChatList() ([]conversation, error) loggedIn() bool username() string @@ -75,35 +69,11 @@ func NewKeybase(path ...string) Keybase { } // Return a new Chat instance -func (k Keybase) NewChat(channel map[string]interface{}) Chat { - var c Chat = Chat{} - c.keybase = k - if value, ok := channel["Name"].(string); ok == true { - c.Name = value - } - if value, ok := channel["Public"].(bool); ok == true { - c.Public = value - } else { - c.Public = false - } - if value, ok := channel["MembersType"].(string); ok == true { - c.MembersType = value - } else { - c.MembersType = USER - } - if value, ok := channel["TopicName"].(string); ok == true { - c.TopicName = value - } else { - if c.MembersType == TEAM { - c.TopicName = "general" - } - } - if value, ok := channel["TopicType"].(string); ok == true { - c.TopicType = value - } else { - c.TopicType = CHAT +func (k Keybase) NewChat(channel Channel) Chat { + return Chat{ + keybase: k, + Channel: channel, } - return c } // username() returns the username of the currently logged-in Keybase user.