Skip to content

Commit

Permalink
Use generated types from AVDL (#21)
Browse files Browse the repository at this point in the history
* Add a makefile for the bot

* Generate the bot types

* Replace Channel with chat1.ChatChannel

* Replace Send with chat1.MsgSender

* Replace Conversation with ConvSummary

* Replace Message with MsgSummary, and update message ids where needed

* Replace Leave and JoinChannelResult with EmptyRes

* Replace ThreadResult with chat1.Thread

* Remove some unused structs

* Replace CommandsAdvertisement with chat1.AdvertiseCommandsParam

* Remove ThreadResult and MessageHolder

* Replace wallet types

* Fix an example

* Use stellar1.LocalPayment for listen

* Use keybase1.TeamMembersDetails instead of ListTeamMembersResultMembers

* Replace ListUserMembershipsResultTeam with keybase1.AnnotatedMemberInfo

* Update readme

* Add .gitattributes file

* Add clean

* Update some types in the readme

* Add go fmt after generated types

* Output of 1.4.1

* Explicit default goal

* Use goimports instead of go fmt

* Skip types/

* Have golangci-lint run vet and lint

* Add dependencies to makefile

* Add a note about goimports
  • Loading branch information
Nathan Smith committed Aug 29, 2019
1 parent 0f6fe4e commit 15c0ee3
Show file tree
Hide file tree
Showing 151 changed files with 35,634 additions and 230 deletions.
1 change: 1 addition & 0 deletions .gitattributes
@@ -0,0 +1 @@
kbchat/types/ linguist-generated=true
5 changes: 5 additions & 0 deletions .golangci.yml
Expand Up @@ -9,3 +9,8 @@ linters:
- gofmt
- gocritic
- unconvert
- govet
- golint
run:
skip-dirs:
- kbchat/types/
3 changes: 1 addition & 2 deletions .travis.yml
Expand Up @@ -2,12 +2,11 @@ language: go

before_install:
- go get golang.org/x/lint/golint
- make dependencies

script:
- curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.16.0
- golangci-lint run
- go vet ./...
- golint ./...

go:
- 1.10.x
Expand Down
19 changes: 19 additions & 0 deletions Makefile
@@ -0,0 +1,19 @@
PROTOCOL_PATH=../client/protocol
AVDLC=$(PROTOCOL_PATH)/node_modules/.bin/avdlc

.DEFAULT_GOAL := types

types:
@mkdir -p kbchat/types/{keybase1,gregor1,chat1,stellar1}/
$(AVDLC) -b -l go -t -o kbchat/types/keybase1 $(PROTOCOL_PATH)/avdl/keybase1/*.avdl
$(AVDLC) -b -l go -t -o kbchat/types/gregor1 $(PROTOCOL_PATH)/avdl/gregor1/*.avdl
$(AVDLC) -b -l go -t -o kbchat/types/chat1 $(PROTOCOL_PATH)/avdl/chat1/*.avdl
$(AVDLC) -b -l go -t -o kbchat/types/stellar1 $(PROTOCOL_PATH)/avdl/stellar1/*.avdl
goimports -w ./kbchat/types/

dependencies:
go get github.com/stretchr/testify/require
go get gopkg.in/yaml.v2

clean:
rm -rf kbchat/types/
35 changes: 31 additions & 4 deletions README.md
Expand Up @@ -63,19 +63,19 @@ This must be run first in order to start the Keybase JSON API stdin/stdout inter

send a new message by specifying a TLF name

#### `API.SendMessage(channel Channel, body string) (SendResponse, error)`
#### `API.SendMessage(channel chat1.ChatChannel, body string) (SendResponse, error)`

send a new message by specifying a channel

#### `API.SendMessageByConvID(convID string, body string) (SendResponse, error)`

send a new message by specifying a conversation ID

#### `API.GetConversations(unreadOnly bool) ([]Conversation, error)`
#### `API.GetConversations(unreadOnly bool) ([]chat1.ConvSummary, error)`

get all conversations, optionally filtering for unread status

#### `API.GetTextMessages(channel Channel, unreadOnly bool) ([]Message, error)`
#### `API.GetTextMessages(channel chat1.ChatChannel, unreadOnly bool) ([]chat1.MsgSummary, error)`

get all text messages, optionally filtering for unread status

Expand Down Expand Up @@ -142,7 +142,7 @@ Returns the same object as above, but this one will have another channel on it t
fail("failed to read message: %s", err.Error())
}

if msg.Message.Content.Type != "text" {
if msg.Message.Content.TypeName != "text" {
continue
}

Expand Down Expand Up @@ -178,6 +178,33 @@ To enable use of these pre-commit hooks:
- Remove any existing pre-commit hooks via `rm .git/hooks/pre-commit`
- Configure via `pre-commit install`

### Types

Most of the types the bot uses are generated from definitions defined in the [`protocol/`](https://github.com/keybase/client/tree/master/protocol) directory inside the Keybase client repo. This ensures that the types that the bot uses are consistent across bots and always up to date with the output of the API.

To build the types for the Go bot, you'll need to clone the `client` repo in the same parent directory that contains `go-keybase-chat-bot/`.

```shell
git clone https://github.com/keybase/client
```

and install the nessecary dependencies for compiling the protocol files. This requires [node.js](https://nodejs.org) and [Yarn](https://yarnpkg.com).

```shell
cd client/protocol
yarn install
```

Then you can generate the types by using the provided Makefile in this Go bot repo. Note that [goimports](https://godoc.org/golang.org/x/tools/cmd/goimports) is required to generate the types.

```shell
go get golang.org/x/tools/cmd/goimports # if you don't have goimports installed
cd ../../go-keybase-chat-bot
make
```

Should you need to remove all the types for some reason, you can run `make clean`.

### Testing

You'll need to have a few demo bot accounts and teams to run the suite of tests. Make a copy of `kbchat/test_config.example.yaml`, rename it to `kbchat/test_config.yaml`, and replace the example data with your own. Tests can then be run inside of `kbchat/` with `go test`.
2 changes: 1 addition & 1 deletion examples/echobot/echobot.go
Expand Up @@ -36,7 +36,7 @@ func main() {
fail("failed to read message: %s", err.Error())
}

if msg.Message.Content.Type != "text" {
if msg.Message.Content.TypeName != "text" {
continue
}

Expand Down
67 changes: 35 additions & 32 deletions kbchat/kbchat.go
Expand Up @@ -11,6 +11,9 @@ import (
"strings"
"sync"
"time"

"github.com/keybase/go-keybase-chat-bot/kbchat/types/chat1"
"github.com/keybase/go-keybase-chat-bot/kbchat/types/stellar1"
)

// API is the main object used for communicating with the Keybase JSON API
Expand Down Expand Up @@ -173,7 +176,7 @@ func (a *API) getAPIPipesLocked() (io.Writer, *bufio.Reader, error) {
}

// GetConversations reads all conversations from the current user's inbox.
func (a *API) GetConversations(unreadOnly bool) ([]Conversation, error) {
func (a *API) GetConversations(unreadOnly bool) ([]chat1.ConvSummary, error) {
apiInput := fmt.Sprintf(`{"method":"list", "params": { "options": { "unread_only": %v}}}`, unreadOnly)
output, err := a.doFetch(apiInput)
if err != nil {
Expand All @@ -189,7 +192,7 @@ func (a *API) GetConversations(unreadOnly bool) ([]Conversation, error) {

// GetTextMessages fetches all text messages from a given channel. Optionally can filter
// ont unread status.
func (a *API) GetTextMessages(channel Channel, unreadOnly bool) ([]Message, error) {
func (a *API) GetTextMessages(channel chat1.ChatChannel, unreadOnly bool) ([]chat1.MsgSummary, error) {
channelBytes, err := json.Marshal(channel)
if err != nil {
return nil, err
Expand All @@ -206,10 +209,10 @@ func (a *API) GetTextMessages(channel Channel, unreadOnly bool) ([]Message, erro
return nil, fmt.Errorf("unable to decode thread: %s", err.Error())
}

var res []Message
var res []chat1.MsgSummary
for _, msg := range thread.Result.Messages {
if msg.Msg.Content.Type == "text" {
res = append(res, msg.Msg)
if msg.Msg.Content.TypeName == "text" {
res = append(res, *msg.Msg)
}
}

Expand All @@ -221,12 +224,12 @@ type sendMessageBody struct {
}

type sendMessageOptions struct {
Channel Channel `json:"channel,omitempty"`
ConversationID string `json:"conversation_id,omitempty"`
Message sendMessageBody `json:",omitempty"`
Filename string `json:"filename,omitempty"`
Title string `json:"title,omitempty"`
MsgID int `json:"message_id,omitempty"`
Channel chat1.ChatChannel `json:"channel,omitempty"`
ConversationID string `json:"conversation_id,omitempty"`
Message sendMessageBody `json:",omitempty"`
Filename string `json:"filename,omitempty"`
Title string `json:"title,omitempty"`
MsgID chat1.MessageID `json:"message_id,omitempty"`
}

type sendMessageParams struct {
Expand Down Expand Up @@ -282,7 +285,7 @@ func (a *API) doFetch(apiInput string) ([]byte, error) {
return byteOutput, nil
}

func (a *API) SendMessage(channel Channel, body string) (SendResponse, error) {
func (a *API) SendMessage(channel chat1.ChatChannel, body string) (SendResponse, error) {
arg := sendMessageArg{
Method: "send",
Params: sendMessageParams{
Expand Down Expand Up @@ -318,7 +321,7 @@ func (a *API) SendMessageByTlfName(tlfName string, body string) (SendResponse, e
Method: "send",
Params: sendMessageParams{
Options: sendMessageOptions{
Channel: Channel{
Channel: chat1.ChatChannel{
Name: tlfName,
},
Message: sendMessageBody{
Expand All @@ -339,7 +342,7 @@ func (a *API) SendMessageByTeamName(teamName string, body string, inChannel *str
Method: "send",
Params: sendMessageParams{
Options: sendMessageOptions{
Channel: Channel{
Channel: chat1.ChatChannel{
MembersType: "team",
Name: teamName,
TopicName: channel,
Expand All @@ -362,7 +365,7 @@ func (a *API) SendAttachmentByTeam(teamName string, filename string, title strin
Method: "attach",
Params: sendMessageParams{
Options: sendMessageOptions{
Channel: Channel{
Channel: chat1.ChatChannel{
MembersType: "team",
Name: teamName,
TopicName: channel,
Expand All @@ -378,8 +381,8 @@ func (a *API) SendAttachmentByTeam(teamName string, filename string, title strin
type reactionOptions struct {
ConversationID string `json:"conversation_id"`
Message sendMessageBody
MsgID int `json:"message_id"`
Channel Channel `json:"channel"`
MsgID chat1.MessageID `json:"message_id"`
Channel chat1.ChatChannel `json:"channel"`
}

type reactionParams struct {
Expand All @@ -398,7 +401,7 @@ func newReactionArg(options reactionOptions) reactionArg {
}
}

func (a *API) ReactByChannel(channel Channel, msgID int, reaction string) (SendResponse, error) {
func (a *API) ReactByChannel(channel chat1.ChatChannel, msgID chat1.MessageID, reaction string) (SendResponse, error) {
arg := newReactionArg(reactionOptions{
Message: sendMessageBody{Body: reaction},
MsgID: msgID,
Expand All @@ -407,7 +410,7 @@ func (a *API) ReactByChannel(channel Channel, msgID int, reaction string) (SendR
return a.doSend(arg)
}

func (a *API) ReactByConvID(convID string, msgID int, reaction string) (SendResponse, error) {
func (a *API) ReactByConvID(convID string, msgID chat1.MessageID, reaction string) (SendResponse, error) {
arg := newReactionArg(reactionOptions{
Message: sendMessageBody{Body: reaction},
MsgID: msgID,
Expand Down Expand Up @@ -444,12 +447,12 @@ func (a *API) Username() string {

// SubscriptionMessage contains a message and conversation object
type SubscriptionMessage struct {
Message Message
Conversation Conversation
Message chat1.MsgSummary
Conversation chat1.ConvSummary
}

type SubscriptionWalletEvent struct {
Payment Payment
Payment stellar1.PaymentDetailsLocal
}

// NewSubscription has methods to control the background message fetcher loop
Expand Down Expand Up @@ -522,16 +525,16 @@ func (a *API) Listen(opts ListenOptions) (NewSubscription, error) {
}
switch typeHolder.Type {
case "chat":
var holder MessageHolder
if err := json.Unmarshal([]byte(t), &holder); err != nil {
var notification chat1.MsgNotification
if err := json.Unmarshal([]byte(t), &notification); err != nil {
errorCh <- err
break
}
subscriptionMessage := SubscriptionMessage{
Message: holder.Msg,
Conversation: Conversation{
ID: holder.Msg.ConversationID,
Channel: holder.Msg.Channel,
Message: *notification.Msg,
Conversation: chat1.ConvSummary{
Id: notification.Msg.ConvID,
Channel: notification.Msg.Channel,
},
}
newMsgCh <- subscriptionMessage
Expand Down Expand Up @@ -615,8 +618,8 @@ func (a *API) ListChannels(teamName string) ([]string, error) {
return channels, nil
}

func (a *API) JoinChannel(teamName string, channelName string) (JoinChannelResult, error) {
empty := JoinChannelResult{}
func (a *API) JoinChannel(teamName string, channelName string) (chat1.EmptyRes, error) {
empty := chat1.EmptyRes{}

apiInput := fmt.Sprintf(`{"method": "join", "params": {"options": {"channel": {"name": "%s", "members_type": "team", "topic_name": "%s"}}}}`, teamName, channelName)
output, err := a.doFetch(apiInput)
Expand All @@ -636,8 +639,8 @@ func (a *API) JoinChannel(teamName string, channelName string) (JoinChannelResul
return joinChannel.Result, nil
}

func (a *API) LeaveChannel(teamName string, channelName string) (LeaveChannelResult, error) {
empty := LeaveChannelResult{}
func (a *API) LeaveChannel(teamName string, channelName string) (chat1.EmptyRes, error) {
empty := chat1.EmptyRes{}

apiInput := fmt.Sprintf(`{"method": "leave", "params": {"options": {"channel": {"name": "%s", "members_type": "team", "topic_name": "%s"}}}}`, teamName, channelName)
output, err := a.doFetch(apiInput)
Expand Down

0 comments on commit 15c0ee3

Please sign in to comment.