Git Product home page Git Product logo

go-keybase-chat-bot's Introduction

go-keybase-chat-bot

Build Status

Write rich bots for Keybase chat in Go.

Installation

Make sure to install Keybase.

go get -u github.com/keybase/go-keybase-chat-bot/...

Hello world

package main

import (
	"flag"
	"fmt"
	"os"

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

func fail(msg string, args ...interface{}) {
	fmt.Fprintf(os.Stderr, msg+"\n", args...)
	os.Exit(3)
}

func main() {
	var kbLoc string
	var kbc *kbchat.API
	var err error

	flag.StringVar(&kbLoc, "keybase", "keybase", "the location of the Keybase app")
	flag.Parse()

	if kbc, err = kbchat.Start(kbchat.RunOptions{KeybaseLocation: kbLoc}); err != nil {
		fail("Error creating API: %s", err.Error())
	}

	tlfName := fmt.Sprintf("%s,%s", kbc.GetUsername(), "kb_monbot")
	fmt.Printf("saying hello on conversation: %s\n", tlfName)
	if _, err = kbc.SendMessageByTlfName(tlfName, "hello!"); err != nil {
		fail("Error sending message; %s", err.Error())
	}
}

Commands

Start(runOpts RunOptions) (*API, error)

This must be run first in order to start the Keybase JSON API stdin/stdout interactive mode.

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

send a new message by specifying a channel

API.SendMessageByConvID(convID chat1.ConvIDStr, body string) (SendResponse, error)

send a new message by specifying a conversation ID

API.SendMessageByTlfName(tlfName string, body string) (SendResponse, error)

send a new message by specifying a TLF name

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

get all conversations, optionally filtering for unread status

API.GetTextMessages(channel chat1.ChatChannel, unreadOnly bool) ([]chat1.MsgSummary, error)

get all text messages, optionally filtering for unread status

Reads the messages in a channel. You can read with or without marking as read.

API.ListenForNextTextMessages() NewSubscription

Returns an object that allows for a bot to enter into a loop calling NewSubscription.Read to receive any new message across all conversations (except the bots own messages). See the following example:

API.InChatSend(channel chat1.ChatChannel, body string) (SendResponse, error)

send a new message which can contain in-chat-send payments (i.e. +5XLM@joshblum) by specifying a channel

API.InChatSendByConvID(convID chat1.ConvIDStr, body string) (SendResponse, error)

send a new message which can contain in-chat-send payments (i.e. +5XLM@joshblum) by specifying a conversation ID

API.InChatSendByTlfName(tlfName string, body string) (SendResponse, error)

send a new message which can contain in-chat-send payments (i.e. +5XLM@joshblum) by specifying a TLF name

	sub, err := kbc.ListenForNewTextMessages()
	if err != nil {
		fail("Error listening: %s", err.Error())
	}

	for {
		msg, err := sub.Read()
		if err != nil {
			fail("failed to read message: %s", err.Error())
		}

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

		if msg.Message.Sender.Username == kbc.GetUsername() {
			continue
		}

		if _, err = kbc.SendMessage(msg.Message.Channel, msg.Message.Content.Text.Body); err != nil {
			fail("error echo'ing message: %s", err.Error())
		}
	}

API.Listen(kbchat.ListenOptions{Wallet: true}) NewSubscription

Returns the same object as above, but this one will have another channel on it that also gets wallet events. You can get those just like chat messages: NewSubscription.ReadWallet. So if you care about both of these types of events, you might run two loops like this:

	sub, err := kbc.Listen(kbchat.ListenOptions{Wallet: true})
	if err != nil {
		fail("Error listening: %s", err.Error())
	}

	go func() {
		for {
			payment, err := sub.ReadWallet()
			if err != nil {
				fail("failed to read payment event: %s", err.Error())
			}
			tlfName := fmt.Sprintf("%s,%s", payment.Payment.FromUsername, "kb_monbot")
			msg := fmt.Sprintf("thanks for the %s!", payment.Payment.AmountDescription)
			if _, err = kbc.SendMessageByTlfName(tlfName, msg); err != nil {
				fail("error thanking for payment: %s", err.Error())
			}
		}
	}()

	for {
		msg, err := sub.Read()
		if err != nil {
			fail("failed to read message: %s", err.Error())
		}

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

		if msg.Message.Sender.Username == kbc.GetUsername() {
			continue
		}

		if _, err = kbc.SendMessage(msg.Message.Channel, msg.Message.Content.Text.Body); err != nil {
			fail("error echo'ing message: %s", err.Error())
		}
	}

TODO:

  • attachment handling (posting/getting)
  • edit/delete
  • many other things!

Contributions

  • welcomed!

Precommit hooks

We check all git commits with pre-commit hooks generated via pre-commit.com pre-commit hooks. To enable use of these pre-commit hooks:

  • Install the pre-commit utility. For some common cases:
    • pip install pre-commit
    • brew install pre-commit
  • 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/ 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/.

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

and install the necessary dependencies for compiling the protocol files. This requires node.js and Yarn.

cd client/protocol
yarn install

Then you can generate the types by using the provided Makefile in this Go bot repo. Note that goimports is required to generate the types.

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

You can optionally specify a directory to the client protocol when making the types if client and go-keybase-chat-bot are not in the same directory.

make PROTOCOL_PATH=path/to/client/protocol

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.

go-keybase-chat-bot's People

Contributors

balboah avatar ddworken avatar heronhaye avatar joshblum avatar kf5grd avatar leighmcculloch avatar marceloneil avatar maxtaco avatar mmaxim avatar mmou avatar pzduniak avatar xgess avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

go-keybase-chat-bot's Issues

Proposal: Parity between bot functions and Keybase API names

Many of the names and functions that the go bot uses are very specific. Examples:

  • GetTextMessages (all other message types are ignored)
  • SendAttachmentByTeam

Rather than have specific functions for cases like these, I think it'd make more sense to have more general options that map 1-to-1 to the functions exposed to us by the underlying Keybase API. The TypeScript bot is a good example of this; the functionality for the above functions is implemented via read and attach, respectively.

That being said, I think there are some methods like SendMessageByTlfName that save the creation of a lot of new channel objects. Since the bot types will require a major version upgrade, I'm opening this issue to start a discussion on what function signatures it'd make sense to change in a v2.

Keybase

Hi,
I have installed Keybase today on windows7, but after installation, only a black window is appearing. Nothing else is showing.
Please guide what to do?
Regards

CombinedOutput causes unmarshal error

Some functions such as ListMembersOfTeam will error if the keybase cli has any warnings, as the function tries to unmarshal both stdout and stderr. Here is an example payload that failed unmarshaling:

2020-02-12T16:04:47.296724-05:00 ▶ [WARN keybase versionfix.go:236] 001 KBFS needs to restart; running version 5.2.0, but 5.3.0-20200212090128+1e4b858d80 installed.
{"result": "..."}

Proposal: rename bot package

Our bot is starting to integrate wallet and team features. If we were to do a major upgrade, I think it'd make sense to rename the package from kbchat to something more general, like kbbot.

Memory leak when `keybase status` times out

In my GCP Cloud Run logs I have a string of the following

2020-02-20 17:00:00.639 PST2020/02/21 01:00:00 Listen: failed to auth: unable to run Keybase command
2020-02-20 17:00:29.641 PST2020/02/21 01:00:29 Listen: failed to auth: unable to run Keybase command
2020-02-20 17:01:17.639 PST2020/02/21 01:01:17 Listen: failed to auth: unable to run Keybase command
2020-02-20 17:02:27.939 PST2020/02/21 01:02:27 Listen: failed to auth: unable to run Keybase command
2020-02-20 17:02:54.638 PST2020/02/21 01:02:54 Listen: failed to auth: unable to run Keybase command
2020-02-20 17:03:18.541 PST2020/02/21 01:03:18 Listen: failed to auth: unable to run Keybase command
2020-02-20 17:03:43.739 PST2020/02/21 01:03:43 Listen: failed to auth: unable to run Keybase command
2020-02-20 17:04:03.739 PST2020/02/21 01:04:03 Listen: failed to auth: unable to run Keybase command
2020-02-20 17:04:42.038 PST2020/02/21 01:04:42 Listen: failed to auth: unable to run Keybase command
2020-02-20 17:05:09.139 PST2020/02/21 01:05:09 Listen: failed to auth: unable to run Keybase command
2020-02-20 17:05:33.239 PST2020/02/21 01:05:33 Listen: failed to auth: unable to run Keybase command
2020-02-20 17:06:11.238 PST2020/02/21 01:06:11 Listen: failed to auth: unable to run Keybase command
2020-02-20 17:07:00.939 PST2020/02/21 01:07:00 Listen: failed to auth: unable to run Keybase command

Which finally ends in a

2020-02-20 17:18:31.241 PSTMemory limit of 1024M exceeded with 1024M used. Consider increasing the memory limit, see https://cloud.google.com/run/docs/configuring/memory-limits

All of them are Listen: failed to auth: unable to run Keybase command, which comes from

case <-time.After(5 * time.Second):
return "", errors.New("unable to run Keybase command")
}

The reason why getUsername() is failing is unclear (and probably unrelated), but it doesn't seem that memory usage should increase as it keeps retrying.

Use GoDoc

Currently, we document all our functions in the markdown of the README. I think it'd be nice and more idiomatic if we transferred most of this documentation over to GoDoc comments and linked to the godoc.org page for the bot.

That being said, I think it's still important to describe some basic functionalities and examples in the README, I just think the comprehensive documentation would make more sense in a medium better suited for it.

Command.stdoutpipe fails on GCP Cloud Run

Breaking this out from #55

Google Cloud's serverless container environment is called Cloud Run.

I'm trying to run a bot here in a container, but it kept crashing on calls to kbc.GetUsername(). Eventually this was tracked down to be a problem with the Command("status") call.

The issue is that Cloud Run captures STDOUT and STDERR without duplicating the file descriptors, so those original STDOUT/STDERR FDs are no longer doing what we expect. getUsername() expects to shell out to keybase status and read its STDOUT, but since the FDs are being hogged by Cloud Run's logging, the output doesn't come back and it ends up timing out as noted in #55.

In that issue I recommended 2 potential fixes: using a different FD that we know Cloud Run isn't hogging, or using another API altogether that doesn't involve shelling out to the keybase CLI.

@malware-unicorn PoC'd the first option successfully.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.